要点: Queueが吸収できるのは短いBurstです。Admission Controlがなければ、過負荷が遅延へ変わるだけです。
課題
Queue LengthだけのScaleはDBや外部APIを過負荷にします。混在Workload、Retry増幅、一TenantのBulk処理が全体を停止させます。
単純な対策だけでは不十分な理由
10件のVideo Encodeと10件のEmailは同じBacklogではありません。Oldest AgeがQueueをService目標へ結びつけます。
Worker ScaleはBottleneckを下流へ移します。全DependencyにConcurrency Budgetが必要です。
処理フロー
AdmissionとPriority→Broker
分離Queue→Worker
制限Concurrency→Dependency
保護Budget
アーキテクチャ上の判断
Failure Domainを分離
Interactive、Bulk、外部連携を別QueueまたはPoolに分けます。
Tenant公平性を強制
Tenant別In-flight QuotaとWeighted Schedulingを使います。
Retryを有限化
一時・恒久Errorを分類し、回数、総Deadline、DLQ Policyを設定します。
さらに深く考える
Backpressureは受付から
予測遅延が目標を超えたら低Priority処理を拒否・延期・劣化します。
Recoveryを段階化
Outage後はDependency Limitを守りRampでBacklogを解消します。
実装手順
- Latency目標、Cost、TenantでJobを分類します。
- Idempotency Key、Retry Budget、最終Failure Policyを持たせます。
- Queue AgeでScaleし、Downstream BudgetでConcurrencyを制限します。
技術例: Leaseを使うIdempotent Job
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))
ACKVisibility Timeoutは通常実行より長くし、長時間JobはHeartbeatで延長します。
想定すべき障害パターン
- 処理中にVisibilityが切れ、重複実行されます。
- Dependencyの部分復旧でRetry Stormが起きます。
- DLQが無監視で増え、恒久的な欠落になります。
監視すべきシグナル
| シグナル | 確認する理由 |
|---|---|
| Class別Oldest Age | Latency目標違反を直接示します。 |
| Attempt数とFailure理由 | Retryの有効性を示します。 |
| Tenant別In-flight | Noisy Neighbourを可視化します。 |
設計の検証方法
- DBをThrottleしBudget内に収まるか確認します。
- 一TenantのBulk中もInteractive目標を維持します。
- Job中にLeaseを切り、結果が一つか確認します。
安全なRollout計画
Queue AgeとRetry Reasonを先に計測し、Riskの高いWorkloadを分離します。LimitをObserve-onlyから少数Tenantへ適用し、DLQ Replayを訓練します。
本番前チェックリスト
- Job Costが違う場合、Message数よりQueue Ageが重要です。
- RetryはJitter付きBackoffと有限Deadlineを使います。
- DLQにはOwner、Alert、Replay Toolが必要です。
まとめ
Production Queueは無限BufferではなくCapacity Control Systemです。
