Repository navigation
Conversation
…on API get_task_instance_states always ran a full, unfiltered TaskInstance query for the Dag even when scoped to a single task_group_id, then discarded those rows in favor of a second, separate query for the group's members. Workers call this endpoint on the task execution hot path, so the wasted round trip added avoidable RTT and DB load on every task-group-scoped call, proportional to Dag size and Dag-run count.
7daf7c0 to
686424e
Compare
|
Resolved the conflict :D |
|
Hello @ColtenOuO - thank you for your contributions to Apache Airflow! The Airflow community has introduced a limit of 5 open pull requests at a time for contributors without write access to the repository. You currently have 19 open pull requests, so - as a one-time step of introducing the limit - we closed the ones where maintainers have not engaged yet:
These pull requests stay open because maintainers are already engaged in them - they count towards your limit:
This is not a judgement of you or of your changes. We never told contributors before that opening many pull requests at once was a problem, so there is nothing to feel bad about - and nothing is lost: your branches, commits and the review history stay where they are. What we ask you to do is to make your first prioritization decision: choose which of the pull requests above matter most to you, and reopen them (up to 5 open at a time, including the ones still open) with the "Reopen pull request" button or While your pull requests are waiting for review, the most valuable thing you can do is help in other ways - reviewing other contributors' pull requests, helping with issues, and taking part in the discussions on the devlist and Slack. Why we introduced the limit, what it means for you and how to reopen or restore a pull request is explained in https://github.com/apache/airflow/blob/main/contributing-docs/32_open_pull_request_limit.rst. Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting |
Sumarry
GET /execution/task-instances/states(get_task_instance_statesinairflow-core/src/airflow/api_fastapi/execution_api/routes/task_instances.py) is called by the worker/Task SDK whenever a task needs the states of sibling task instances (e.g. trigger-rule evaluation,wait_for_downstream, cross-task state checks). When the request is scoped withtask_group_id, the handler was doing unnecessary DB work on every call:TaskInstancerow for the Dag (optionally narrowed bytask_ids,logical_dates,run_ids,map_index), loading full ORM entities.TaskInstancerows.task_ids, the result of the first query was thrown away entirely and only the second query's rows were used — meaning step 1 was pure waste on every task-group-scoped call.For Dags with many tasks and/or many Dag runs, that first discarded query could return and hydrate a large number of rows for nothing, adding an avoidable round trip and CPU/memory cost to a request that sits on the task execution hot path (workers call this while running tasks, so the extra latency is paid per task, not once).
This PR removes the wasted round trip:
task_group.iter_tasks()), which requires no extraTaskInstancequery — only the existing Dag-version lookup that was already being performed.task_idsfrom the request before building the query, so there is exactly oneTaskInstancequery per request, filtered withtask_id.in_(...)— preserving the exact original semantics (task_ids alone, task_group_id alone, or the union of both).run_id,task_id,map_index,state) instead of loading fullTaskInstanceORM objects, cutting the amount of data hydrated per call further._get_group_taskshelper (still used by the sibling/countendpoint, which needs actualmap_indexvalues from persisted rows for a different query shape) is left untouched aside from factoring out the shared "resolve the task group or 404" logic into_get_task_group.Change
get_task_instance_states: replaced the "query everything, then maybe discard it" flow with a singleTaskInstancequery. The task group's task IDs are resolved up front (fromtask_group.iter_tasks()) and unioned with any requesttask_idsinto onetask_id.in_(...)filter before the query runs, instead of running one query filtered bytask_idsand a second, separate query for the group, then conditionally throwing the first result set away.select(TI.run_id, TI.task_id, TI.map_index, TI.state)instead ofselect(TI), so it loads four scalar columns per row instead of hydrating fullTaskInstanceORM objects._get_task_group(Dag/task-group lookup + 404 handling) factored out of the pre-existing_get_group_tasks, and a new_get_group_task_idsthat returns just the group's task IDs without touching theTaskInstancetable._get_group_tasksitself is unchanged in behavior — it's still used as-is by the/countendpoint, which needs real persisted(task_id, map_index)pairs for a different query shape.test_get_task_states_with_task_group_id_query_count(query-count regression test).Impact
For any
task_group_id-scoped call to this endpoint, DB round trips drop from 2 (one of which was always discarded) down to 1, and that query is narrower (4 scalar columns instead of full-row ORM hydration). This directly reduces the RTT the worker/Task SDK pays on this call, and removes the redundant row hydration/network cost on the API server and DB — proportional to Dag size and Dag-run count, so the win scales with the DAGs that would have hit this path hardest.Testing
test_get_task_states_with_task_group_id_query_count, a query-count regression test usingassert_queries_count, which fails against the old code (3 queries) and passes against the fix (2 queries: one Dag-version lookup, one TaskInstance query).TestGetTaskStatesandTestGetCount(the/countendpoint that shares the untouched helper) continue to pass, confirming the task_ids/task_group_id union semantics are unchanged, including the mixedtask_ids+task_group_idcase.Was generative AI tooling used to co-author this PR?