💡

Pythonにおける非同期処理ライブラリの選定について(Celery vs Threading)

に公開

はじめに

Pythonで非同期処理を設計する際には、Celery(マルチプロセスを用いたタスクキュー)と標準のスレッド(threading)のどちらを使用するか検討する必要があります。
本記事では選定の基本軸となる「CPU負荷」と「スループット」をベースにどちらを使用するのが適切かをまとめてみました。

選定基準: CPU負荷とスループット

非同期処理方式を選ぶ際は、処理がCPU集約型か(CPU負荷)と必要な処理量(スループット)を基準に検討します。
CPUを多用する処理ではスレッドを使って並列化しても性能向上は限定的です。
Celeryであれば別プロセスのワーカーでタスクを実行でき、複数のCPUコアやマシンを活用できます。また、大量のタスク処理が必要な場合はワーカーを増やすことで水平スケールが可能です。

スレッド(threading)の活用: 軽量・短時間タスク向き

Python標準のthreadingモジュールを使ったスレッドは並行処理を行います。
I/O待ちが多いタスクや短時間で完了する軽量なタスクに適しています。
スレッドはメモリ空間を共有し、メッセージキューなどの外部サービスを必要としないため、実装が容易でオーバーヘッドも小さく済みます。

しかし、CPU集約的な処理ではスレッド間でGILを奪い合うためスループットは向上しません。
さらに、スレッドで長時間の処理を実行するとメインプロセスのリソースを占有し、アプリケーション全体の応答性に影響を与えかねません。
CPU負荷が高い処理や数秒以上かかるタスクはスレッドで抱え込まず、後述のCeleryに委ねるのが望ましいでしょう。

Celeryの活用: CPU集約・長時間タスク向き

Celeryは分散タスクキューシステムで、別プロセスのワーカー上でタスクを非同期実行します。
これにより、重い計算処理や長時間に及ぶタスクでもメインプロセスをブロックせずに実行できます。
ワーカーを複数起動すれば、必要に応じて処理を水平展開(スケールアウト)でき、複数のCPUコアやサーバーを活用可能です。

Celeryの導入にはメッセージブローカー(例:RedisやRabbitMQ)の設定などが必要で、実装が複雑になります。
また、ごく短いタスクを大量に投げると、キューへのメッセージ通信やシリアライズのオーバーヘッドが相対的に無視できなくなり、性能低下を招く場合があります。
したがって、Celeryは主に重い処理や多数のタスクを捌くケースで活用し、軽微な処理には過度に使用しないといった使い分けが重要です。

まとめ

  • スレッド (threading) は軽量・短時間の処理やI/O待ちの多いタスクに向いており、実装がシンプルでオーバーヘッドも少ないですが、CPU集約処理には不向きです。
  • Celery はCPU負荷の高い処理や長時間タスク、大量のタスク処理に適しており、ワーカー追加によるスケールアウトで高スループットを実現できますが、環境構築の手間と多少のオーバーヘッドを伴います。

これらの特性を踏まえ、システム要件に応じて適切な手法を選定してください。小規模な処理はスレッドで実装し、大規模または将来的に負荷増加が見込まれる処理にはCeleryを採用する、といった使い分けが有効です。

参考となりそうな資料

https://qiita.com/Tadataka_Takahashi/items/0cbc1f89da4635cec39c
https://docs.celeryq.dev/en/stable/userguide/concurrency/index.html
https://github.com/celery/celery

Discussion