Je devrais être en mesure de créer un seul nœud Group1 qui répond à la limitation et a également
J'ai essayé d'expliquer cela dans le diagramme suivant:
Comment implémenter une telle hiérarchie dans Airflow pour un Spring Boot Java application? Est-il possible de concevoir ce type de DAG en utilisant les constructions Airflow et de dire dynamiquement Java application combien de tables il peut extraire à la fois. Par exemple, si tous les nœuds de calcul sauf Worker1 ont terminé, Worker1 peut désormais utiliser les 5 threads disponibles tandis que tout le reste passera à l'étape2.
Ces contraintes ne peuvent pas être modélisées comme un graphe acyclique dirigé, et donc ne peuvent pas être implémentées dans un flux d'air exactement comme décrit. Cependant, elles peuvent être modélisées sous forme de files d'attente et peuvent donc être implémentées avec une infrastructure de file d'attente de travaux. Voici vos deux options:
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
L'aspect sous-optimal ici, c'est que, pour la première moitié du DAG, le cluster sera sous-utilisé par 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)
En utilisant cette approche, le cluster ne sera pas sous-utilisé.