FastAPI + Celery信頼性設計: 非同期ジョブを本番で壊さないための実装パターン
FastAPIでAPIを作ると、重い処理はすぐに非同期ジョブへ逃がしたくなります。画像変換、レポート生成、外部API連携、メール配信など、Celeryは非常に便利です。ですが、本番で問題になるのは「動くかどうか」ではなく、失敗したときに壊れないか です。
- 同じジョブが二重実行される
- 一時障害で永遠にリトライしてキューが詰まる
- ワーカー再起動で中途半端な状態が残る
- 完了通知が先に飛んで実データがない
本記事では FastAPI + Celery + Redis 構成を前提に、再実行安全性(idempotency)と運用信頼性を上げる実装手順をまとめます。
1. まず守るべき設計原則
非同期基盤の事故は、ほぼ次の4原則で防げます。
- At-least-once前提(同一タスク再実行は必ず起こる)
- 副作用は冪等化(何回実行されても結果が壊れない)
- 状態遷移を明示(PENDING/RUNNING/SUCCEEDED/FAILED)
- 失敗を可観測化(リトライ回数・死活・滞留時間を計測)
この原則を外すと、障害時に「何が完了して何が未完了か」が追えなくなります。
2. 参照アーキテクチャ
- API: FastAPI
- Queue Broker: Redis
- Worker: Celery
- Result Store: PostgreSQL(業務状態)
- Monitoring: Flower + Prometheus + Sentry
ポイントは、業務上重要な状態はRedis結果バックエンドに依存しない ことです。Redisは一時的に使い、真実の状態はRDBに持たせます。
3. 実装の土台: タスク受付API
3.1 受け付け時に idempotency_key を必須化
|
|
これでクライアント再送が来てもジョブ多重作成を防げます。
3.2 DB制約で最終防衛線を張る
idempotency_key に UNIQUE 制約を入れ、アプリバグ時も二重作成を防ぎます。
|
|
4. Celeryタスクの実践設定
4.1 推奨設定
|
|
acks_late=True: 実行完了後にACK。途中クラッシュ時は再配信prefetch_multiplier=1: 取り込み過多を防ぎ、偏りを減らす- time limit: ハング抑止
4.2 リトライは指数バックオフ + 上限
|
|
無制限リトライは障害増幅装置です。必ず上限を設定します。
5. 冪等タスクの実装パターン
5.1 状態遷移をトランザクションで管理
|
|
FOR UPDATE を使い、同時実行で状態が競合しないようにします。
5.2 副作用前に“実行済みチェック”
外部API呼び出しやファイル生成前に、既に成果物が存在するか確認します。
- 既に同名レポートが生成済みならスキップ
- 外部通知は送信履歴テーブルで重複防止
- 決済や課金は必ず業務ID単位で一意化
5.3 完了処理はCompare-and-Setで確定
|
|
これで二重完了更新を防げます。
6. 失敗時の設計(DLQ相当の運用)
Celeryに“標準DLQ”はありませんが、実運用では次の形で代替できます。
- リトライ上限超過時に
FAILED_PERMANENTへ遷移 - 失敗理由とスタックトレースをDB保存
- 再実行API(手動リカバリ)を提供
- 重大失敗はSentry + Pagerで通知
この構成で「黙って死ぬジョブ」をなくせます。
7. 監視設計(最低限)
メトリクス
- キュー滞留数(queue length)
- oldest message age
- タスク成功率 / 失敗率
- p95 実行時間
- リトライ回数分布
アラート例
- 滞留数が通常の3倍を10分継続
- 失敗率 > 5% が15分継続
- oldest message age > 20分
- worker heartbeat消失
「CPU高い」より「キューが古い」がユーザー影響に直結します。
8. デプロイ時の落とし穴
8.1 ローリング更新での重複実行
acks_late+ graceful shutdown を設定TERM後にタスク完了待ち時間を確保- 長時間ジョブは分割し、中断耐性を持たせる
8.2 スキーマ変更の順序
非同期基盤では、ワーカーとAPIが異なるバージョンで同居します。
安全な順序:
- 先に後方互換なDB変更を適用
- ワーカーを先に更新
- APIを更新
- 非互換削除は次リリースで
これを守らないと、古いタスクが新スキーマで失敗します。
9. ローカル・ステージングでの検証手順
- 正常系: ジョブ作成→完了→結果取得
- 再送系: 同一
idempotency_keyで2回POST - 障害系: 外部APIタイムアウトを強制しリトライ確認
- クラッシュ系: 実行中にworker再起動し再配信確認
- 負荷系: 1000ジョブ投入で滞留時間と失敗率確認
この5ケースを自動テストに入れるだけで、運用品質は大幅に上がります。
10. 本番チェックリスト
- idempotency_key のUNIQUE制約あり
- 冪等な状態遷移実装(RUNNING/SUCCEEDED)
- リトライ上限 + バックオフ設定済み
- 手動再実行導線あり
- 失敗通知(Sentry/Pager)有効
- 滞留監視とアラート運用あり
- デプロイ手順に互換性ルール明記
まとめ
FastAPI + Celeryの本質は、非同期化そのものではなく 失敗しても壊れない設計 にあります。
- At-least-once を前提に設計する
- 冪等性をDB制約と状態遷移で担保する
- リトライと監視を“運用可能”な形で実装する
- デプロイ時のバージョン混在を想定する
ここまで作り込むと、ジョブ基盤は「たまに落ちるブラックボックス」から「予測可能に運用できるインフラ」へ変わります。まずは idempotency_key と状態遷移の明確化から始めるのがおすすめです。