バックグラウンドジョブが『本番事故』の温床になる
SaaSアプリの本番事故の3割以上はバックグラウンドジョブに起因すると言われます。同じジョブが2回実行されて二重課金、リトライループで雪だるま、DLQに溜まった失敗ジョブが見過ごされて顧客対応漏れ等、典型的な事故パターンが存在します。本記事では本番運用で必須となる設計パターンを、BullMQ/SQS/Inngest等の実装例とともに整理します。
必須設計パターン5つ
- 1. 冪等性(Idempotency): 同じジョブを2回実行しても結果が変わらない設計
- 2. リトライポリシー: 指数バックオフ・最大試行回数の明示
- 3. デッドレターキュー(DLQ): 最終失敗ジョブの隔離と監視
- 4. タイムアウト: ジョブの最大実行時間を明示
- 5. 並行制御: 同種ジョブの同時実行を制限
パターン1: 冪等性設計
Idempotency Key: ジョブ作成時に一意なキー(例: order:123:send-email)を発行
受信側チェック: 既存処理済みのキーを記録、重複は無視
外部API連携: Stripe等のIdempotency-Key Headerを必ず利用
DB書き込み: ON CONFLICT DO NOTHINGまたは事前SELECTでチェック
ファイル操作: 既存ファイル存在チェックで二重生成を防ぐ
パターン2: リトライポリシー
- 指数バックオフ: 1秒・2秒・4秒・8秒・16秒…と間隔を倍々に
- ジッター追加: ランダム要素で集中リトライを防ぐ
- 最大試行回数: 5〜10回が標準・以降はDLQ
- エラー種別: 4xxエラーはリトライしない・5xx/Network エラーはリトライ
- Permanent Failure: 不可逆失敗(バリデーションエラー等)はリトライ不要
パターン3: デッドレターキュー(DLQ)
(1) 最大リトライ回数を超えた失敗ジョブをDLQに移動
(2) DLQ件数を監視・閾値超過でSlack/PagerDuty通知
(3) 人手での原因分析・再投入の運用フロー
(4) DLQの永続保管期間を明示(30日等)
(5) DLQから本キューへの再投入APIを用意
パターン4: タイムアウト
- ジョブ単体の最大実行時間: 5〜15分が標準
- 外部API呼び出し: 30秒〜2分のタイムアウト
- 長時間処理は分割: 大きな処理を小さなジョブに分けて Temporal/Step実装
- タイムアウト時の冪等性: タイムアウト後にリトライしても安全な設計
パターン5: 並行制御
- 同一エンティティの並列実行禁止(例: ユーザー1人につき1ジョブ)
- 外部API rate limit保護: 1秒5リクエスト等の制限
- リソース集約処理: CPU/メモリ消費の大きい処理は並列度2〜4
- BullMQ: rate limiter機能
- SQS: 並列度はLambda Concurrency制限で
監視・アラート設計
(1) ジョブ実行率: 想定実行回数/時間の閾値
(2) エラー率: 1%超で警告・5%超でクリティカル
(3) 処理時間: p99で異常値
(4) DLQ件数: 件数閾値で通知
(5) キュー長: 滞留時間が異常値
各ジョブツール別の実装目安
- BullMQ: redis bullmqライブラリで上記5パターンを全て自前実装
- AWS SQS+Lambda: Visibility Timeout・DLQ・並列度Lambda設定
- Inngest: Step.run・Retry設定・DLQが標準サポート
- Trigger.dev: タスク定義に Idempotency Key・Concurrency制限
- Temporal.io: Workflow・Activity・補償処理が宣言的
30日実装プラン
- 1週目: 最小ジョブ実装・冪等性キー設計
- 2週目: リトライポリシー・DLQ設定
- 3週目: 並行制御・タイムアウト・監視設定
- 4週目: 本番デプロイ・障害シミュレーション
関連リンク
Temporal.ioは Temporal.io深掘り、Inngestは Inngest深掘り、Trigger.devは Trigger.dev深掘り を参照してください。マイクロサービスは マイクロサービス実践 もどうぞ。