ブログ一覧

メッセージキューの滞留を解消するには?処理能力・再試行・DLQの設計

· 約10分で読めます

ワーカーを増やしてもキューが減らない、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:一定流量を仮定した解消時間の計算
# 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
同じ18万件の滞留を解消する時間の計算例。数値は秒で、流入と完了能力は一定。
  • 流入800件/秒・完了1,000件/秒: 900秒(15分)
  • 流入1,200件/秒・完了1,800件/秒: 300秒(5分)

毎秒の試行数と、毎秒の業務完了数を分ける

ワーカーが毎秒2,000回動いても、同じ失敗を繰り返していれば完了能力は増えません。初回受付数、全試行数、業務ID単位の初回成功数、隔離数を別々に記録します。元キューと処理中の両方を見て、受信しただけのメッセージを完了へ数えないことも必要です。

監視用のキュー離脱数にはDLQへの移動や期限切れが混ざるため、そのまま前節のμへ代入できません。業務台帳には未完了、成功、要調査などの状態を残します。期限を過ぎた通知を破棄できるか、注文を補償すべきかは業務ごとに決め、単に保持期限が来たから成功扱いにはしません。

Visibility Timeoutを伸ばすだけでは二重処理を防げない

SQSでは受信後のVisibility Timeout中、メッセージが一時的に見えなくなります。処理が長引いて期限を越えると再配信されますが、先のワーカーが停止した保証はありません。必要に応じて可視性の期限を延長しつつ、重複配信を前提に副作用を守ります。

ワーカー追加の前に、下流の共有上限を確認する

仮に外部APIへ安全に送れる枠を全体で毎秒1,000回、通常処理を700回、再試行を100回と見積もると、再投入へ使えるのは最大でも毎秒200回です。これは1ジョブにつきAPI呼び出し1回、他の利用者なしという計算上の上限で、運用ではさらに余力を残します。1ジョブが2回呼ぶなら、同じ200回の枠でも最大100ジョブ/秒です。

台数だけ増やすと、DB接続数や外部APIへの送信も増えます。プロセスごとの毎秒200回制限を10台に設定しても、全体を毎秒200回には抑えられません。共有の送信制御や配分した予算で全体を制限し、待機中の処理にも上限と期限を設けます。飽和した下流への再試行は回数・時間予算と間隔を制御します。

DLQを安全に再投入する手順

  1. 業務ID、元の受付時刻、試行数、エラー分類を調べる。認証設定、壊れた入力、一時的な過負荷など、直す対象を特定する。
  2. 修正を適用し、重複と結果不明の外部処理を扱えることを確認する。不正データは修正・隔離の経路を別途用意する。
  3. 通常処理と再試行を含む余力を見積もり、小さな再投入速度で始める。SQSのredrive機能は移動中のフィルタ・書き換えに対応しないため、必要な修正は別工程で行う。
  4. 再投入件数に対する業務成功、DB待ち、外部API制限、通常処理の遅延を確認して速度を上げる。悪化したら再投入を停止し、既に移動した分も追跡する。
  5. DLQ件数の減少だけで閉じず、対象業務の完了・補償・要調査を照合し、残った担当と期限を記録する。

復旧試験では、通常処理を流したまま再投入する

試験では下流を一時的に遅くし、滞留を作ってから復旧させます。通常流入を止めた試験だけでは、再投入が本来の処理枠を奪う問題を見逃します。古いスキーマのメッセージ、同じ業務の重複、処理直後のワーカー停止、外部APIの結果不明も混ぜ、二重副作用と未完了の取り残しを調べます。

合格条件は「キューがゼロ」だけにせず、通常業務の完了期限、滞留の解消時間、下流の待ち時間、重複副作用ゼロ、要調査の可視化をセットにします。実測の完了能力を使って予測を更新できれば、次の障害で台数・流入制限・再投入速度の判断ができます。

この記事で深掘りしたTips