Add durable execution to SnowflakeSqlApiOperator#69477
Open
amoghrajesh wants to merge 3 commits into
Open
Conversation
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
SnowflakeSqlApiOperatorsubmits one or more SQL statements to the Snowflake SQL API and polls their statement handles to completion on the worker. On a worker crash or preemption mid-poll, Airflow retries the task by callingexecute()again which re-submits the SQL from scratch, since nothing about the in-flight statement handles is persisted across attempts.Current behaviour
A retry after a crash always resubmits the full SQL, even if the original statements are still running (or already finished) in Snowflake. For non-idempotent SQL (
INSERT,UPDATE,CREATE TABLE, etc.) this risks duplicate writes; for expensive queries it's wasted warehouse compute, since the orphaned original execution keeps running with nobody polling it.Proposed change
Adds
ResumableJobMixinsupport (Airflow 3.3+) toSnowflakeSqlApiOperator, following the same pattern already completed forDatabricksSubmitRunOperator/DatabricksRunNowOperator. Before polling begins, the submitted statement handles are persisted totask_state_store. On retry, the operator reads them back and:durable=Trueis the default; setdurable=Falseto keep the old "always submit fresh on retry" behavior.deferrable=Truetakes precedence overdurable— the Triggerer already tracks handles across the wait in that mode.Includes a companion fix (already merged separately, #69450) that removed an unconditional per-handle sleep in
poll_on_queries(), which this port relies on to stay latency-neutral on the already-resolved case.Changes of Note
get_job_statusaggregates across the list of statement handles into the single status string the mixin's interface expects: anyerrorwins over anyrunning, which wins over all-success— matching Snowflake's all-or-nothing submission semantics (there's no per-statement repair, unlike Databricks'repair_run).poll_until_completealone on reconnect —get_job_resultis never invoked on that path. Sinceget_job_resultis wherecheck_query_output(the actual result fetch/log) lives,check_query_outputwas moved intopoll_until_completeitself, guarded by a flag so the fresh-submit path (which calls both methods) doesn't fetch/push twice.get_job_statusand treated as anot_foundsentinel, degrading to a fresh resubmit rather than failing the task.User implications / backcompat
No breaking change.
durabledefaults toTrueon Airflow 3.3+; on earlier versions it's a no-op stub and the operator always submits fresh, exactly as before. Iftask_state_storeisn't available at runtime, the operator logs that crash recovery is disabled and falls back to the same fresh-submit behavior.One minor, intentional behavior shift: a fresh submission that is still running on its very first status check now incurs one
poll_intervalof latency it didn't before (the old code had a sleep-free pre-check before entering the poll loop; the durable path goes straight into the sleep-guarded loop). One-time cost, not compounding, and documented in the test suite.Testing
Follow this to create a snowflake connection using RSA: https://medium.com/@chik0di/using-the-snowflakesqlapi-operator-in-airflow-0206632db2a3
DAG used:
Before changes
Before:
Initial try where worker crashed:
Later try repeats the same effort which was already done:
Duplicate effort:
First try suceeded in snowflake at 5:37:43 even when airflow task failed at: 5:37:17
After changes
Initial try where worker crashed

Worker is back up but job has already completed:

Since I had a custom backend configured, data stored is:
[Breeze:3.10.20] root@62f2e50bd27b:/opt/airflow$ cat /tmp/airflow_state/ti_multi_statement/snowflake_query_ids.json ["01c5873f-061b-2e62-0068-2101308e710b"]No duplicate resubmission.
{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.