Dead-letter queueとは?処理できないイベントを隔離して安全に戻す
一つの壊れたイベントが何度も失敗し、ワーカーの再試行とログを埋め続けることがある。そのイベントを捨てれば業務データが欠け、無限に再試行すれば後続処理まで遅くなる。Dead-letter queueは、通常の再試行では処理できないメッセージを本流から隔離し、原因調査と回復のために残す場所である。
DLQを設定するだけでは回復できない。なぜ移されたかを記録し、監視し、修正後に対象を選んで戻す運用まで必要になる。誰も見ないDLQは、失敗を見えない場所へ移しただけである。
一時的な失敗と恒久的な失敗を分ける
外部APIの短い障害、レート制限、ネットワーク切断は、時間を置けば回復する可能性がある。指数バックオフと上限付きの再試行を使う。一方、必須項目がない、schemaが読めない、参照先が永久に削除された、権限がないといった失敗は、同じ入力を同じコードへ何度送っても直らない。
後者をpoison messageとして隔離すれば、後続メッセージを処理し続けられる。ただし、失敗回数だけでは原因を判定できない。外部サービスが長時間停止した場合、大量の正常メッセージがDLQへ移ることもある。例外の種類、HTTP状態、再試行可能性、対象の業務期限を見て扱いを決める。
AIエージェントでは、モデルが返した構造化出力の形式不正、廃止された道具名、対象リソースの消失、承認期限切れ、外部APIの恒久的な入力拒否などが候補になる。モデルへ同じ指示を投げ直すだけでなく、入力と契約を直すべき失敗かを確認する。
最大試行回数は小さすぎても大きすぎても困る
メッセージを何回受け取ったらDLQへ移すかを設定する。Amazon SQSではredrive policyの maxReceiveCount がこの役割を持つ。AWSは値が1のように低すぎると一度の受信失敗で移動するため、システムが一時的なエラーへ耐えられる回数を確保するよう案内している。
反対に回数を大きくしすぎると、恒久的に失敗するメッセージが長く本流へ残る。各試行でLLMや外部APIを呼ぶなら、費用とレート枠も消費する。試行回数だけでなく、最初の失敗からの経過時間、合計費用、業務期限も停止条件にする。
Google Cloud Pub/Subではdead-letter topicへ送る最大配送試行回数を設定できるが、公式資料は回数がbest effortであり、設定より少ないまたは多い試行後に転送される場合があるとしている。回数を厳密な業務保証として使わず、アプリ側でも操作IDと状態を管理する。
DLQへ原因と文脈を残す
元のpayloadだけでは、数日後に原因を調べられない。少なくとも次の情報を関連付ける。
- 元のevent IDとoperation ID
- 元queue、subscription、consumer
- event typeとschema版
- 最初と最後の失敗時刻
- 配送・処理の試行回数
- 正規化したエラー分類
- エラーの発生段階
- 使用したアプリ、プロンプト、道具定義の版
- 対象IDとtrace ID
例外スタックや外部応答を保存するときは、認証情報、会話本文、個人情報をそのまま複製しない。調査に必要な最小情報へ絞り、payloadとエラー詳細の閲覧権限を分ける。
Azure Service Busは DeadLetterReason と説明を持ち、最大配送回数超過、期限切れ、session ID欠落、filter評価エラーなどの原因を区別できる。アプリ独自の隔離でも、自由文だけではなく集計可能なreason codeを設ける。
DLQを通常queueのように監視する
DLQへ入った件数が1件でも重要な業務と、一定の不正入力が想定内の業務では警告条件が違う。総件数だけでなく、次を見る。
- 新規流入件数と増加率
- 最古メッセージの経過時間
- エラー分類別の件数
- event type、schema版、consumer版ごとの偏り
- 同じ対象や利用者への集中
- 未調査、修正中、再処理待ち、解決済みの件数
一件ずつ通知すると大量障害で警告が埋まる。新しいreason code、急増、業務期限接近、最古時間の超過を別のalertにする。ダッシュボード上の件数が減っただけで解決としない。削除、期限切れ、再処理成功のどれで減ったかを記録する。
保持期間は元queueより長くする
調査前に消えればDLQの意味がない。Amazon SQSのstandard queueでは、DLQへ移しても元のenqueue時刻を基準に期限が進む。AWSはDLQの保持期間を元queueより長くすることを推奨している。
一方で無期限保存は、費用と個人情報保持の問題になる。障害検知までの時間、担当者が調査できる時間、再処理可能期間、法的な保持方針を基に期間を決める。期限切れ前に未解決メッセージを安全な調査用ストレージへ移す場合も、暗号化、アクセス記録、削除期限を持たせる。
修正せずに一括で戻さない
DLQから元queueへ戻すredriveは、原因を直したあとに行う。外部障害が回復していない、schema変換が未導入、権限が不足したままなら、再び失敗して往復するだけである。
再処理前にメッセージをreason codeで分け、少数の代表例をshadowまたは検証用consumerへ通す。成功、出力差分、副作用、費用を確認し、短い範囲から戻す。redriveの速度を制限し、通常トラフィックの容量を奪わないようにする。
Amazon SQSのDLQ redriveでは移動速度を設定できる。公式資料は、redriveしたメッセージと生産側からの新規メッセージが宛先queueで混ざると説明している。古いメッセージが先に処理されるとは限らないため、対象ごとの版と順序を受信側で確認する。
再処理は新規イベントと同じ安全策を通す
DLQから戻したメッセージも重複する可能性がある。元event IDとoperation IDを維持し、受信側の冪等性を使う。製品によってredrive時に新しいmessage IDが付く場合があるため、brokerのmessage IDだけを業務上の重複判定に使わない。
現在状態が変わっている場合、古い命令をそのまま適用しない。例えば「未対応チケットへ返信する」イベントがDLQに入っている間に、人がチケットを閉じているかもしれない。再処理直前に現在状態、権限、期限、対象版を再検査する。
メール送信、購入、公開、削除のような不可逆な作用は、redriveの一括操作から切り離す。自動再処理で安全な派生データ更新と、人の承認が必要な外部作用を同じconsumerに混ぜない。
順序が重要なら隔離の影響を設計する
途中の一件をDLQへ送って後続を処理すると、業務上の順序が崩れる場合がある。AWSも、厳密な順序を壊したくないFIFO処理ではDLQを使わないよう注意している例がある。
同じ注文や会話のイベントが前の結果に依存するなら、対象単位で処理を止める、保留状態にする、後続を別に待たせる方法を検討する。全体を止める必要はなく、ordering keyや対象IDの範囲で隔離する。順序を守れない場合は、後続処理が前提不足を検出し、安全に待機できるようにする。
DLQから学んだ失敗を本流へ戻す
同じreason codeが増えたら、個別メッセージの修正だけで終えない。schema検証を生産側へ追加する、道具の入力定義を明確にする、権限チェックを早める、期限切れを送信前に除くなど、失敗を入口側で防ぐ。
回帰テストには、実際に隔離された入力を匿名化して加える。既知のpoison messageが本流を塞がず、正しいreasonでDLQへ入り、修正後に一度だけ処理されることを確認する。DLQ件数をゼロに見せるため検証を緩めるのではなく、失敗を正しく分類して回復時間を短くする。
Dead-letter queueはゴミ箱ではなく、通常処理と障害調査の境界である。再試行してよい失敗だけを十分に試し、処理できないものを原因付きで隔離する。監視、保持、修正、段階的なredriveまで決めて初めて、失敗イベントを失わずに本流を守れる。