Tasks with a newer logical_date are never scheduled to run when max_active_tis_per_dag is used #69010
-
|
Due to issues with the external data provider, newer data may be available before older data. The order of the data is not a concern for me, so I allow the tasks to keep retrying throughout the day. I have limited the number of task instances. However, tasks from newer DAG runs are never executed because Airflow always prioritizes tasks from older DAG runs. I created a simple example to reproduce this. There are 6 active DAG runs. Airflow only executes tasks from the first 4 DAG runs and never executes tasks from the remaining 2 DAG runs. I tried using a pool with 2 slots and 6 DAGs, but the result is the same. Any thoughts on this? Do I need to implement a custom scheduler? Thanks. import pendulum
import time
from airflow import DAG
from airflow.providers.standard.operators.python import PythonOperator
from airflow.sdk.exceptions import AirflowException
def example(**context):
print("Before sleep")
time.sleep(30)
print("After sleep")
raise AirflowException("Testing")
with DAG(
dag_id="example_dag",
start_date=pendulum.datetime(2026, 5, 1, 0, 0, tz="UTC"),
schedule="* * * * *",
catchup=False,
max_active_runs=6,
is_paused_upon_creation=False,
tags=["example"],
) as dag:
PythonOperator(
task_id="example",
python_callable=example,
retries=300,
retry_delay=pendulum.duration(seconds=30),
max_active_tis_per_dag=2,
) |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
|
This looks like the scheduler behaving consistently with the current policy, not the pool behaving oddly. In the critical scheduling path, Airflow picks scheduled TIs ordered by priority weight descending, then The retry pattern in the repro makes the starvation much easier to hit. The first two runs execute, fail, and enter retry delay. While they are waiting, the next two can run. When the first two become eligible again, they are still older than runs 5 and 6, so they get picked again. That creates a loop where the first few active runs keep cycling and the newest active runs never get a chance. A custom scheduler is probably more machinery than you want here. If missing data is an expected condition, I would avoid representing “data is not ready yet” as A cleaner shape is usually:
For example, conceptually: wait_for_data >> process_dataPut the pool / task concurrency limit on If you really want to keep this retry-based design and only change ordering, look at priority weights before touching the scheduler. Since priority is sorted before Roughly: class NewerOrFewerRetriesFirst(PriorityWeightStrategy):
def get_weight(self, ti):
# Example only: tune this policy for your case.
return max(1000 - ti.try_number, 1)Then use it as the task’s If this is intended as a bug report rather than a design question, the useful next details would be the exact Airflow version, executor, database backend, and a short scheduler log excerpt showing the runs cycling. My read is that this is more of a scheduler fairness/improvement case than a pool or If my answer solved your problem, you can click answered the question. I'm really here to help, and along the way I'm also collecting Galaxy Brain badges haha 😆 |
Beta Was this translation helpful? Give feedback.
This looks like the scheduler behaving consistently with the current policy, not the pool behaving oddly. In the critical scheduling path, Airflow picks scheduled TIs ordered by priority weight descending, then
DagRun.logical_dateascending, thenmap_index; after that it applies constraints such as pool slots, DAG/task concurrency, andmax_active_tis_per_dag. With one task and equal priority, the oldest eligible run always wins the tie. A pool with 2 slots changes the capacity, not the ordering, so it does not make the selection fair across logical dates. (apache.googlesource.com)The retry pattern in the repro makes the starvation much easier to hit. The first two runs execute, fail, and…