Skip to content

[v3-4-test] Fix missing task.queued_duration metric in Airflow 3 (#67592) - #74965

Merged
vatsrahul1001 merged 1 commit into
v3-4-testfrom
backport-21c858e-v3-4-test
Oct 18, 2026
Merged

vatsrahul1001 merged 1 commit into
v3-4-testfrom
backport-21c858e-v3-4-test

Conversation

@github-actions

Copy link
Copy Markdown
Contributor
  • Fix missing task.queued_duration metric in Airflow 3

task.queued_duration (and its registry-derived legacy name
dag.<dag_id>.<task_id>.queued_duration) stopped firing after the Airflow 3
worker switched to the Task SDK / supervisor / Execution API. The only emit
site was TaskInstance.emit_state_change_metric, reachable solely from the
legacy LocalTaskJob path, while Airflow 3 workers flip the TI to RUNNING
through the ti_run Execution API endpoint instead.

Emit it from ti_run on the genuine QUEUED -> RUNNING transition, tagged via
DagRun.stats_tags so the restored metric stays sliceable the same way as its
sibling task.scheduled_duration (dag_id, task_id, queue, run_type, team_name
under multi-team, and Dag tags when configured). Resumes from deferral are
skipped so a deferral cycle does not add a second sample inside the same try.

closes: #63503
closes: #66067

  • Pin the team_name tag on task.queued_duration under multi-team

With multi-team off the tag is absent from both the metric and any expectation
derived from stats_tags, so the assertion held for the wrong reason and would
not have caught the tag going missing.

  • Describe what the queued_duration guard actually does

The comment claimed to mirror a legacy emit that fired on every transition to
RUNNING; that emit skipped retries and fired on deferral resumes, so the claim
was inverted on both paths. It also credited the trigger with setting
next_method, which the DEFERRED transition does, and asserted one sample per try
without accounting for operators deferred before they are ever queued.

  • Show start_from_trigger tasks emit no queued_duration

These operators are deferred straight from SCHEDULED without being queued, so
the resume the next_method guard skips is the only queue wait they ever have.

  • Count task.queued_duration once per queue wait

A resume from deferral waits for a worker slot exactly as a first run does, and
an operator with start_from_trigger=True never reaches the queue until that
resume, so suppressing it left the only wait those tasks have unmeasured.

Dag tags are eager-loaded because this path runs once per task start, where the
lazy load stats_tags otherwise falls back to costs a query per task.

  • Emit task.queued_duration only once the run request cannot fail

The SDK retries a 5xx from this route, and the rollback returns the task
instance to QUEUED for that retry to pick up, so a sample taken before the
response was settled counted a single queue wait twice.

Airflow 2 already reported the wait on deferral resumes; what changes for
anyone upgrading is retries and reschedule-mode pokes, so the newsfragment
names those instead.
(cherry picked from commit 21c858e)

Co-authored-by: Tung, Hsiao-Yu myps6415@gmail.com

)

* Fix missing task.queued_duration metric in Airflow 3

task.queued_duration (and its registry-derived legacy name
dag.<dag_id>.<task_id>.queued_duration) stopped firing after the Airflow 3
worker switched to the Task SDK / supervisor / Execution API. The only emit
site was TaskInstance.emit_state_change_metric, reachable solely from the
legacy LocalTaskJob path, while Airflow 3 workers flip the TI to RUNNING
through the ti_run Execution API endpoint instead.

Emit it from ti_run on the genuine QUEUED -> RUNNING transition, tagged via
DagRun.stats_tags so the restored metric stays sliceable the same way as its
sibling task.scheduled_duration (dag_id, task_id, queue, run_type, team_name
under multi-team, and Dag tags when configured). Resumes from deferral are
skipped so a deferral cycle does not add a second sample inside the same try.

closes: #63503
closes: #66067

* Pin the team_name tag on task.queued_duration under multi-team

With multi-team off the tag is absent from both the metric and any expectation
derived from stats_tags, so the assertion held for the wrong reason and would
not have caught the tag going missing.

* Describe what the queued_duration guard actually does

The comment claimed to mirror a legacy emit that fired on every transition to
RUNNING; that emit skipped retries and fired on deferral resumes, so the claim
was inverted on both paths. It also credited the trigger with setting
next_method, which the DEFERRED transition does, and asserted one sample per try
without accounting for operators deferred before they are ever queued.

* Show start_from_trigger tasks emit no queued_duration

These operators are deferred straight from SCHEDULED without being queued, so
the resume the next_method guard skips is the only queue wait they ever have.

* Count task.queued_duration once per queue wait

A resume from deferral waits for a worker slot exactly as a first run does, and
an operator with start_from_trigger=True never reaches the queue until that
resume, so suppressing it left the only wait those tasks have unmeasured.

Dag tags are eager-loaded because this path runs once per task start, where the
lazy load stats_tags otherwise falls back to costs a query per task.

* Emit task.queued_duration only once the run request cannot fail

The SDK retries a 5xx from this route, and the rollback returns the task
instance to QUEUED for that retry to pick up, so a sample taken before the
response was settled counted a single queue wait twice.

Airflow 2 already reported the wait on deferral resumes; what changes for
anyone upgrading is retries and reschedule-mode pokes, so the newsfragment
names those instead.
(cherry picked from commit 21c858e)

Co-authored-by: Tung, Hsiao-Yu <myps6415@gmail.com>
@boring-cyborg boring-cyborg Bot added area:API Airflow's REST/HTTP API area:task-sdk labels Oct 18, 2026
@vatsrahul1001
vatsrahul1001 marked this pull request as ready for review October 18, 2026 08:38
@vatsrahul1001
vatsrahul1001 merged commit 88d425b into v3-4-test Oct 18, 2026
100 of 111 checks passed
@vatsrahul1001
vatsrahul1001 deleted the backport-21c858e-v3-4-test branch October 18, 2026 09:39
@github-actions github-actions Bot added this to the Airflow 3.4.1 milestone Oct 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor Author

Hi maintainer, this PR was merged without a milestone set.
We've automatically set the milestone to Airflow 3.4.1 based on: bug fix merged to version branch
If this milestone is not correct, please update it to the appropriate milestone.

This comment was generated by Milestone Tag Assistant.

vatsrahul1001 pushed a commit that referenced this pull request Oct 20, 2026
) (#74965)

* Fix missing task.queued_duration metric in Airflow 3

task.queued_duration (and its registry-derived legacy name
dag.<dag_id>.<task_id>.queued_duration) stopped firing after the Airflow 3
worker switched to the Task SDK / supervisor / Execution API. The only emit
site was TaskInstance.emit_state_change_metric, reachable solely from the
legacy LocalTaskJob path, while Airflow 3 workers flip the TI to RUNNING
through the ti_run Execution API endpoint instead.

Emit it from ti_run on the genuine QUEUED -> RUNNING transition, tagged via
DagRun.stats_tags so the restored metric stays sliceable the same way as its
sibling task.scheduled_duration (dag_id, task_id, queue, run_type, team_name
under multi-team, and Dag tags when configured). Resumes from deferral are
skipped so a deferral cycle does not add a second sample inside the same try.

closes: #63503
closes: #66067

* Pin the team_name tag on task.queued_duration under multi-team

With multi-team off the tag is absent from both the metric and any expectation
derived from stats_tags, so the assertion held for the wrong reason and would
not have caught the tag going missing.

* Describe what the queued_duration guard actually does

The comment claimed to mirror a legacy emit that fired on every transition to
RUNNING; that emit skipped retries and fired on deferral resumes, so the claim
was inverted on both paths. It also credited the trigger with setting
next_method, which the DEFERRED transition does, and asserted one sample per try
without accounting for operators deferred before they are ever queued.

* Show start_from_trigger tasks emit no queued_duration

These operators are deferred straight from SCHEDULED without being queued, so
the resume the next_method guard skips is the only queue wait they ever have.

* Count task.queued_duration once per queue wait

A resume from deferral waits for a worker slot exactly as a first run does, and
an operator with start_from_trigger=True never reaches the queue until that
resume, so suppressing it left the only wait those tasks have unmeasured.

Dag tags are eager-loaded because this path runs once per task start, where the
lazy load stats_tags otherwise falls back to costs a query per task.

* Emit task.queued_duration only once the run request cannot fail

The SDK retries a 5xx from this route, and the rollback returns the task
instance to QUEUED for that retry to pick up, so a sample taken before the
response was settled counted a single queue wait twice.

Airflow 2 already reported the wait on deferral resumes; what changes for
anyone upgrading is retries and reschedule-mode pokes, so the newsfragment
names those instead.
(cherry picked from commit 21c858e)

Co-authored-by: Tung, Hsiao-Yu <myps6415@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:API Airflow's REST/HTTP API area:task-sdk

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants