Dead Letter Queue(DLQ)とは?
- 通常処理にて配信が叶わなかったメッセージを格納します
- 継続的なエラーが発生するメッセージ
- 再送回数が上限に達するメッセージなどが該当
配信に失敗したメッセージを別途置いておくことで、後から分析することが可能
DLQを放置して、障害対応が地獄になっていた・・・
数年前にいたとある現場の話になります。
ECSワーカー(ECS on EC2)でSQSをポーリング処理する基盤を
途中から運用することになりました。
簡単なフロー
- Web/API → SQSに投入
- ECSワーカーがポーリングして実行
- 失敗はDLQへ
所謂「失敗に強い」構成を目指したという背景が..
障害対応時
…が、実際の障害対応でこうなりました↓
- DLQに溜まってるのは分かる
- しかし 何が原因で失敗したのか追えない状況
- どの処理?どのタイミング?か全く分からない
- 「とりあえず戻す」と同じ失敗を繰り返して、ノイズだけ増える
結論:DLQを作って満足してしまっている・・・
なぜDLQ放置が辛いのか
DLQは「失敗したキュー」ですが、正確にはこうです。
DLQは 失敗メッセージを隔離して、正常系を止めずに、調査と再処理を可能にするため の仕組み
つまり、DLQに落ちた瞬間必要なのは次の3つ。
- 追える(調査できること)
- 戻せる(再処理できること)
- 壊さない(冪等性があること)
この3つが欠けると、「DLQがあるのに辛い」になります。
前提:今回の構成(ECS on EC2 / ポーリング)
- ECS on EC2 でワーカーを稼働
- ワーカーがSQSをポーリングして処理
- 成功:
DeleteMessage - 失敗:削除しない → 再配信 → 一定回数でDLQ
処理時間は平均 2分程度/ジョブ。
“失敗に強い”を成立させる4つの柱
キュー構成によくある「失敗に強い」といわれる構成はどのようなものなのでしょうか?
1) 非同期化(ピークを吸収)
- APIを軽くして、重い処理をワーカーへ逃がせる
- 負荷がピークな際もサービスが落ちにくくなるメリットがある
2) リトライ(仕組みで再試行)
- 失敗時に
DeleteMessageしなければ、SQSが再配信してくれる
3) 隔離(DLQで毒メッセージを止めない)
- 永遠に失敗する入力が混ざっても、DLQに逃がして正常系を止めない
4) 冪等性(再試行が怖くない)
- ここがないと、リトライ=二重実行の地雷になる。
修正:DLQ運用の最低ラインを作る
(A) 監視:DLQのメッセージ数をアラートにする
DLQは放置すると「あとで気づく」になりがちです(笑)
最低限:
- DLQの
ApproximateNumberOfMessagesVisible > 0でアラート - 可能なら「増加率」も(急増はデグレや外部障害のサイン)
“DLQが気づいたら溜まってた” ではなく
“DLQが溜まったら都度確認” が運用のスタート!
ApproximateNumberOfMessagesVisibleとは?
- 取得・処理が可能なメッセージの数
- エラーメッセージがDLQに来るが、この値が1以上になったらアラームを設定することがよくあるらしい
(B) DLQの1件からログまで辿れるようにする
DLQが辛い最大の理由は「辿れない」こと。
メッセージに最低限入れると運用が変わります。
-
job_id(ジョブの一意ID) -
tenant_id(マルチテナントなら) -
resource_id(対象ID) -
trace_id(ログ/トレースと紐付け)
特に当時のプロジェクトはユーザー数が多く、負荷も高かったため
メッセージから判別できると原因に辿り着くスピードが上がると考えました。
障害対応の速度は、だいたい「相関IDがあるか」で決まる。
(C) DLQが出た時に「何をするか」を決める
DLQを見ても、人によって対応がブレると回りません。
障害対応の基本かもですが、実際発生した際どう動くかを決めておくといざという時
迅速かつ落ち着いて対処できます。
最低限のRunbook例:
- DLQ件数と増加速度を確認
- 影響範囲(tenant_id / 機能)を把握
- (B)で設定した
job_idでログ検索 → 失敗理由を分類 - 分類に応じて対処
- 一時障害:復旧後に再投入
- 恒久障害:入力修正/仕様修正/データ修正
- 再投入する場合は「再投入条件」を満たしてから実施
- 再投入後、成功率とDLQ増加が止まったことを確認
etc...
(D) 再処理(リドライブ):戻す手順を用意する
ちなみにDLQからの復旧処理として、修正済メッセージを元に戻す機能があったりします。
またDLQは「再処理する前提」なので、戻し方を決めておきます!
考え方:
- 原因が未解決のまま戻さない
- 戻した後に「成功したか」を検証する
以下方法も可能なので参考になれば・・・
- バッチ処理などによる復旧
- 段階的復旧
冪等性(迷うならジョブ台帳方式)
DLQ運用と相性が良いのは ジョブ台帳(ledger)方式です。
ジョブ台帳方式とは
- メッセージに
job_idを必ず入れる - DBに
job_idを 一意制約 で保存する - すでに成功済みなら安全にスキップできる
メリット:
- DLQからの再投入が怖くない(同じジョブが2回走っても壊れない)
- 失敗理由や試行回数も記録できる → 調査が速い
DLQ運用が楽になるのは「DLQがあるから」ではなく
冪等性があるからDLQを運用できる という順番。
失敗の分類(DLQ運用が一気に楽になる)
繰り返しになりますが、DLQの苦労は「全部同じ失敗」に見えることです。
運用上はこの2種類に分けてみました。
(サービスや現場によってルールは変わりますが・・・)
一時障害(Transient)
- 外部APIの瞬断
- DBの一時的な負荷
- ネットワーク揺らぎ
→ リトライが効く。復旧後に再投入しやすい。
恒久障害(Permanent)
- 入力不正(データフォーマット等の形式エラー)
- 存在しないID
- 権限不足
→ リトライしても無駄!早めに隔離して原因修正に寄せる。
無駄なリトライを減らすことも重要!
まとめ
いかがでしたでしょうか。
1つの現場の例なのでどこまで参考になるかは分かりませんが、
DLQの調査に時間を要してしまったりしている人のお役に立てれば幸いです!
参考
Amazon SQS best practices