Webアプリケーションの処理時間が伸びる原因は、たいていリクエストの中で外部APIを叩いたり、PDFを生成したり、大量のメールを送ったりしていることにあります。Celeryは、こうした処理をリクエストの外へ追い出し、別プロセスのワーカーに実行させるためのPython製ライブラリです。同期処理と非同期処理の方式そのものの違いは別記事で整理しているため、ここではCeleryという具体的な道具を、構成要素・ブローカー選定・再試行と冪等性・本番運用という順で、実装判断ができる粒度まで落として説明します。
まとめ:Celeryは非同期処理の実行基盤を最短で用意する道具
先に結論を置きます。Celeryは「タスクを投げる側(アプリ)」「タスクを預かる側(ブローカー)」「タスクを実行する側(ワーカー)」の三者を、Pythonのデコレータ1行で結ぶライブラリです。導入の判断で迷いやすい点は次の4つに集約されます。
- ブローカーの選定:Redisは導入が軽く低遅延、RabbitMQは配送制御が細かい、Amazon SQSは運用をAWSへ委ねられる。この3択がCeleryの設計の半分を決めます。
- 実行保証の性格:既定では取り出した時点でタスクが消える(at-most-once寄り)。落ちても再実行してほしいなら
acks_lateを有効にして、at-least-onceに寄せます。 - 冪等性の担保:再実行を許した瞬間、二重実行が起こり得ます。課金・在庫・メール送信を扱うタスクは、キーで重複を弾く実装が前提になります。
- 運用コスト:ワーカー、Beat、ブローカー、監視の4つが常駐します。タスクが日に数件しかない案件では、この常駐コストが見合いません。
2026年9月時点の安定版は5.6.3(2026年3月26日リリース)で、5.6系はPython 3.9以上が最小要件です。Python 3.14への初期対応も入っています。この4点を押さえたうえで、以下で個別の判断材料を見ていきます。
Celery(Python)とは?構成要素と1件のタスクが処理される流れ
Celeryを構成する4つの役割とタスク処理が進んでいく順序を理解する
Celeryは単体で動くソフトウェアではなく、4つの役割の組み合わせで成立します。
| 役割 | 担うもの | 実体の例 |
|---|---|---|
| アプリ(プロデューサ) | タスクの投入 | Django・FastAPI |
| ブローカー | タスクの一時保管 | Redis・RabbitMQ |
| ワーカー(コンシューマ) | タスクの実行 | celery workerプロセス |
| 結果バックエンド | 戻り値と状態の保存 | Redis・DB・省略可 |
処理の進行順は次のとおりです。アプリがdelay()を呼ぶと、関数名と引数がシリアライズされてブローカーへ積まれます。ワーカーはブローカーを購読していて、届いたメッセージを取り出し、対応する関数を自分のプロセスで実行する仕組みです。結果バックエンドを設定していれば、戻り値と成功・失敗の状態がそこに書き込まれ、呼び出し側は後から参照できます。設定していなければ、実行しっぱなしの投げ放ちになります。
ここで押さえておきたいのは、ブローカーと結果バックエンドが別物だという点です。同じRedisを使うにしても、キューとしてのRedisと、結果置き場としてのRedisは役割が違います。データベース番号を分けておくと、片方をフラッシュしたときにもう片方を巻き込む事故を避けられます。
最小構成のコードで確認するタスク定義と非同期呼び出しの基本手順
Celeryアプリの定義、タスクの宣言、呼び出しの3点だけなら、コードはこの程度に収まります。
from celery import Celery
app = Celery(
"billing",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1",
)
@app.task
def send_invoice(order_id):
# 請求書PDFの生成とメール送信
return order_id
呼び出し側はsend_invoice.delay(1234)と書くだけで、関数はその場では実行されずキューへ積まれます。戻り値としてAsyncResultオブジェクトが返るので、タスクIDを保存しておけば後から状態を照会できます。ワーカーの起動はコマンド1行です。
celery -A billing worker --loglevel=info --concurrency=4
-Aで読み込むモジュールを、--concurrencyで同時実行数を指定します。この状態でアプリ側からdelay()を呼べば、ワーカーのログにタスクの受信と完了が流れます。ここまでが動作確認の最小単位です。
2026年9月時点のバージョンとPythonの動作要件を導入前に確認する
PyPIとstable系ドキュメントを実測したところ、安定版は5.6.3で、リリースは2026年3月26日でした。5.6系ではPython 3.8のサポートが終了し、最小要件がPython 3.9へ引き上げられています。あわせてPython 3.14の初期対応が入りました。5.6系ではETAタスクによるメモリ枯渇を防ぐworker_eta_task_limitの追加や、遅延配送時にブローカーの認証情報がログへ出ていた問題の修正なども入っています。5.5系から上げる際は、この最小Pythonバージョンの引き上げが最初の関門になります。
なお、ドキュメントのIntroductionには「RabbitMQとRedisのトランスポートはfeature complete」という記述があり、それ以外のトランスポートは実験的な扱いです。バージョン番号や対応表は改訂されるため、採用の直前に公式ドキュメントとPyPIで再確認してください。
ブローカーの選び方:Redis・RabbitMQ・Amazon SQSの判断基準
Redisを選ぶ場面は小規模構成と低遅延を優先する案件の判断基準
Redisは導入が最も軽い選択肢です。すでにキャッシュ用途でRedisが立っているなら、データベース番号を分けるだけでブローカーとして使い始められます。投入から実行までの遅延も小さく、結果バックエンドも同じRedisで賄えます。
弱点はメッセージの耐久性です。Redisはメモリ上のデータストアであり、永続化設定に依存するため、プロセスの異常終了でキューの内容を失う可能性がある点に注意が必要です。あわせて、メモリが上限に達したときにキーが追い出される設定になっていると、未処理タスクが黙って消えます。ブローカーとして使うRedisでは、追い出しポリシーを無効側に倒しておく設定が要ります。
RabbitMQを選ぶ場面は配送保証と細かい制御が要る案件の判断基準
RabbitMQはメッセージブローカーとして設計された製品で、確認応答(ack)、永続化キュー、優先度、ルーティングといった機能が標準で揃っています。Celeryのドキュメントが第一に挙げるトランスポートでもあり、機能面の欠落を踏む確率が最も低い選択です。
代償は運用の重さです。RabbitMQ自体のクラスタ構成、ディスク使用量の監視、バージョンアップの計画が必要になり、Redisと比べて面倒を見る対象が増えます。RabbitMQとKafkaの設計思想の違いを整理した記事も用意しているので、ストリーム処理との比較まで含めて検討したい場合はそちらを参照してください。Celeryの文脈では、Kafkaは正式なトランスポートではないため候補から外れます。
Amazon SQSを選ぶ場面は運用をAWSへ寄せる案件の判断基準
Amazon SQSをブローカーにすると、キューそのものの可用性と運用をAWSへ委ねられます。ワーカーだけをECSやEC2で持てばよく、ブローカーの死活監視から解放されます。5.6系ではSQSトランスポートの内部実装がpycurl側へ戻され、5.5系で入った変更が巻き戻されました。
制約は次のとおりです。SQSは結果バックエンドとしては使えないため、結果を保存したい場合はRedisやデータベースを別に用意する必要があります。可視性タイムアウトの設定がCeleryの再配送タイミングを直接左右するので、タスクの実行時間より短く設定すると同じタスクが何度も走る原因です。可視性タイムアウトとデッドレターキューの設計はSQS単体でも判断材料が多いため、別記事で個別に整理しています。
| 観点 | Redis | RabbitMQ | Amazon SQS |
|---|---|---|---|
| 導入の手間 | 小さい | 中程度 | 小さい |
| メッセージ耐久性 | 設定依存 | 高い | 高い |
| 結果バックエンド兼用 | できる | 非推奨 | できない |
| 運用の担当 | 自社 | 自社 | AWS |
| 向く規模 | 小〜中規模 | 中〜大規模 | AWS前提の構成 |
再試行と冪等性の設計:一度きりの実行を諦めて障害時の再配送まで組む
acks_lateと可視性タイムアウトが決める再配送の境界条件と実行時間
Celeryの既定挙動では、ワーカーはタスクを取り出した直後にブローカーへ確認応答を返す仕組みです。この時点でメッセージはキューから消えるため、実行中にワーカーのプロセスが落ちるとタスクは失われます。落ちても拾い直してほしい場合は、タスク定義にacks_late=Trueを指定して、確認応答を処理完了後まで遅らせます。
ただしこの設定を入れると、実行保証の性格がat-least-onceへ変わります。ワーカーが処理の途中で落ちた場合、ブローカー側は未確認のメッセージを別のワーカーへ再配送するため、同じタスクが2回走る状況が生まれます。SQSを使っている場合は、この再配送の待ち時間が可視性タイムアウトそのものです。処理に5分かかるタスクに対して可視性タイムアウトを1分にしていれば、1分おきに同じタスクが起動する事態になります。
指数バックオフとジッターで再試行の波を安全に散らす具体的な設定方法
外部APIの一時的な失敗は、間隔を空けて再試行すれば通ることが多いものです。Celeryはタスクデコレータの引数だけで、この挙動を宣言できます。
@app.task(
bind=True,
acks_late=True,
autoretry_for=(TimeoutError,),
retry_backoff=True,
retry_backoff_max=600,
retry_jitter=True,
max_retries=5,
)
def call_partner_api(self, order_id):
...
autoretry_forで再試行する例外の型を限定し、retry_backoffで間隔を指数的に伸ばします。retry_jitterを有効にすると再試行の時刻がランダムにずれるため、大量のタスクが同時に失敗したときの再送が一点に集中しません。retry_backoff_maxは間隔の上限で、これを置かないと再試行の間隔が際限なく伸びます。指数バックオフとリトライ予算の考え方そのものは言語やライブラリを問わず共通なので、アルゴリズムの詳細は別記事で扱っています。
再試行する例外の型を絞る点は見落とされがちです。autoretry_for=(Exception,)のような広い指定にすると、バリデーションエラーのような何度やっても通らない失敗まで5回繰り返してしまい、障害時のブローカー負荷を押し上げます。
冪等キーを持たせて二重実行を無害にする実装方法と運用上の判断基準
at-least-onceを受け入れた以上、二重実行は設計で吸収します。実務でよく使うのは、タスクの単位ごとに一意なキーを決めて、実処理の前にそのキーを取れた場合だけ処理を進める形です。
@app.task(bind=True, acks_late=True)
def charge(self, order_id):
key = f"charge:{order_id}"
if not redis.set(key, "1", nx=True, ex=86400):
return "skipped"
do_charge(order_id)
return "done"
nx=Trueは既存キーがあるときに書き込みを失敗させる指定です。2回目以降の実行はここで弾かれ、実処理へは進みません。有効期限を付けているのは、キーが永久に残ってキャッシュを圧迫するのを避けるためです。
この方式には抜けがあり、キーを取った直後にプロセスが落ちると、実処理が走らないまま重複扱いになる点に注意が必要です。金額が動く処理では、キーだけで判断せず、処理結果を永続化して状態遷移で判定する設計へ寄せます。冪等性という性質そのものの整理と、冪等キーの発行・保存・再生の実装手順は別記事に分けているので、決済系を扱うならそちらを先に読んでおくと判断が速くなります。
定期実行のCelery Beatとタスク合成(chain・group・chord)
Celery Beatで定期実行を組むときに多重起動を防ぐ設計方法
Celery Beatは、スケジュールに従ってタスクをキューへ投入する常駐プロセスです。cronの代わりに使えて、スケジュール定義をアプリのコードと同じ場所に置けます。
from celery.schedules import crontab
app.conf.beat_schedule = {
"nightly-order-sync": {
"task": "billing.sync_orders",
"schedule": crontab(hour=3, minute=0),
}
}
運用で最も踏みやすい失敗は、Beatを複数台で起動してしまうことです。BeatはCeleryの中で唯一、スケジュールの発火判断を単独で行うプロセスであり、2台動いていれば同じ時刻に2件のタスクが積まれます。ワーカーは水平にスケールさせてよいのに対し、Beatは常に1つに絞る、という非対称なルールを運用手順書へ明記しておいてください。コンテナで運用するなら、ワーカーとBeatを別のサービス定義に分け、Beat側のレプリカ数を1で固定します。
chain・group・chordでタスクを組み合わせる書き方
複数のタスクを連結したり並列化したりする合成の仕組みも用意されています。chainは前のタスクの戻り値を次のタスクの第一引数へ渡して直列に実行し、groupは複数タスクを並列に投入します。chordはgroupの全完了を待って集約タスクを走らせる形です。
from celery import chain, group, chord
chain(fetch.s(order_id), transform.s(), store.s()).apply_async()
group(notify.s(uid) for uid in user_ids).apply_async()
合成を深くしすぎると、途中で失敗したときの再開点が分かりにくくなります。3段を超える連結が必要になった時点で、Celeryの合成機能で組み続けるのか、ワークフローエンジンへ寄せるのかを判断してください。特にchordは、集約側が全件の完了状態を追うため結果バックエンドへの依存が強く、結果バックエンドを省略した構成では使えません。
本番運用の設計:並行モデルの選択と監視・安全な停止手順を決める
preforkとgevent・threadsの選び分けと同時実行数
Celeryのワーカーには複数の並行モデルがあり、既定はprefork(マルチプロセス)です。CPUを使う処理、たとえば画像変換や集計はpreforkが向きます。同時実行数はCPUコア数と同程度から始めて、実測で調整します。
一方、処理時間の大半が外部APIやデータベースの応答待ちで占められるタスクでは、preforkはメモリを無駄に消費する方式です。この場合はgeventやeventletといったコルーチンベースのプールへ切り替えると、同じメモリで数百の同時実行を捌けます。ただしgeventは標準ライブラリへパッチを当てて動くため、C拡張に依存するライブラリと組み合わせると想定外の挙動が出ることがあります。切り替える前に、そのタスクが使うライブラリで動作確認を通してください。threadsプールは、パッチを当てずにI/O待ちを並行化したい場合の中間的な選択肢になります。
監視すべき指標とデプロイ時に必要な安全な停止手順の具体的な決め方
ワーカーの生死だけを見ていると、キューが詰まっている状態を見逃します。監視の中心はキューの滞留数、つまりブローカーに積まれたまま処理されていないメッセージ数に置きます。これが単調増加していれば、ワーカー数か処理速度が需要に追いついていません。あわせて、タスクごとの実行時間の分布、失敗率、再試行回数を取っておくと、どのタスクが劣化しているかを切り分けられます。
デプロイ時は、実行中のタスクを途中で切らない停止手順が必要です。ワーカーへTERMシグナルを送ると、新しいタスクの受け取りを止めたうえで、実行中のタスクの完了を待ってから終了する挙動になります。オーケストレータ側の猶予時間を、最も長いタスクの実行時間より長く設定しておかないと、待っている途中でKILLされて中断します。Djangoアプリの本番配置とプロセス構成を扱った記事も用意しているので、Web側とワーカー側をどう分けて動かすかはそちらとあわせて設計してください。
受託開発でCeleryを採用する条件と、見送るべき案件の見分け方
Celeryを採用してよい案件の条件を先に決めておくための判断基準
ここまでの内容を、案件を受けるときの判断へ落とし込みます。私たちがCeleryを提案するのは、次の条件が揃っているときです。
- 非同期タスクが定常的に走る:日次バッチ1本ではなく、時間あたり数十件以上のタスクが継続して流れる規模。常駐プロセスのコストが処理量で回収できる水準にあること。
- Pythonで実装が完結する:既存のDjangoやFastAPIのモデルとビジネスロジックを、ワーカー側からそのまま呼べる。言語をまたぐと利点の大半が消えます。
- ブローカーの運用先が決まっている:Redisを自前で見るのか、SQSへ委ねるのか。ここが未定のまま実装へ入ると、後から再試行設計をやり直すことになります。
- 再実行を許せる処理である:二重実行が起きても業務が壊れない、あるいは冪等キーで吸収できる設計に落とせること。
この4つのうち2つ以上が満たせないなら、Celeryを入れる前に構成を見直したほうが結果的に安く済みます。
Celeryを見送るべき場面とそのときに置き換える選択肢の判断基準
逆に、見送りを勧める場面もはっきりしています。まず、タスクが日に数件しかない案件です。ワーカー・Beat・ブローカー・監視の4点を常駐させるコストに対して、得られるものが釣り合いません。この規模なら、cronからPythonスクリプトを叩く構成か、クラウドのマネージドなスケジューラで足ります。
次に、処理の順序と分岐が業務要件として複雑な案件です。承認待ちで数日止まる、条件によって経路が分かれる、途中から再開したい、といった要求はワークフローエンジンの領分で、Celeryの合成機能で組むと再開点の管理が手作業になります。イベント駆動アーキテクチャの実装パターンまで視野を広げて、そもそもキューで解くべき問題かを見直す価値があります。
実装言語がPythonでない場合も対象外です。Node.jsならBullMQが同じ役割を担い、Redis前提の設計や再試行の考え方はCeleryとよく似ています。判断の枠組みは言語をまたいで使い回せるので、本記事の4条件はそのまま当てはめて構いません。
私たちは、外部システムとのAPI連携を非同期化し、再試行と冪等性を含めて設計・実装するところまで請け負っています。既存システムへ後からキューを差し込む場合の切り分けや、ブローカーの選定から運用手順の整備までを含めた相談は、API開発・システム連携のページからお問い合わせください。
よくある質問
CeleryとCelery Beatは何が違いますか?
役割が別です。Celeryワーカーはキューからタスクを取り出して実行するプロセスで、Celery Beatはスケジュールに従ってタスクをキューへ投入するプロセスです。Beatは投入するだけで実行はしないため、Beatだけを動かしてもタスクは処理されません。両方を常駐させる必要があり、ワーカーは複数台に増やせる一方、Beatは重複投入を避けるため1つに絞ります。
ブローカーはRedisとRabbitMQのどちらを選ぶべきですか?
すでにRedisが構成にあり、タスクが数件失われても業務が壊れない性質ならRedisで始めて問題ありません。決済や在庫のように取りこぼしが許されない処理を含むなら、永続化キューと確認応答が標準で揃うRabbitMQを選びます。AWS上で運用していてブローカーの面倒を見たくないなら、Amazon SQSが3つ目の選択肢になります。判断できないまま実装へ進むより、初期に決め切るほうが安全です。
タスクが実行されないときは何から確認しますか?
最初に確認する対象は、ワーカーが起動しているか、そしてタスクを投入している側と同じブローカーURLを見ているかの2点です。次の確認対象はキュー名の一致です。ルーティング設定を入れている場合、投入先のキューをワーカーが購読していないケースがよくあります。ここまでで解決しなければ、ワーカーのログレベルをinfoまで下げて受信ログが出るかを見て、出ていないならブローカー側にメッセージが積まれているかを直接確認します。
結果バックエンドは必ず設定する必要がありますか?
不要な場合もあります。メール送信のように投げ放ちで完結する処理だけなら、結果バックエンドを省略してストレージの消費を避けられます。ただしchordを使う場合や、呼び出し側でタスクの完了を待ちたい場合、管理画面で実行結果を見せたい場合には必要です。設定するなら保持期間を短めに切って、結果データが際限なく溜まらないようにしてください。
Celeryの最新バージョンとPythonの対応状況は?
2026年9月時点の安定版は5.6.3で、リリース日は2026年3月26日です。5.6系はPython 3.9以上が最小要件で、Python 3.8のサポートは終了しました。Python 3.14への初期対応も入っています。バージョン番号と対応表は改訂されるため、採用前にPyPIのプロジェクトページと公式ドキュメントで、その時点の値を確認する運用にしてください。
関連記事
- BullMQとは?v6のPostgreSQL対応・リトライとワーカー運用を実装者目線で解説:Node.js側で同じ役割を担うライブラリ。Redis前提の設計や再試行の考え方をCeleryと比較できます。
- イベント駆動アーキテクチャとは?仕組み・実装パターンと採用判断を解説:キューで解くべき問題かどうかを、アーキテクチャの側から見直したい場合に。
- gRPC Pythonの実装|スタブ生成からasync・FastAPI併存まで解説:Pythonで外部システムと同期的につなぐ場合の実装。非同期化との使い分けの参考に。