Fix deadlock when concurrent tasks queue the same asset's downstream dags#70128
Open
Andrushika wants to merge 1 commit into
Open
Fix deadlock when concurrent tasks queue the same asset's downstream dags#70128Andrushika wants to merge 1 commit into
Andrushika wants to merge 1 commit into
Conversation
…dags When a task updates an asset, register_asset_change queues an AssetDagRunQueue row for each of the asset's downstream dags. The dags come from a set, whose iteration order varies between processes, and the rows were inserted in that order. Two tasks completing at once and emitting to the same asset could take the per-row locks in opposite orders and deadlock. Insert in a fixed dag_id order so the lock order is consistent across processes; this covers the postgres, mysql, and per-row SAVEPOINT paths.
Andrushika
marked this pull request as ready for review
July 20, 2026 13:06
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.
Fix deadlock when concurrent tasks queue the same asset's downstream dags
Background
When a task produces an asset, the downstream Dags scheduled on that asset need to run. Airflow records this in the
asset_dag_run_queue(ADRQ) table, one row per(asset_id, target_dag_id). So when a task succeeds and updates assetS,register_asset_changeinserts one ADRQ row for every Dag scheduled onS. Those Dags are collected into a Pythonset(dags_to_queue), and the rows are inserted by looping over that set.Why
A Python
sethas no stable order (The essence ofsetis a hash table). Here, the elements areDagModelobjects, freshly loaded at different memory addresses in each process, so two processes can loop over the same Dags in opposite orders.When two tasks finish at the same time and both emit to the same asset, they insert the same ADRQ rows, but maybe in opposite orders. Each insert holds a row lock until commit. So transaction A can hold row
(S, dag_a)and wait for(S, dag_b), while transaction B holds(S, dag_b)and waits for(S, dag_a). That is a deadlock. Postgres aborts one side and it retries, which adds latency and error noise under high asset fan-out.What
Insert the rows in a fixed order, sorted by
dag_id, so every process takes the row locks in the same order and the cycle cannot form. A small shared helper_sorted_by_dag_idis used in all three insert paths (postgresON CONFLICT, mysqlON DUPLICATE KEY, and the per-rowSAVEPOINTfallback), because all three loop over the same set.I was running the concurrent test for #70078 and found that this problem exists on
main.Then I reproduced it on Postgres with a script that has 8 concurrent transactions insert one asset's downstream rows in opposite orders (what two processes' set iteration can produce). Over 20 rounds it hit 658 deadlocks with the old order and 0 after sorting. I only benchmarked Postgres. MySQL uses InnoDB row locks with the same inversion, so sorting fixes it there too. The SQLite path cannot deadlock this way and is sorted only for consistency.
The script for reproduce:
Generated-by: Claude Code Opus 4.8 following the guidelines
{pr_number}.significant.rst, in airflow-core/newsfragments. You can add this file in a follow-up commit after the PR is created so you know the PR number.