要点: キューが吸収できるのは短い負荷の波だけです。無制限に受け入れれば、過負荷が長い待ち時間へ変わるだけです。
この記事の用語: DLQ:繰り返し失敗したジョブを隔離するキュー。冪等キー:重複処理を防ぐ識別子。
現場の課題
キューの長さだけを見てワーカーを増やすと、DBや外部サービスを過負荷にします。大量処理を行う一つのテナントが、共有能力を使い切る可能性もあります。
単純な対策だけでは不十分な理由
10通のメールと10件の動画エンコードでは負荷が違います。件数より種別別の最古待ち時間を見て、DBや外部サービスの実能力を超えない同時実行上限を置きます。
処理フロー
受付とPriority→ブローカー
分離キュー→ワーカー
制限同時実行数→依存サービス
保護Budget
アーキテクチャ上の判断
障害領域を分離
Interactive、Bulk、外部連携を別キューまたはプールに分けます。
テナント公平性を強制
テナント別In-flight QuotaとWeighted Schedulingを使います。
再試行を有限化
一時・恒久エラーを分類し、回数、総期限、DLQ ポリシーを設定します。
さらに深く考える
Backpressureは受付から
予測遅延が目標を超えたら低Priority処理を拒否・延期・劣化します。
復旧を段階化
Outage後は依存サービス 上限を守りRampで滞留ジョブを解消します。
実装手順
- ジョブを遅延目標、処理コスト、テナントで分類してからキューへ入れます。
- 各ジョブへ冪等キー、再試行上限、最終失敗時の方針を持たせます。
- 最古ジョブの待ち時間でスケールし、DBと外部サービスの安全上限を超えないようにします。
コード例: Leaseを使う冪等 ジョブ
job = broker.receive(visibilityTimeout = 60s)
if inbox.exists(job.id): ACK
with dependencyLimiter.acquire(job.kind):
result = handle(job.payload)
transaction(inbox.insert(job.id), save(result))
ACK処理権限の有効時間は通常実行より長くし、長時間ジョブはHeartbeatで延長します。
想定しておく障害
- 処理中にVisibilityが切れ、重複実行されます。
- 依存サービスの部分復旧で再試行Stormが起きます。
- DLQが無監視で増え、恒久的な欠落になります。
監視すべきこと
| 指標 | 何が分かるか |
|---|---|
| 種別別最古ジョブの待ち時間 | 遅延目標違反を直接示します。 |
| 試行数と失敗理由 | 再試行の有効性を示します。 |
| テナント別In-flight | 一部Tenantによる資源占有を可視化します。 |
設計の検証方法
- DBをThrottleしBudget内に収まるか確認します。
- 一つのテナントのBulk中もInteractive目標を維持します。
- ジョブ中にLeaseを切り、結果が一つか確認します。
本番導入の進め方
最古待ち時間と再試行理由を先に測ります。高リスクなジョブを別プールへ分離し、観測のみの上限から少数テナントへ適用し、DLQからの再実行も訓練します。
本番前チェックリスト
- ジョブ コストが違う場合、Message数より最古ジョブの待ち時間が重要です。
- 再試行はJitter付き待ち時間の段階的増加と有限期限を使います。
- DLQには担当者、アラート、再実行ツールが必要です。
まとめ
本番キューは無限バッファではなく、有限な処理能力を配分する仕組みです。
