Adding durable execution to BigQueryInsertJobOperator - #69542
Merged
Conversation
kaxil
reviewed
Jul 7, 2026
kaxil
approved these changes
Jul 8, 2026
BigQueryInsertJobOperator
Contributor
Author
|
Thanks for the reviews, @kaxil. Merging this one in! |
Contributor
Didn't manage to review it, but looks great :) |
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.
Was generative AI tooling used to co-author this PR?
What
BigQueryInsertJobOperatorsubmits a BigQuery job and polls it on the worker. If the worker crashes mid-poll and the task is retried, there was no reliable way to reconnect to the still running job & the retry submits a brand new one, orphaning the original (which keeps running and costing money) instead of reusing it.Current behaviour
The operator already has a partial, fragile reattach mechanism: it optimistically resubmits with a recomputed job id and relies on BigQuery rejecting the duplicate (
Conflict/HTTP 409) to detect and reattach to an existing job. This only works whenforce_rerun=False(or a fixedjob_id) andreattach_statesis populated butforce_rerun=Trueis the operator's own default, which makesgenerate_job_idreturn a fresh random id on every attempt, so by default the retry's recomputed id never matches and reattach never triggers. A crash-and-retry under default settings always submits a duplicate job and orphans the first one.Proposed change
Adopt
ResumableJobMixin(fromairflow.sdk, AIP-103) so the operator persists the actual submitted job id to task state and reads it back on retry, rather than trying to recompute an identical id. This makes reattach work regardless offorce_rerun:submit_job: extracted from the oldexecute(): generates/submits the job (still honoring the existingConflict/reattach_states/429-retry rules on the fresh-submit path) and persists the job id.get_job_status: fetches the job by id (get_job), mappingDONEto"success"/"error"based onerror_result, withNotFounddegrading to a"not_found"sentinel so an expired/unknown id triggers a fresh submit instead of crashing.is_job_active/is_job_succeeded: simple predicates over that status.poll_until_complete/get_job_result: wait on the job and return its id, reusing the existing link/XCom bookkeeping (_persist_job_links) so behavior is identical whether this attempt submitted the job or reconnected to it.execute()now checksdeferrablefirst (unchanged) and routes the synchronous path throughexecute_resumable;submit_jobis shared as-is by the deferrable branch since its logic doesn't depend ondeferrableat all.Changes of Note
self._configured_job_id:get_job_status(called during a durable resubmit's initial status check) mutatesself.job_idto the old failed job's id via_persist_job_links, beforesubmit_jobruns again. Ifsubmit_jobusedself.job_idas input togenerate_job_id, it would compute a corrupted id from the stale value. Fixed by capturing the user's originaljob_idinput once in__init__asself._configured_job_id(never mutated) and using that instead.poll_until_completemust return the job id: on the mixin's reconnect path,execute_resumablereturnspoll_until_complete's result directly, skippingget_job_resultentirely, an easy bug to reintroduce (also hit independently in the Redshift port).get_job_statusself-resolvesself.hook/self.project_id: these are normally set bysubmit_job, which is skipped entirely on the reconnect path, soget_job_statusresolves them itself when unset.reattach_states/Conflict/429/DONE-can't-reattach special cases are preserved unchanged on the fresh-submit path (durable=False, or pre-Airflow-3.3).User implications / backcompat
New
durableparameter, defaulting toTrueon Airflow 3.3+. On older Airflow theResumableJobMixinimport is stubbed and the operator falls back to today's regenerate-id +Conflict-reattach behavior — no behavior change for those versions.force_rerun's default is unchanged; durable execution makes its non-determinism irrelevant to crash-recovery specifically, but it's still relevant to idempotency across separate DAG runs (task state is scoped per task instance), soforce_rerunis not being deprecated.Testing
Create a connection with your GCP credentials, like so:
airflow connections add google_cloud_default --conn-type google_cloud_platform --conn-extra '{"key_path": "/opt/airflow/dev/sigma-night-269811-6107ef06a57f.json", "project": "sigma-night-269811", "scope": "https://www.googleapis.com/auth/cloud-platform"}'DAG:
Before changes
First try where worker was killed:

This job started running:
When worker comes back up, a new bigquery job gets submitted:
Hence lot of compute wastage
Before changes but with
force_rerun=FalseTry 1

Worker killed and brought back up
Try 2:
Try 3:
You can see that:
force_rerun=False-without-reattach_states fragility, and it's why the mixin's persisted-id-and-reconnect approach (on bigquery-durable-execution) is needed, it reconnects on Try 2 directly instead of depending on reattach_states being populated correctly.After Changes:
Try 1:

Worker came back up, try 2:
What's next
Identified several other BigQuery operators with the same crash-vulnerability shape (
BigQueryCheckOperator,BigQueryValueCheckOperator,BigQueryIntervalCheckOperator,BigQueryColumnCheckOperator,BigQueryTableCheckOperator), will pick them and work one by one.{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.