CPUを集中的に使用するCeleryタスクがあります。多くのEC2インスタンスですべての処理能力(コア)を使用して、このジョブを高速に実行したいと思います(マルチプロセッシングによるセロリ並列分散タスク-と思います)。
スレッド化、マルチプロセッシング、分散コンピューティング、分散並列処理という用語は、すべて理解しようとしている用語ですより良い。
タスクの例:
@app.task
for item in list_of_millions_of_ids:
id = item # do some long complicated equation here very CPU heavy!!!!!!!
database.objects(newid=id).save()
上記のコードを使用する(可能な場合は例付き)これを許可することでCeleryを使用してこのタスクを分散する前の方法クラウド内の利用可能なすべてのマシンですべてのコンピューティングCPUパワーを利用して分割するタスクですか?
あなたの目標は:
セロリはあなたのためにこれらの両方をかなり簡単に行うことができます。最初に理解することは、各セロリワーカーが デフォルトで構成 であり、システムで使用可能なCPUコアと同じ数のタスクを実行することです。
同時実行性とは、タスクを同時に処理するために使用されるpreforkワーカープロセスの数です。これらのすべてが仕事で忙しい場合、新しいタスクは、タスクの1つが完了するまで待つ必要があります。
デフォルトの同時実行数は、そのマシンのCPU数(コアを含む)です。-cオプションを使用してカスタム数を指定できます。最適な数はいくつかの要因に依存するため、推奨値はありませんが、タスクの大部分がI/Oバウンドである場合は、それを増やすことができますが、2倍以上のCPUを追加することはほとんどありません効果的で、代わりにパフォーマンスを低下させる可能性があります。
つまり、個々のタスクは、マルチプロセッシング/スレッドを使用して複数のCPU /コアを使用することを心配する必要はありません。代わりに、セロリは、使用可能な各CPUを使用するのに十分なタスクを同時に実行します。
邪魔にならないように、次のステップは_list_of_millions_of_ids
_のサブセットの処理を処理するタスクを作成することです。ここにはいくつかのオプションがあります-1つは各タスクに単一のIDを処理させることで、Nタスクを実行します(N == len(list_of_millions_of_ids)
)。これにより、作業がすべてのタスクに均等に分散されることが保証されます。なぜなら、1人のワーカーが早く終了し、ただ待機している場合は決してないからです。作業が必要な場合は、キューからIDを取得できます。セロリgroup
を使用してこれを行うことができます(John Doeによると)。
tasks.py:
_@app.task
def process_id(item):
id = item #long complicated equation here
database.objects(newid=id).save()
_
タスクを実行するには:
_from celery import group
from tasks import process_id
jobs = group(process_id.s(item) for item in list_of_millions_of_ids)
result = jobs.apply_async()
_
別のオプションは、リストをより小さな断片に分割し、その断片を労働者に配布することです。このアプローチでは、一部の作業者がまだ作業を行っている間に一部の作業者が待たされる可能性があるため、いくつかのサイクルを無駄にするリスクがあります。ただし、 celery documentation notes この懸念はしばしば根拠のないものです:
タスクをチャンクすると並列性が低下することを心配する人もいますが、これはビジーなクラスターにはほとんど当てはまらず、メッセージングのオーバーヘッドを回避しているため、パフォーマンスが大幅に向上する可能性があります。
そのため、メッセージングのオーバーヘッドが減少するため、リストをチャンク化し、各タスクにチャンクを分散する方がパフォーマンスが向上する場合があります。一度に1つのIDを実行するのではなく、各IDを計算してリストに保存し、完了したらリスト全体をDBに追加することで、おそらくデータベースの負荷を少し軽くすることもできます。チャンキングアプローチは次のようになります
tasks.py:
_@app.task
def process_ids(items):
for item in items:
id = item #long complicated equation here
database.objects(newid=id).save() # Still adding one id at a time, but you don't have to.
_
タスクを開始するには:
_from tasks import process_ids
jobs = process_ids.chunks(list_of_millions_of_ids, 30) # break the list into 30 chunks. Experiment with what number works best here.
jobs.apply_async()
_
チャンクサイズが最良の結果をもたらすものを少し試してみることができます。メッセージングのオーバーヘッドを削減しつつ、他のワーカーよりもはるかに速くワーカーがチャンクを終了し、何もすることなく待機するだけのサイズにならないように、サイズを小さく維持するスイートスポットを見つけます。
配布の世界では、何よりも覚えておくべきことが1つだけあります。
早すぎる最適化は、すべての悪の根源です。 D.クヌース
明らかなように聞こえますが、ダブルチェックを配布する前に、最適なアルゴリズムを使用しています(存在する場合)。そうは言っても、配信の最適化は3つのことの間のバランスをとる行為です。
コンピューターは、処理ユニットに近づくほど(3)より速く、より効率的に(1)および(2)になるように作られています。クラシッククラスターの順序は次のとおりです。ネットワークハードドライブ、ローカルハードドライブ、RAM、内部処理ユニット領域...今日、プロセッサは、一般にコアと呼ばれる独立したハードウェア処理ユニットのアンサンブルと見なされるほど洗練されています。スレッド(2)を介したデータ(3)。コアが非常に高速であるため、1つのスレッドでデータを送信するときにコンピューターの電力の50%を使用し、コアに2つのスレッドがある場合は100%を使用することを想像してください。コアあたり2つのスレッドはハイパースレッディングと呼ばれ、OSはハイパースレッドコアあたり2つのCPUを認識します。
プロセッサでスレッドを管理することは、一般にマルチスレッドと呼ばれます。 OSからCPUを管理することは、一般にマルチプロセッシングと呼ばれます。クラスタ内の並行タスクの管理は、一般に並列プログラミングと呼ばれます。クラスター内の依存タスクの管理は、一般に分散プログラミングと呼ばれます。
だからあなたのボトルネックはどこですか?
セロリはどうですか?
Celeryは分散プログラミング用のメッセージングフレームワークであり、通信用のブローカーモジュール(2)と永続性用のバックエンドモジュール(1)を使用します。つまり、設定を変更することで(可能な場合)ボトルネックを回避できます。ネットワーク上でのみ。最初にコードをプロファイリングして、1台のコンピューターで最高のパフォーマンスを実現します。次に、デフォルトの構成でクラスターでセロリを使用し、CELERY_RESULT_PERSISTENT=True
:
from celery import Celery
app = Celery('tasks',
broker='amqp://guest@localhost//',
backend='redis://localhost')
@app.task
def process_id(all_the_data_parameters_needed_to_process_in_this_computer):
#code that does stuff
return result
実行中にお気に入りの監視ツールを開き、rabbitMQにはデフォルトを、セロリには花を、CPUにはtopを使用します。結果はバックエンドに保存されます。ネットワークのボトルネックの例としては、タスクキューが大きくなりすぎて実行が遅れる場合があります。ボトルネックがない場合は、モジュールまたはセロリの構成を変更してください。
これにgroup
セロリタスクを使用しないのはなぜですか?
http://celery.readthedocs.org/en/latest/userguide/canvas.html#groups
基本的に、ids
をチャンク(または範囲)に分割し、group
内の一連のタスクに渡す必要があります。
特定のセロリのタスクの結果を集約するなど、より洗練された方法で、同様の目的でchord
タスクを正常に使用しました。
http://celery.readthedocs.org/en/latest/userguide/canvas.html#chords
増加する settings.CELERYD_CONCURRENCY
合理的で余裕のある数にすると、これらのセロリ労働者は、完了するまでグループまたはコードでタスクを実行し続けます。
注:kombu
のバグにより、過去に多数のタスクでワーカーを再利用するのに問題がありましたが、現在修正されているかどうかはわかりません。多分そうですが、そうでない場合は、CELERYD_MAX_TASKS_PER_CHILDを減らします。
私が実行した単純化および変更されたコードに基づく例:
@app.task
def do_matches():
match_data = ...
result = chord(single_batch_processor.s(m) for m in match_data)(summarize.s())
summarize
はすべてのsingle_batch_processor
タスク。すべてのタスクは任意のCeleryワーカーで実行され、kombu
はそれを調整します。
今、私はそれを得る:single_batch_processor
とsummarize
は、通常の関数ではなく、セロリのタスクである必要があります-そうでなければ、もちろん並列化されません(セロリのタスクでない場合、コードコンストラクターがそれを受け入れるかどうかもわかりません)。
セロリ労働者を追加すると、タスクの実行が確実にスピードアップします。ただし、別のボトルネックがある可能性があります:データベース。同時挿入/更新を処理できることを確認してください。
あなたの質問に関して:EC2インスタンスに別のプロセスをceleryd
として割り当てることにより、セロリ労働者を追加しています。必要なワーカー数に応じて、さらにインスタンスを追加できます。