kaxil commented on code in PR #67668:
URL: https://github.com/apache/airflow/pull/67668#discussion_r3677728067
##########
airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py:
##########
@@ -258,6 +262,20 @@ def ti_run(
result = session.execute(query)
log.info("Task instance state updated", rows_affected=getattr(result,
"rowcount", 0))
+ # queued_duration was historically emitted on the move to RUNNING; in
Airflow 3 that
+ # transition happens here rather than in the ORM, so emit it here
(mirrors scheduled_duration).
+ # Emit only on the first try (no prior end_date), matching the Airflow
2 behavior.
+ if (
+ previous_state in (TaskInstanceState.QUEUED,
TaskInstanceState.RESTARTING)
+ and ti.queued_dttm
+ and ti.end_date is None
Review Comment:
The mechanics work as written, and the comment above is accurate: a retry
really does still carry the previous attempt's `end_date` when `ti_run` is next
reached (`ti.end_date = ti_patch_payload.end_date` is set on the retry
transition, and `prepare_db_for_next_try` then rotates `id` via `uuid7()`
without clearing it), and Airflow 2 was first-try-only for the same reason --
`emit_state_change_metric` returns early on `if self.end_date`.
The issue is that @ashb made the opposite call on #67592, the other open fix
for #63503:
https://github.com/apache/airflow/pull/67592#discussion_r3435464094. The
scheduler refreshes `queued_dttm` on every enqueue, so a retry genuinely waited
in the queue and that sample is a real one worth keeping. That PR dropped the
guard in response. Worth aligning rather than shipping two different answers to
the same question. If the guard goes, `TI.end_date` comes back out of the
select and `test_ti_run_skips_queued_duration_on_retry` goes with it.
##########
airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py:
##########
@@ -258,6 +262,20 @@ def ti_run(
result = session.execute(query)
log.info("Task instance state updated", rows_affected=getattr(result,
"rowcount", 0))
+ # queued_duration was historically emitted on the move to RUNNING; in
Airflow 3 that
+ # transition happens here rather than in the ORM, so emit it here
(mirrors scheduled_duration).
+ # Emit only on the first try (no prior end_date), matching the Airflow
2 behavior.
+ if (
+ previous_state in (TaskInstanceState.QUEUED,
TaskInstanceState.RESTARTING)
+ and ti.queued_dttm
+ and ti.end_date is None
+ ):
+ stats.timing(
+ "task.queued_duration",
+ timezone.utcnow() - ti.queued_dttm,
+ tags={"task_id": ti.task_id, "dag_id": ti.dag_id, "queue":
ti.queue},
Review Comment:
These tags don't line up with `task.scheduled_duration`, which goes out as
`{**ti.stats_tags, "queue": ti.queue}`: `dag_id`, `task_id`, `queue`,
`run_type`, plus `team_name` under multi-team and Dag tags when that config is
enabled. In Airflow 2 both metrics shared that single emit path in
`emit_state_change_metric`, so `queued_duration` carried `run_type` as well. As
written the restored metric can't be sliced the same way as its sibling.
`DR.run_type` is a free add to the select since DR is already joined, and
`get_team_name_for_ti` is called about 40 lines below for `dr.team_name`, so
emitting after that point would reuse the value instead of paying for a second
query.
##########
airflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py:
##########
@@ -258,6 +262,20 @@ def ti_run(
result = session.execute(query)
log.info("Task instance state updated", rows_affected=getattr(result,
"rowcount", 0))
+ # queued_duration was historically emitted on the move to RUNNING; in
Airflow 3 that
+ # transition happens here rather than in the ORM, so emit it here
(mirrors scheduled_duration).
+ # Emit only on the first try (no prior end_date), matching the Airflow
2 behavior.
Review Comment:
It isn't first-try-only in practice: a resume from deferral reaches this
again. The `DEFERRED` transition writes only `state`, `trigger_id`,
`next_method`, `next_kwargs` and `trigger_timeout`, and `ti_run` already
cleared `end_date` when the attempt started, so on resume `end_date` is `None`,
the scheduler has refreshed `queued_dttm`, and `previous_state` is `QUEUED`.
Every deferral cycle adds another sample inside the same try.
`ti.next_method` is already in the select and used further down, so `and
ti.next_method is None` covers it. @henry3260 raised the same point on #67592:
https://github.com/apache/airflow/pull/67592#discussion_r3311945730
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]