Skip to content

Load the serialized Dag in ti_run only for stub tasks - #75795

Merged
jason810496 merged 5 commits into
apache:mainfrom
jason810496:fix/execution-api/gate-arg-bindings-on-is-stub
Oct 28, 2026
Merged

jason810496 merged 5 commits into
apache:mainfrom
jason810496:fix/execution-api/gate-arg-bindings-on-is-stub

Conversation

@jason810496

@jason810496 jason810496 commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Why

Starting any task deserialized its whole Dag in Execution API, so that ti_run could find out whether the task was a stub and read its arg_bindings. On a Dag cache miss that is a full serialized_dag read plus deserialization per task start, paid while the route holds SELECT ... FOR UPDATE on the task_instance row, and for a Python task the result is thrown away.

How

  • The serialized is_stub flag is denormalized onto task_instance table for the fast lookup.

  • The route loads the task's definition only when the flag is set:

    serialized_task = get_task(dag_bag, ti, session=session) if ti.is_stub else None
  • Null means "not a stub", with no fallback. Every null row predates the migration, so it was written by 3.3, which has no arg_bindings at all, and such a task behaves exactly as it did there.

For the release manager

main has moved past the 3.4.0 chain head, and a 3.4.0 revision cannot sit after 3.5.0 ones or db migrate --to-version 3.4.0 would stop short of it. The new revision is therefore inserted, and the two 3.5.0 revisions after it are renumbered:

ee86eed19e24  0148_3_4_0_add_pending_partition_key_to_apdr
b4f1a7d2c903  0149_3_4_0_add_is_stub_to_task_instance        <- new
c71065a1b14f  0150_3_5_0_add_session_id_to_job               <- was 0149, down_revision rewritten
38c8df3fcb9c  0151_3_5_0_add_dag_run_dag_id_run_after_index  <- was 0150, unchanged

Note


Was generative AI tooling used to co-author this PR?

Comment thread airflow-core/tests/unit/dag_processing/test_dagbag.py Outdated

@ephraimbuddy ephraimbuddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits: I think there's no need to have the TABLE_NAME variable. non-blocking

@ashb

ashb commented Oct 27, 2026

Copy link
Copy Markdown
Member

More generally, I'm not sure gating this on "is_stub" is right. This is needed for an go task isn't it? I think we defined a stub task as one written in a language other than the one the dag is defined in. But that isn't the only place we need the arg_binding, is it?

@jason810496

Copy link
Copy Markdown
Member Author

This is needed for an go task isn't it?

Yes.

I think we defined a stub task as one written in a language other than the one the dag is defined in. But that isn't the only place we need the arg_binding, is it?

Actually, both Python Dag with Stub task and the task define in non-Python Dag will set the "is_stub" as true and the arg_binding field.

@jason810496 jason810496 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks all for the review.

Comment thread airflow-core/tests/unit/dag_processing/test_dagbag.py Outdated
jason810496 and others added 5 commits October 28, 2026 09:20
Starting any task deserialized its whole Dag so the route could find out
whether the task was a stub and read its arg_bindings. Denormalize the
serialized is_stub flag onto task_instance, which ti_run already fetches
and locks, and load the Dag only when it is set.

execution_timeout is resolved from the same definition, so it is now sent
for a lang-SDK task only; apache#75387 has a Python task send its own to the
supervisor.

Co-Authored-By: Claude <noreply@anthropic.com>
Dropping a column rebuilds the table on SQLite, unlike adding one, so the
downgrade copies task_instance rows while other tables still point at it.

Co-Authored-By: Claude <noreply@anthropic.com>
create_dagrun refreshes every row from its task right after the bulk write,
so the existing test passed even when insert_mapping dropped the key. Assert
the mapping itself instead, and record what a row keeps when it moves to a
new Dag version without being refreshed.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@vatsrahul1001
vatsrahul1001 force-pushed the fix/execution-api/gate-arg-bindings-on-is-stub branch from 01d776d to 978a6bc Compare October 28, 2026 03:50
@jason810496

Copy link
Copy Markdown
Member Author
  File "/usr/python/lib/python3.11/site-packages/requests/api.py", line 71, in request
    return session.request(method=method, url=url, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/python/lib/python3.11/site-packages/requests/sessions.py", line 651, in request
    resp = self.send(prep, **send_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/python/lib/python3.11/site-packages/requests/sessions.py", line 784, in send
    r = adapter.send(request, **kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/python/lib/python3.11/site-packages/requests/adapters.py", line 727, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /pypi/apache-airflow-providers-common-compat/json (Caused by SSLError(SSLEOFError(8, '[SSL: UNEXPECTED_EOF_WHILE_READING] EOF occurred in violation of protocol (_ssl.c:1016)')))

Flaky CI, merging the PR now.

@jason810496
jason810496 merged commit c24242e into apache:main Oct 28, 2026
139 of 143 checks passed
@jason810496
jason810496 deleted the fix/execution-api/gate-arg-bindings-on-is-stub branch October 28, 2026 07:08
jason810496 added a commit that referenced this pull request Oct 28, 2026
…5795) (#75802)

* [v3-4-test] Load the serialized Dag in ti_run only for stub tasks (#75795)

Starting any task deserialized its whole Dag so the route could find out
whether the task was a stub and read its arg_bindings. Denormalize the
serialized is_stub flag onto task_instance, which ti_run already fetches
and locks, and load the Dag only when it is set.

* Mark a Go or Java TriggerDagRun task a stub, as TypeScript does

* Inline the table name in the is_stub migration

* Starve a Dag the scheduler cannot deserialize

test_find_executable_task_instances_skips_dag_that_cannot_be_deserialized
fails on v3-4-test independently of this backport. #75745 brought the guard
over from #75263 without the starved set that #75263 relies on, because that
set came with #70307, which is not on this branch. The guard then returns
False without narrowing the next query, so a cycle whose batch is filled by
the bad Dag ends with nothing queued.

---------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Performance regression: The execution API load serialized Dag for every task

4 participants