web-dev-qa-db-ja.com

コンテキストスロットルを必要とするAirflow DAGの構築

  • DAGとして実行するジョブユニット(ワーカー)のグループがあります
  • Group1には10人のワーカーがあり、各ワーカーはDBから複数のテーブル抽出を行います。各ワーカーは単一のDBインスタンスにマップされ、各ワーカーは自身を完了として正常にマークする前に、合計で100個のテーブルを正常に処理する必要があることに注意してください。
  • Group1には、10人のワーカーすべてで一度に5つ以下のテーブルを使用する必要があるという制限があります。例えば:
    • Worker1は2つのテーブルを抽出しています
    • Worker2は2つのテーブルを抽出しています
    • Worker3は1つのテーブルを抽出しています
    • Worker4 ... Worker10は、Worker1 ... Worker3がスレッドを解放するまで待機する必要があります
    • Worker4 ... Worker10は、step1のスレッドが解放されるとすぐにテーブルを取得できます
    • 各ワーカーが100個のテーブルをすべて完了すると、待機せずにステップ2に進みます。ステップ2には同時実行制限はありません

スロットルに対応し、また持っている単一ノードGroup1を作成できるはずです

  • ワーカーの10個の独立したノード。いずれかのノードが失敗した場合に備えて、それらを再起動できます

これを次の図で説明してみました: enter image description here

  • ワーカーのいずれかが失敗した場合、他のワーカーに影響を与えることなく再起動できます。それでもGroup1の同じスレッドプールを使用するため、同時実行制限が適用されます
  • グループ1は、ステップ1とステップ2のすべての要素が完了すると完了します。
  • ステップ2には同時実行性の対策はありません

Spring Boot JavaアプリケーションのAirflowにこのような階層を実装するにはどうすればよいですか?Airflow構造を使用してこの種のDAGを設計し、動的にJavaたとえば、Worker1以外のすべてのワーカーが終了した場合、Worker1は5つすべてのスレッドを使用できるようになり、他のすべてはステップ2に進みます。

6
Rafay

これらの制約は、有向非循環グラフとしてモデル化できないため、記載されているとおりに気流に実装することはできません。ただし、それらはキューとしてモデル化できるため、ジョブキューフレームワークで実装できます。 2つのオプションがあります。

エアフローDAGとして準最適に実装します。

from airflow.models import DAG
from airflow.operators.subdag_operator import SubDagOperator
# Executors that inherit from BaseExecutor take a parallelism parameter
from wherever import SomeExecutor, SomeOperator

# Table load jobs are done with parallelism 5
load_tables = SubDagOperator(subdag=DAG("load_tables"), executor=SomeExecutor(parallelism=5))

# Each table load must be it's own job, or must be split into sets of tables of predetermined size, such that num_tables_per_job * parallelism = 5
for table in tables:
    load_table = SomeOperator(task_id=f"load_table_{table}", dag=load_tables)

# Jobs done afterwards are done with higher parallelism
afterwards = SubDagOperator(
    subdag=DAG("afterwards"), executor=SomeExecutor(parallelism=high_parallelism)
)

for job in jobs:
    afterward_job = SomeOperator(task_id=f"job_{job}", dag=afterwards)

# After _all_ table load jobs are complete, start the jobs that should be done afterwards

load_tables > afterwards

ここでの次善の側面は、DAGの前半では、クラスターがhigher_parallelism - 5によって十分に活用されないことです。

ジョブキューを使用して最適に実装します。

# This is pseudocode, but could be easily adapted to a framework like Celery

# You need two queues
# The table load queue should be initialized with the job items
table_load_queue = Queue(initialize_with_tables)
# The queue for jobs to do afterwards starts empty
afterwards_queue = Queue()

def worker():

    # Work while there's at least one item in either queue
    while not table_load_queue.empty() or not afterwards_queue.empty():
        working_on_table_load = [worker.is_working_table_load for worker in scheduler.active()]

        # Work table loads if we haven't reached capacity, otherwise work the jobs afterwards
        if sum(working_on_table_load) < 5:
            is_working_table_load = True
            task = table_load_queue.dequeue()
        else
            is_working_table_load = False
            task = afterwards_queue.dequeue()

        if task:
            after = work(task)
            if is_working_table_load:

                # After working a table load, create the job to work afterwards
                afterwards_queue.enqueue(after)

# Use all the parallelism available
scheduler.start(worker, num_workers=high_parallelism)

このアプローチを使用すると、クラスターが十分に活用されません。

1
Dave