Fix Cloud SQL 409 operationInProgress on import/export operations - #68361
Fix Cloud SQL 409 operationInProgress on import/export operations#68361radhwene wants to merge 3 commits into
Conversation
E2E evidence: real Cloud SQL PostgreSQL instance, Airflow v2Adding the reproduction and validation runs behind the “Why hook-level retry, not only a sensor” Sensor-only approach does not close the race. I validated the standalone In that run, the sensor task That demonstrates the remaining TOCTOU window between:
Hook-level retry handles the contention at the submit point. I also validated this PR with a harder topology: four Cloud SQL admin operations All four tasks succeed after retrying the operation submit, and no This validates both patched hook methods, E2E DAG used for the validation (
|
78000d7 to
bc90bbc
Compare
051c092 to
9c98fbd
Compare
Address review feedback on apache#68361: operation_in_progress_retry() previously wrapped the whole import_instance, including the operation-status polling. A retryable 429 raised during polling re-ran the method and re-submitted an import that was already accepted, importing the same data twice. The submit now lives in _submit_import, which alone carries the retry decorator; import_instance waits outside the retry scope, so a polling failure fails the task instead of re-submitting. Adds a regression test asserting exactly one submit when polling raises a retryable error.
Address review feedback on apache#68361: operation_in_progress_retry() previously wrapped the whole import_instance, including the operation-status polling. A retryable 429 raised during polling re-ran the method and re-submitted an import that was already accepted, importing the same data twice. The submit now lives in _submit_import, which alone carries the retry decorator; import_instance waits outside the retry scope, so a polling failure fails the task instead of re-submitting. Adds a regression test asserting exactly one submit when polling raises a retryable error.
9c98fbd to
bccbb65
Compare
Address review feedback on apache#68361: operation_in_progress_retry() previously wrapped the whole import_instance, including the operation-status polling. A retryable 429 raised during polling re-ran the method and re-submitted an import that was already accepted, importing the same data twice. The submit now lives in _submit_import, which alone carries the retry decorator; import_instance waits outside the retry scope, so a polling failure fails the task instead of re-submitting. Adds a regression test asserting exactly one submit when polling raises a retryable error.
bccbb65 to
3a2d2ff
Compare
7593908 to
4a13166
Compare
Address review feedback on apache#68361: operation_in_progress_retry() previously wrapped the whole import_instance, including the operation-status polling. A retryable 429 raised during polling re-ran the method and re-submitted an import that was already accepted, importing the same data twice. The submit now lives in _submit_import, which alone carries the retry decorator; import_instance waits outside the retry scope, so a polling failure fails the task instead of re-submitting. Adds a regression test asserting exactly one submit when polling raises a retryable error.
|
Rebased onto current Hi @potiuk The earlier red CI was not from this change: @henry3260 's review feedback is addressed: the |
Address review feedback on apache#68361: operation_in_progress_retry() previously wrapped the whole import_instance, including the operation-status polling. A retryable 429 raised during polling re-ran the method and re-submitted an import that was already accepted, importing the same data twice. The submit now lives in _submit_import, which alone carries the retry decorator; import_instance waits outside the retry scope, so a polling failure fails the task instead of re-submitting. Adds a regression test asserting exactly one submit when polling raises a retryable error.
4a13166 to
3b3948c
Compare
3b3948c to
818da96
Compare
Address review feedback on apache#68361: operation_in_progress_retry() previously wrapped the whole import_instance, including the operation-status polling. A retryable 429 raised during polling re-ran the method and re-submitted an import that was already accepted, importing the same data twice. The submit now lives in _submit_import, which alone carries the retry decorator; import_instance waits outside the retry scope, so a polling failure fails the task instead of re-submitting. Adds a regression test asserting exactly one submit when polling raises a retryable error.
Apply the existing operation_in_progress_retry() policy to CloudSQLHook.import_instance and export_instance, the only two admin methods that lacked it. Also re-raise operationInProgress HttpError un-wrapped from import_instance so the retry decorator can see it; terminal HttpErrors still get the friendly AirflowException message.
Address review feedback on apache#68361: operation_in_progress_retry() previously wrapped the whole import_instance, including the operation-status polling. A retryable 429 raised during polling re-ran the method and re-submitted an import that was already accepted, importing the same data twice. The submit now lives in _submit_import, which alone carries the retry decorator; import_instance waits outside the retry scope, so a polling failure fails the task instead of re-submitting. Adds a regression test asserting exactly one submit when polling raises a retryable error.
d1c82ed to
fa83950
Compare
|
Rebased on current The changes requested on 2026-06-12 got fixed the next day. The changes-requested review is still open seven weeks later, and it's the only thing |


Problem
Cloud SQL allows only one administrative operation at a time per instance. When
CloudSQLImportInstanceOperatororCloudSQLExportInstanceOperatorsubmits anoperation while another Cloud SQL admin operation is still running on the same
instance, the Cloud SQL Admin API returns
HTTP 409 operationInProgress.CloudSQLHookalready usesGoogleBaseHook.operation_in_progress_retry()forseveral Cloud SQL admin methods, but
import_instanceandexport_instancewere not covered. As a result, transient backend contention fails the Airflow
task instead of retrying the operation submit.
Closes: #68040
Solution
This PR applies the existing
operation_in_progress_retry()policy to:CloudSQLHook.import_instanceCloudSQLHook.export_instanceIt also fixes a latent exception-handling issue in
import_instance.Before this change,
import_instancewrapped everyHttpErrorinto anAirflowException. That preventedoperation_in_progress_retry()from seeingthe original retryable
HttpError.With this change:
operationInProgressHttpErrors are re-raised unchanged so theretry decorator can evaluate them;
HttpErrors are still converted to the existing friendlyAirflowExceptionmessage.There is no public API change, no new operator parameter, and no new
authentication surface.
Why hook-level retry, not only a sensor
A standalone "no operation in progress" sensor can be useful as an optional
pre-wait primitive, but it cannot fully fix this bug.
The sensor checks the Cloud SQL instance state before the import/export operator
submits the operation. There is still a race between:
Another DAG, scheduler, user, maintenance operation, or external process can
start an admin operation during that gap. Therefore, the retry must exist at the
submit call itself.
This keeps the retry policy centralized in
CloudSQLHook, where all operatorsusing these methods benefit automatically. It is also consistent with the
existing retry behavior already used by other Cloud SQL admin methods.
A sensor can still be added separately as an optional convenience, but the
correctness fix for
409 operationInProgressbelongs in the hook.Testing
This PR is covered at two levels.
Unit tests
The unit tests verify the local retry contract:
export_instanceretries retryableHttpErrorresponses.import_instanceretries retryableHttpErrorresponses.import_instancere-raises retryableoperationInProgressHttpErrorunchanged, so
operation_in_progress_retry()can evaluate the originalexception type.
HttpErrors are still converted to the existingAirflowExceptionmessage.
The assertions are based on exception types and retry behavior, not string
matching on error messages.
E2E validation
The fix was also validated against a real Cloud SQL PostgreSQL instance.
reproduce
409 operationInProgress.The E2E DAG submits imports and exports in parallel against the same instance,
which validates both patched hook methods under real Cloud SQL operation
serialization.
Breaking changes
None.
The public API is unchanged. The only behavioral change is that Cloud SQL
importandexportnow retry the same transient backend contention conditionalready handled by other Cloud SQL admin methods.
Important
🛠️ Maintainer triage note for @radhwene · by
@potiuk· 2026-06-17 14:51 UTCHelpful heads-up from the maintainers — please address before this PR can be reviewed:
CI image checks / Static checks). Run them locally withprek run --all-files(orpre-commit run --all-files) and push the fixes.Non-DB tests: providers / Non-DB-prov::3.10:-amazon,celer...standard,Special tests / Latest Boto test: providers / All-prov:LatestBoto-Postgres:14:3.10:-amazon,celer...standard,Special tests / Pendulum2 test: providers / All-prov:Pendulum2-Postgres:14:3.10:-amazon,celer...standard,provider distributions tests / Compat 2.11.1:P3.10:… (+3 more). Reproduce and fix locally, then push.The ball is in your court — you've been assigned to this PR. Fix the above, then mark it Ready for review.
Automated triage — may be imperfect; a maintainer takes the next look.