Repository navigation
Load each serialized Dag once per triggerer pickup batch - #76215
Open
hussein-awala wants to merge 1 commit into
Open
hussein-awala wants to merge 1 commit into
hussein-awala wants to merge 1 commit into
Conversation
When the triggerer picks up newly deferred tasks, it resolved the Dag version, fetched the full serialized Dag row and deserialized the whole Dag separately for every trigger, even though a batch is usually many tasks from the same few runs. That is two queries plus a full Dag deserialization per trigger on the supervisor's main loop, which also handles trigger events and the job heartbeat, so a burst of deferrals from a large Dag slows down every other triggerer duty and transfers the same serialized Dag blob dozens of times. The rules for which Dag version a run uses are unchanged. Within one batch, runs without a pinned version all see the same latest version instead of re-reading it per trigger.
hussein-awala
marked this pull request as ready for review
November 1, 2026 12:04
hussein-awala
requested review from
XD-DENG,
ashb and
dstandish
as code owners
November 1, 2026 12:04
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When the triggerer picks up newly deferred tasks,
_create_workloadresolves the run's Dag version and fetches the full serialized Dag row separately for every trigger, then deserializes the whole Dag just to check one task'sstart_from_triggerflag (triggerer_job_runner.py#L950-L960). A batch is usually many tasks of a few runs, so the same work repeats dozens of times on the supervisor's main loop, which also persists trigger events and heartbeats the job. Instead, read each Dag's latest version and load each distinct Dag version once per pickup batch.related: #76209
The rule that picks a run's version is unchanged and keeps the run-pinned semantics from #69988: the run's pinned version (the latest for runs without one), falling back to the task instance's own
dag_version_idwhen that version has no serialized Dag. The rule moves intoDBDagBag._resolve_version_for_run, which takes the latest-version lookup as a callable, soDBDagBagand the batch lookup share it.The batch lookup is a new
BatchSerializedDagResolverinairflow/models/dagbag.py, next toDBDagBagand marked:meta private:like it. Keeping it in that module means_resolve_version_for_runstays private and nothing outsidedagbag.pycalls it; the triggerer only creates a resolver and calls itsresolve(). The long-livedDBDagBagcaches are untouched; there is no new configuration or schema change.One visible difference: among the task triggers claimed in one loop (at most
[triggerer] max_trigger_to_select_per_loop, 50 by default), unversioned runs of a Dag all resolve against the latest version read at the first lookup. A version rewritten in place bySerializedDagModel.write_dagmid-batch is not re-read for that batch's later triggers; the next batch reads fresh values. The batch's triggers share one deserialized Dag, used only forget_task()._create_workloadgains an optional keyword-onlyserdag_resolverargument thatbuild_trigger_workloadspasses, so overrides must accept it. It no longer callsDBDagBag._version_from_dag_run, so aDBDagBagsubclass overriding it is bypassed here; nothing in the tree overrides it.DBDagBag.get_serialized_dag_model_for_run, which #69988 added for this call site, has no caller left and is removed. A follow-up planned in #76209 builds on this PR to keep deserialized Dags across batches; this PR stands on its own.In a local benchmark draining one Dag run's deferred tasks through
load_triggers():Ten of the 12 statements per batch are claim and bulk-fetch queries every batch already runs. This is the best case: the saving shrinks as a batch spans more distinct Dags or versions.
Statement counts are engine-wide, exclude COMMIT, and were identical in every round; the listener only increments a counter, so timings carry no per-statement instrumentation cost. Times are medians of 5 rounds, each running the base and the change once, alternating which runs first; no before/after ranges overlap. An unchanged control bench (50 failed triggers) moved +0.3 % on SQLite and +24.8 % on PostgreSQL, with overlapping ranges on both. PostgreSQL wall times are noisy: the control's runs spanned 154 to 256 ms. SQLite (file database) and PostgreSQL 16 (Docker, 2-CPU, 8 GiB VM) ran on a macOS Apple-silicon development machine; these are local measurements, not end-to-end latency.
Unit tests in
tests/unit/models/test_dagbag.pycover the resolver's memoization, its fallback to the task instance's version, and one latest-version lookup per Dag. A database-backedbuild_trigger_workloadstest intests/unit/jobs/test_triggerer_job.pyexpects one version lookup and one serialized Dag fetch for three triggers of one run, and fails without the change.Validation:
test_triggerer_job.py,test_trigger.py,test_dagbag.py): 247 passed, 1 skipped on SQLite (the skip is a PostgreSQL-only test).breeze verifylists passed on a local merge of the nine PRs opened together under Improve triggerer performance (AI-assisted deep dive) #76209: core DB tests of the API, Always, CLI, Core, Other and Serialization groups on SQLite (12,254 run, no failures), core non-DB tests (5,582 passed) and theapache-airflowdocumentation build.Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code (Opus 5.5) following the guidelines
🤖 Generated with Claude Code