メッセージキューの滞留を解消するには?処理能力・再試行・DLQの設計
ワーカーを増やしてもキューが減らない、DLQを戻したら再び障害になる。Amazon SQSを例に、滞留の解消時間、業務完了の計測、冪等性、下流の余力に合わせた再投入をつないで解説します。
キューが減らないとき、最初に何を見るか
キューは瞬間的な流入を後で処理するための緩衝材です。受信APIが速く返せても、注文や通知が期限までに終わるとは限りません。まず「受付から業務完了まで何分以内か」を決め、通常の流入、重複を除いた完了、未完了件数、最も古い業務の年齢を同じ時間軸で並べます。
| 観測 | 考えられる状態 | 次の確認 |
|---|---|---|
| 流入が完了を上回り、年齢も増える | 処理能力の不足、または下流の遅延 | ワーカーの稼働数、DB待ち、外部API制限 |
| 試行回数だけ増えて完了が横ばい | 再試行が処理枠を消費している | 試行回数ごとの成功率とエラー分類 |
| 元キューは減り、DLQが増える | 失敗を隔離している | 未完了の業務件数と復旧担当 |
| 一部の業務だけ古いまま残る | 不正データ、特定キーの競合や順序制約 | 失敗例、キーごとの偏り、処理の依存関係 |
Amazon SQSのキュー件数や年齢は近似値です。標準キューの最古メッセージ年齢には繰り返し失敗するメッセージが含まれない場合があり、DLQへ移すと年齢の基準も変わります。ペイロードか業務台帳に元の受付時刻を残し、移動後も追えるようにします。
滞留の解消時間を計算する:B ÷(μ − λ)
ここからは独自の仮定による計算です。処理時間の分布やジョブ構成が変わらず、流入λと有効な完了能力μが一定で、失敗隔離や期限切れによる離脱もない単純なモデルを使います。μがλより大きければ、未完了B件を解消する時間は B / (μ - λ) です。μ以下へ流入を抑えるだけでは、余力がなければ既存の滞留は減りません。
毎秒1,200件の流入を毎秒1,000件で15分処理すると、18万件残ります。ピーク後に流入が毎秒800件へ戻れば毎秒200件ずつ減り、解消まで15分。流入が毎秒1,200件のままでも、完了能力を毎秒1,800件へ増やせれば5分です。
# Python 3: illustrative constant-rate model, not a latency prediction
backlog = (1200 - 1000) * (15 * 60)
print(backlog) # 180000 logical jobs
def drain_seconds(backlog, arrival_per_second, completion_per_second):
if completion_per_second <= arrival_per_second:
return None # no finite drain time at these constant rates
return backlog / (completion_per_second - arrival_per_second)
print(drain_seconds(backlog, 800, 1000)) # 900.0 seconds
print(drain_seconds(backlog, 1200, 1800)) # 300.0 seconds
print(drain_seconds(backlog, 1200, 1000)) # None- 流入800件/秒・完了1,000件/秒: 900秒(15分)
- 流入1,200件/秒・完了1,800件/秒: 300秒(5分)
毎秒の試行数と、毎秒の業務完了数を分ける
ワーカーが毎秒2,000回動いても、同じ失敗を繰り返していれば完了能力は増えません。初回受付数、全試行数、業務ID単位の初回成功数、隔離数を別々に記録します。元キューと処理中の両方を見て、受信しただけのメッセージを完了へ数えないことも必要です。
監視用のキュー離脱数にはDLQへの移動や期限切れが混ざるため、そのまま前節のμへ代入できません。業務台帳には未完了、成功、要調査などの状態を残します。期限を過ぎた通知を破棄できるか、注文を補償すべきかは業務ごとに決め、単に保持期限が来たから成功扱いにはしません。
Visibility Timeoutを伸ばすだけでは二重処理を防げない
SQSでは受信後のVisibility Timeout中、メッセージが一時的に見えなくなります。処理が長引いて期限を越えると再配信されますが、先のワーカーが停止した保証はありません。必要に応じて可視性の期限を延長しつつ、重複配信を前提に副作用を守ります。
- 同じDB内の更新なら、テナントと業務操作IDの一意制約、状態遷移、処理済み記録を同じトランザクションに置く。失敗時に一緒に戻る境界を作る。
- 外部決済などは、相手の冪等キーと保存期間を確認する。結果不明のタイムアウトでは同じ操作IDで照会・照合し、別IDで再実行しない。
- DLQへ移して戻しても業務操作IDを保持する。SQSの再投入では新しいmessageIDが付くため、配送IDだけの重複排除に頼らない。
- 処理済み記録の保持期間を、通常再試行だけでなく復旧時の再投入や外部APIの照会可能期間と合わせて決める。
ワーカー追加の前に、下流の共有上限を確認する
仮に外部APIへ安全に送れる枠を全体で毎秒1,000回、通常処理を700回、再試行を100回と見積もると、再投入へ使えるのは最大でも毎秒200回です。これは1ジョブにつきAPI呼び出し1回、他の利用者なしという計算上の上限で、運用ではさらに余力を残します。1ジョブが2回呼ぶなら、同じ200回の枠でも最大100ジョブ/秒です。
台数だけ増やすと、DB接続数や外部APIへの送信も増えます。プロセスごとの毎秒200回制限を10台に設定しても、全体を毎秒200回には抑えられません。共有の送信制御や配分した予算で全体を制限し、待機中の処理にも上限と期限を設けます。飽和した下流への再試行は回数・時間予算と間隔を制御します。
DLQを安全に再投入する手順
- 業務ID、元の受付時刻、試行数、エラー分類を調べる。認証設定、壊れた入力、一時的な過負荷など、直す対象を特定する。
- 修正を適用し、重複と結果不明の外部処理を扱えることを確認する。不正データは修正・隔離の経路を別途用意する。
- 通常処理と再試行を含む余力を見積もり、小さな再投入速度で始める。SQSのredrive機能は移動中のフィルタ・書き換えに対応しないため、必要な修正は別工程で行う。
- 再投入件数に対する業務成功、DB待ち、外部API制限、通常処理の遅延を確認して速度を上げる。悪化したら再投入を停止し、既に移動した分も追跡する。
- DLQ件数の減少だけで閉じず、対象業務の完了・補償・要調査を照合し、残った担当と期限を記録する。
復旧試験では、通常処理を流したまま再投入する
試験では下流を一時的に遅くし、滞留を作ってから復旧させます。通常流入を止めた試験だけでは、再投入が本来の処理枠を奪う問題を見逃します。古いスキーマのメッセージ、同じ業務の重複、処理直後のワーカー停止、外部APIの結果不明も混ぜ、二重副作用と未完了の取り残しを調べます。
合格条件は「キューがゼロ」だけにせず、通常業務の完了期限、滞留の解消時間、下流の待ち時間、重複副作用ゼロ、要調査の可視化をセットにします。実測の完了能力を使って予測を更新できれば、次の障害で台数・流入制限・再投入速度の判断ができます。
- Amazon SQS — CloudWatch metrics and oldest-message age
- Amazon SQS — Visibility timeout and repeated delivery
- Amazon SQS — DLQ redrive, velocity, and new message IDs
- Google SRE — Handling overload