【AWS SQS】DLQを放置して痛い目を見た話
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が溜まったら都度確認できる状態” が運用のスタート!
(公式:SQS CloudWatch metrics)
(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増加が止まったことを確認
…など
(D) 再処理(リドライブ):戻す手順を用意する
DLQからの復旧手段として、DLQのメッセージを元のキューへ戻す(再投入する)運用があります。
DLQは「再処理する前提」なので、戻し方を決めておきます。
考え方
- 原因が未解決のまま戻さない
- 戻した後に「成功したか」を検証する
冪等性(迷うならジョブ台帳方式)
DLQ運用と相性が良いのは ジョブ台帳(ledger)方式です。
ジョブ台帳方式とは
- メッセージに
job_idを必ず入れる - DBに
job_idを 一意制約 で保存する - すでに成功済みなら安全にスキップできる
メリット:
- DLQからの再投入が怖くない(同じジョブが2回走っても壊さない)
- 失敗理由や試行回数も記録できる → 調査が速い
失敗の分類(DLQ運用が一気に楽になる)
繰り返しになりますが、DLQの苦労は「全部同じ失敗」に見えることです。
運用上はこの2種類に分けるだけで、かなり楽になります。
(サービスや現場によってルールは変わりますが…)
一時障害(Transient)
- 外部APIの瞬断
- DBの一時的な負荷
- ネットワーク揺らぎ
→ リトライが効く。復旧後に再投入しやすい。
恒久障害(Permanent)
- 入力不正(データフォーマット等の形式エラー)
- 存在しないID
- 権限不足
→ リトライしても無駄!早めに隔離して原因修正に寄せる。
まとめ
いかがでしたでしょうか。
1つの現場の例なのでどこまで参考になるかは分かりませんが、
DLQの調査に時間を要してしまっている人のお役に立てれば幸いです!
参考
Amazon SQS best practices
Discussion