fix: eliminate duplicate JOINs in get_task_instances endpoint#62910
Conversation
airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
Outdated
Show resolved
Hide resolved
|
Thanks for the review @pierrejeambrun Regarding the performance benchmark - I ran a PostgreSQL benchmark at 20,000 rows and didn't see any measurable difference. Root cause: all duplicated JOINs are on many-to-one relationships (each TI has exactly one DagRun/DagVersion), so there's no row multiplication regardless. Updated the PR description to remove performance claims. |
looks good :) |
|
@ShubhamGondane This PR has a few issues that need to be addressed before it can be reviewed — please see our Pull Request quality criteria. Issues found:
What to do next:
Please address the issues above and push again. If you have questions, feel free to ask on the Airflow Slack. |
airflow-core/src/airflow/api_fastapi/core_api/routes/public/task_instances.py
Outdated
Show resolved
Hide resolved
…#62027) Replaces joinedload with contains_eager for already-joined tables, reducing the main query from 7 JOINs to 5.
|
@ShubhamGondane This PR has a few issues that need to be addressed before it can be reviewed — please see our Pull Request quality criteria. Issues found:
What to do next:
Please address the issues above and push again. If you have questions, feel free to ask on the Airflow Slack. |
…idation for all task instance endpoints
90f3a49 to
fc621a5
Compare
|
Thanks for the feedback @pierrejeambrun! I've reworked the approach:
|
…#62910) * fix: eliminate duplicate JOINs in get_task_instances endpoint (apache#62027) Replaces joinedload with contains_eager for already-joined tables, reducing the main query from 7 JOINs to 5. * tests: replace query-count test with SQL-structure regression test for apache#62027 * fix: mypy type annotation for dialect in benchmark script * fix: centralise join + eager-loading in eager_load_TI_and_TIH_for_validation for all task instance endpoints * ci: retrigger checks
…#62910) * fix: eliminate duplicate JOINs in get_task_instances endpoint (apache#62027) Replaces joinedload with contains_eager for already-joined tables, reducing the main query from 7 JOINs to 5. * tests: replace query-count test with SQL-structure regression test for apache#62027 * fix: mypy type annotation for dialect in benchmark script * fix: centralise join + eager-loading in eager_load_TI_and_TIH_for_validation for all task instance endpoints * ci: retrigger checks
…#62910) * fix: eliminate duplicate JOINs in get_task_instances endpoint (apache#62027) Replaces joinedload with contains_eager for already-joined tables, reducing the main query from 7 JOINs to 5. * tests: replace query-count test with SQL-structure regression test for apache#62027 * fix: mypy type annotation for dialect in benchmark script * fix: centralise join + eager-loading in eager_load_TI_and_TIH_for_validation for all task instance endpoints * ci: retrigger checks
…#62910) * fix: eliminate duplicate JOINs in get_task_instances endpoint (apache#62027) Replaces joinedload with contains_eager for already-joined tables, reducing the main query from 7 JOINs to 5. * tests: replace query-count test with SQL-structure regression test for apache#62027 * fix: mypy type annotation for dialect in benchmark script * fix: centralise join + eager-loading in eager_load_TI_and_TIH_for_validation for all task instance endpoints * ci: retrigger checks
…#62910) * fix: eliminate duplicate JOINs in get_task_instances endpoint (apache#62027) Replaces joinedload with contains_eager for already-joined tables, reducing the main query from 7 JOINs to 5. * tests: replace query-count test with SQL-structure regression test for apache#62027 * fix: mypy type annotation for dialect in benchmark script * fix: centralise join + eager-loading in eager_load_TI_and_TIH_for_validation for all task instance endpoints * ci: retrigger checks


Root cause
get_task_instancescombined explicitjoin(TI.dag_run)+outerjoin(TI.dag_version)withjoinedloadoptions for the same tables insideeager_load_TI_and_TIH_for_validation(). SQLAlchemy emits a separate JOIN for eachjoinedloadeven when the table is already in the FROM clause, producing redundant duplicate JOINs in the SQL.Before — 7 JOINs (dag_run and dag_version each joined twice):
After — 5 JOINs (each table once):
Note on performance: Because the query selects from
task_instanceand all the duplicated JOINs traverse many-to-one relationships (each TI has exactly one DagRun, one DagVersion), there is no row multiplication — the duplicate JOINs are redundant but not harmful at the row level. Benchmarks on PostgreSQL at scale (20,000 rows) confirmed no measurable timing difference. The value of this change is SQL correctness:contains_eageris the right SQLAlchemy idiom when the JOIN is already explicit, and it avoids emitting unnecessary SQL.Fix
Replace
joinedloadoptions for already-joined tables withcontains_eager, which tells SQLAlchemy to reuse the existing JOIN result for eager loading instead of adding a new one.eager_load_TI_and_TIH_for_validation()is intentionally left unchanged — its other callers (get_mapped_task_instances,get_task_instances_history) have different join patterns and are not affected.Testing
TestGetTaskInstancestests pass unchanged.test_no_duplicate_joins_in_get_task_instances_query, which calls the real endpoint and captures the SQL it emits via a SQLAlchemy event listener, then assertsdag_runanddag_versioneach appear exactly once in the JOINs.related: #62027
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Sonnet 4.6 following the guidelines