Repository navigation
Add pin_symlink_on_refresh option to LocalDagBundle for atomically-swapped (git-sync) checkouts - #70003
Add pin_symlink_on_refresh option to LocalDagBundle for atomically-swapped (git-sync) checkouts#70003kjh0623 wants to merge 4 commits into
Conversation
GitDagBundle fetches the repository itself: every refresh runs git fetch and resolves the bundle's Airflow Connection inside the dag processor. For large repositories parsed frequently this puts git traffic, latency and credentials in the parsing path, and pairing Airflow with the kubernetes git-sync sidecar currently requires a custom bundle class. Add GitSyncDagBundle: a read-only bundle for checkouts maintained by an external synchronization process such as git-sync. No git command or Connection resolution ever happens in Airflow. The bundle resolves the symlink git-sync swaps atomically on each refresh and pins the resolved worktree until the next refresh, so a sync happening mid-parse cannot mix files from two different commits in one parsing round. We have been running this pattern in production over a ~3,600-DAG repository (git-sync pulls, the dag processor only reads) after the built-in fetch contended with parsing load; this upstreams it.
ephraimbuddy
left a comment
There was a problem hiding this comment.
There's no regression from airflow 2 to 3 in LocalDagBundle. It works same as airflow 2 dags folder. This is also documented.
If the symlink pinning is the issue here, why not an opt-in symlink pinning in LocalDagBundle instead of a new bundle construction?
LocalDagBundle(path="/git/repo", pin_symlink_on_refresh=True) # default False
But note that the worker also initializes the bundle at task start which will give the current symlink at the start
…stead of a new bundle class Per review feedback from ephraimbuddy: LocalDagBundle already covers the git-sync pattern the same way the Airflow 2 dags folder did, so a new bundle class is unnecessary. Move the only genuinely new behaviour -- resolving and pinning the atomically-swapped symlink on each refresh -- into LocalDagBundle behind an opt-in pin_symlink_on_refresh flag (default False, no behaviour change for existing users). Revert the GitSyncDagBundle class, its provider.yaml registration and git-provider docs; add tests and documentation for the new flag in core instead, including the note that each component (dag processor, worker, triggerer) resolves and pins independently at bundle initialization.
|
Thanks for the review @ephraimbuddy — you're right, LocalDagBundle already covers the git-sync pattern the same way the Airflow 2 dags folder did, so a new bundle class was unnecessary. I reworked the PR as you suggested: the GitSyncDagBundle class is removed, and the symlink pinning is now an opt-in pin_symlink_on_refresh flag on LocalDagBundle (default False, so no behavior change for existing users). On your note about workers: correct — each component (dag processor, worker, triggerer) resolves the symlink when it initializes the bundle, so pinning gives per-process consistency (one parse round or one task run), not global consistency. I documented that scope, plus the git-sync --stale-worktree-timeout recommendation, in the dag-bundles docs. PTAL when you have a chance. |
|
Maybe I don't follow, but why wouldn't you want to jump to the newer commit right when you can? I don't see a point in finishing the round on the old version, just to reparse it all on the new version? |
jedcunningham
left a comment
There was a problem hiding this comment.
Also, why do you want to use git-sync instead of GitDagBundle? You are loosing the code versioning benefits it brings.
|
@jedcunningham Good question — pinning doesn't delay picking up new commits: the very next Another way to look at it: For context: we ran this exact pattern (a central git-sync deployment + shared RWX PVC + |
|
@jedcunningham On losing the versioning benefits — you're right that The thing is, git-sync + So "use |
sphinx spellcheck flagged the two prose occurrences of "worktree" in the dag-bundles docs; wrap them in double backticks like the other git-sync terms on the same lines rather than adding the word to the shared wordlist. Signed-off-by: kjh0623 <[email protected]>
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 5 days if no further activity occurs. Thank you for your contributions. |
Note
Reworked per review feedback from @ephraimbuddy: the original version added a new
GitSyncDagBundleclass in the git provider. As pointed out,LocalDagBundlealready covers the git-sync pattern the same way the Airflow 2 dags folder did, so the only genuinely new behaviour — symlink pinning — is now an opt-in flag onLocalDagBundleinstead.What this adds
An opt-in
pin_symlink_on_refreshkwarg (defaultFalse, no behaviour change for existing users) onLocalDagBundle, for deployments where the bundle path is a symlink that an external synchronization process swaps atomically — typically the Kubernetes git-sync sidecar:git-sync publishes each synced commit as a separate worktree directory and atomically swaps a symlink to point at the current one. Without pinning, every file access follows the symlink, so a sync happening mid-parse can mix files from two different commits within one parsing round. With the flag enabled, the symlink is resolved on each
refresh()and the resolved target is pinned until the next refresh.Scope note (also in the docs): each component — dag processor, worker, triggerer — resolves and pins independently when it initializes the bundle, so this provides per-process consistency (one parse round, one task run), not global consistency. The docs recommend git-sync's
--stale-worktree-timeoutso a previously pinned worktree remains readable after a swap.Production context
We ran this pattern over a ~3,600-DAG repository (git-sync pulls, Airflow only reads the synced worktree) and hit the mid-parse mixing issue in practice; resolving the symlink once per refresh closes exactly that window. It is also the pattern the official helm chart deploys when
dags.gitSyncis enabled: the default bundle is aLocalDagBundleover the dags folder, with git-sync swapping the checkout underneath it.Included
pin_symlink_on_refreshonLocalDagBundle+initialize()refreshing so the pin is set before first use70003.feature.rstWas generative AI tooling used to co-author this PR?
Generated-by: Claude Code 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.