Skip to content

Allow explicit runs to drain a paused Dag - #73763

Merged
eladkal merged 7 commits into
apache:mainfrom
dheerajturaga:drain-dag-on-explicit-runs
Oct 5, 2026
Merged

eladkal merged 7 commits into
apache:mainfrom
dheerajturaga:drain-dag-on-explicit-runs

Conversation

@dheerajturaga

@dheerajturaga dheerajturaga commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

A paused Dag cannot execute an explicitly triggered run without changing its scheduling state. Unpausing also resumes the schedule, which is undesirable for Dags used to reprocess historical data on demand.

This change lets manual runs, backfills, and asset materializations put the Dag into the existing draining state. Unfinished runs can start or resume, the scheduler creates no scheduled or asset-triggered runs, and the Dag returns to paused when its unfinished runs finish. Runs held by a paused backfill remain held; that backfill must be resumed for its runs to finish and draining to complete.

The state change affects the whole Dag. A paused Dag still starts nothing: draining clears its paused state, without adding a per-run override or changing the scheduler's paused checks.

closes: #60768

  • The trigger, backfill, and materialization forms offer Unpause (the default), Drain, and Keep paused. Unpause resumes the schedule; Drain allows unfinished runs to finish without resuming the schedule; Keep paused leaves the Dag paused. Paused backfills remain paused under every choice.
  • Unpause and Drain show the count of unfinished runs that can proceed. The count waits for fresh backfill state and excludes runs held by a paused backfill. The options have an accessible radio-group label.
  • Forms wait for the Dag-state refresh before allowing submission. A denied drain reports the missing edit permission; choosing Keep paused clears the error and allows retrying without reopening the form.
  • POST /dags/{dag_id}/dagRuns, POST /backfills, and POST /assets/{asset_id}/materialize accept drain_dag, defaulting to false. Draining requires Dag edit permission in addition to the existing creation permission. On an active Dag, it stops scheduling and leaves the Dag paused after its unfinished runs finish.
  • airflowctl dags trigger and airflowctl backfill create expose --drain-dag. Default fields are omitted from outgoing requests for compatibility with older strict API models, while required nullable logical_date is preserved.

The drain state change commits or rolls back with run creation. Backfill creation retains its early backfill-row commit, then creates the runs before taking the Dag drain lock, keeping that lock out of the bulk insertion work. The scheduler's initializing-backfill guard covers this interval. The pre-existing finalizer race for a Dag that is already draining remains outside this change, as described in the review discussion.

Documentation, the existing drain release note, API descriptions, and generated clients describe the paused-backfill exception.

Validation: 56 affected UI tests and 27 airflowctl operation tests passed. PostgreSQL API/model/scheduler tests and rollback/cleanup tests passed; the independent-session lock tests passed on both PostgreSQL and MySQL. Regular and manual prek checks, including the production UI build, passed.

The radio-card layout:

Trigger form Drain selected Backfill form

These existing screenshots show the layout; the updated descriptions also explain that paused backfills stay paused.


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 5.5), Codex (GPT-6)

Generated-by: Claude Code (Opus 5.5), Codex (GPT-6) following the guidelines

@bbovenzi

Copy link
Copy Markdown
Contributor

It's also more than "queued runs start" @dheerajturaga . Any pre-existing dag run in running state would also resume it's tasks. That to me is the biggest potential footgun about this feature.

I think the "Dag is paused" + "unpause" being a collapsed section looks a little odd. @bbovenzi @pierrejeambrun Would one of you have time to do follow up converting that collapsed section into a custom dropdown control?

Let's just remove it from its own section and either put it in Advanced or have it always shown. I like the radio cards because we definitely need to tell a user how many dag runs will start running in unpaused or draining options. I would prefer the radio cards to be horizontal instead of vertical. I don't think we need a separate warning banner. It should be inline with the option so that it is very clear which action will run how many dag runs.

@dheerajturaga

dheerajturaga commented Sep 29, 2026 •

Copy link
Copy Markdown
Member Author

It's also more than "queued runs start" @dheerajturaga . Any pre-existing dag run in running state would also resume it's tasks. That to me is the biggest potential footgun about this feature.

I think the "Dag is paused" + "unpause" being a collapsed section looks a little odd. @bbovenzi @pierrejeambrun Would one of you have time to do follow up converting that collapsed section into a custom dropdown control?

@ash, I guess your concern is that it isnt fairly obvious that "Drain" will cause pre-existing in flight dag runs which were paused mid way to start executing, What do you think of the current approach with the new push where I make it obvious to the user saying that drain will cause these dag runs to start execution aswell?

image

@dheerajturaga
dheerajturaga force-pushed the drain-dag-on-explicit-runs branch from 9f474cf to 769cd44 Compare September 30, 2026 00:11

@eladkal eladkal 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.

LGTM
I like how it looks now!

@dheerajturaga
dheerajturaga force-pushed the drain-dag-on-explicit-runs branch from 769cd44 to 17c259a Compare September 30, 2026 16:39

@bbovenzi bbovenzi 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.

UI looks good. I'll let @ashb or @kaxil give the final OK

@dheerajturaga
dheerajturaga force-pushed the drain-dag-on-explicit-runs branch from 17c259a to ef46e73 Compare October 1, 2026 16:57
@dheerajturaga

Copy link
Copy Markdown
Member Author

Hopefully no objections ? @ashb , @kaxil ? Had to fix a merge conflict. will merge once CI passes

@ashb

ashb commented Oct 1, 2026

Copy link
Copy Markdown
Member

Screenshot looks much better, thank you. (My concern was with the section being collapsed meaning the behaviour could be hidden)

I'll give the code one last pass

Comment thread airflow-core/src/airflow/models/dag.py Resolved
A paused Dag never starts the runs a user triggers or backfills, so the only
way to run them was to unpause the Dag, which also resumes its schedule. Dags
that only reprocess historical data on demand then get scheduled runs that
fail or waste resources (see apache#60768). Letting runs execute on a paused Dag
was rejected, because a paused Dag should never run.

Draining already means "create no scheduled runs, finish the existing ones,
then pause", which is the behaviour these users want. Starting the drain
together with the explicit run keeps the paused contract intact and lands the
Dag back on paused once its runs finish, without any new scheduling state.
Draining changes a Dag's scheduling state, which the Dag update endpoint only
allows for users who may edit the Dag. The run-creating endpoints check just
the permission to create runs, so without this a user who may trigger runs
but not pause the Dag could still stop its schedule by asking to drain it.
The backfill drain was committed with the backfill row, and the runs were
created in a later transaction. When creating them failed, the Dag stayed
draining although the request failed: runs already queued on the paused Dag
started, and an active Dag stopped its schedule and ended up paused. Taking
the drain in the transaction that creates the runs makes it commit or roll
back with them, and its row lock keeps the drain finalizer from pausing the
Dag before they exist.

The trigger, backfill and materialize forms chose whether to drain from Dag
data cached for up to five minutes, so a Dag unpaused elsewhere in the
meantime could be drained and paused again. The drain warning also counted
the runs of a paused backfill, which do not start.

airflowctl sends drain_dag as false, never null, so the trigger body no
longer accepts null.
"Run without resuming the schedule" read like an option on the new run
alone, and could be read as "no scheduled runs resume either". What it
actually does is drain the whole Dag, so runs already queued while the Dag
was paused start as well. Naming the option "Drain" and stating that
consequence first keeps it from surprising users, and matches the
short-state-plus-detail format reviewers asked for across the paused-Dag
options.
Unpausing or draining a paused Dag lets every unfinished run proceed:
queued runs start, and runs that were already running resume their held
tasks. Describing that as "queued runs start" understated the effect, and
reviewers flagged the running-run case as the main surprise. Showing the
count inside the options that release those runs, with all three options
always visible, puts the consequence next to the choice instead of in a
separate warning behind a collapsed section.

The trigger modal already fetches the Dag when it opens, so its paused
state now comes only from that fetch rather than being overridden halfway
through the props from a value the caller may have cached.
Older servers reject newly serialized defaults, and failed drain requests
leave users unable to retry with Keep paused. Cached Dag state and
unfinished-run counts can also describe the wrong action.

Backfill insertion can hold the Dag row longer than necessary. Drain
documentation must distinguish runs held by paused backfills and avoid
promising protection from the existing finalizer race.
@eladkal

eladkal commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Merging so it won't miss the 3.4 cut time. We can iterate over fixes if needed

@eladkal
eladkal merged commit a44fd6d into apache:main Oct 5, 2026
158 checks passed
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Backport failed to create: airflow-ctl/v0-1-test. View the failure log Run details

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
❌ airflow-ctl/v0-1-test Commit Link

You can attempt to backport this manually by running:

cherry_picker a44fd6d airflow-ctl/v0-1-test

This should apply the commit to the airflow-ctl/v0-1-test branch and leave the commit in conflict state marking
the files that need manual conflict resolution.

After you have resolved the conflicts, you can continue the backport process by running:

cherry_picker --continue

If you don't have cherry-picker installed, see the installation guide.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an option to run backfill while keeping dag execution suspended

5 participants