Skip to content

fix(destination-bigquery): retry transient backend errors when opening write channel - #84296

Draft
devin-ai-integration[bot] wants to merge 4 commits into
devin/1785468672-bigquery-standard-inserts-interruptfrom
devin/1786543372-bigquery-writer-open-retry
Draft

fix(destination-bigquery): retry transient backend errors when opening write channel#84296
devin-ai-integration[bot] wants to merge 4 commits into
devin/1785468672-bigquery-standard-inserts-interruptfrom
devin/1786543372-bigquery-writer-open-retry

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

This PR targets PR #83276 (same function, open draft):


What

Related to https://github.com/airbytehq/oncall/issues/13286:

A single transient BigQueryException: 503 Service Unavailable from BigQuery's backend fails the whole destination attempt and gets paged as system_error. The exception comes from opening the resumable upload session in BigqueryBatchStandardInsertsLoader.switchToWriteChannel()bigquery.writer(job, writeChannelConfiguration), i.e. POST /upload/bigquery/v2/projects/<project>/jobs?uploadType=resumable.

That call is effectively not retried by the Google client, despite the connector configuring RetrySettings(maxAttempts = 15) in BigqueryBeansFactory. Reading the client source: TableDataWriteChannel.open() does go through BigQueryRetryHelper.runWithRetries, but the callable is HttpBigQueryRpc.openSkipExceptionTranslation(), which ends in a bare httpRequest.execute() and therefore throws HttpResponseException (an IOException) rather than a BaseServiceException. BigQueryOptions defaults to BigQueryBaseService.DEFAULT_BIGQUERY_EXCEPTION_HANDLER, which retries only BaseServiceException.isRetryable() plus ConnectException/UnknownHostException/SocketException — so ExceptionHandler.shouldRetry returns NO_RETRY for a 5xx on this path.

BigQuery's error reference documents backendError as 500/502/503/504, service-side, and says the client should retry with truncated exponential backoff (and that jobs.insert should be retried).

The loader's catch block then converted only 403/404 into ConfigErrorException and rethrew everything else as a plain BigQueryException, which the bulk CDK's default classifier turns into system_error — that is what paged Sentry (airbyte-destination-bigquery@3.0.22).

Caveat on the reported job: for the customer job in the oncall issue, 15 of 16 attempts failed source-side (TikTok Marketing "transient server-side error (code 50000)"); the BigQuery 503 only hit the final attempt. This PR closes a real destination-side gap (no retry + wrong failure classification), but it would not have made that particular job succeed. Prior low-volume reports of the same Sentry issue: https://github.com/airbytehq/oncall/issues/9724, https://github.com/airbytehq/oncall/issues/10592, https://github.com/airbytehq/oncall/issues/12881, https://github.com/airbytehq/oncall/issues/13046.

How

In switchToWriteChannel(), the write-channel open is now attempted in a bounded retry loop:

  • Retryable backend statuses (500, 502, 503, 504) are retried up to 5 attempts with truncated exponential backoff (1s, doubling, capped at 30s) plus up to 1s of jitter, logging each retry at warn with the attempt number, HTTP code, and message. Backoff/jitter style follows the transient-retry loop in the (also open) fix(destination-bigquery): retry transient timeouts and concurrent-update aborts during typing+deduping #76902 / fix(destination-bigquery): retry typing+deduping queries on concurrent-update aborts #76440 work on BigQueryDatabaseHandler.
  • 403/404 still fail fast as ConfigErrorException(CONFIG_ERROR_MSG + e), and any other non-retryable code is still rethrown immediately as BigQueryException(e.code, e.message, e) — unchanged behavior.
  • When retries are exhausted, the failure surfaces as TransientErrorException with the last BigQueryException as cause, so the bulk CDK classifies it transient instead of system_error.
  • Retries reuse the same explicit JobId; initiating a resumable upload session does not create the load job, so re-initiating is well-defined.
  • The sleep and jitter functions (and the retry bounds) are constructor parameters with production defaults, purely so the unit tests can exercise exhaustion without sleeping for real. BigqueryBatchStandardInsertsLoaderFactory.create() is unchanged.

Relationship to other open work on this same function. This PR is stacked on #83276, which wraps the blocking BigQuery calls in BigQueryUtils.executeBigQueryOperation { ... } (interrupt → TransientErrorException) and preserves the cause when rethrowing. The retry loop keeps the open call inside that utility, so the interrupt semantics from #83276 are preserved and the two changes don't conflict. #77873 ("handle missing load job") also touches this loader; it edits finish(), not the open path. If #83276 lands or closes, this branch can be rebased and retargeted to master.

Review guide

  1. write/standard_insert/BigqueryBatchStandardInsertLoader.kt — retry loop, status-code classification, exhaustion → TransientErrorException, retry-parameter seams
  2. write/standard_insert/BigqueryBatchStandardInsertsLoaderTest.kt — retry-then-success, 403/404 fail-fast (no retries), exhaustion, non-retryable 400 rethrow
  3. metadata.yaml / docs/integrations/destinations/bigquery.md3.0.25-rc.1 (progressive rollout enabled) + changelog

Test Coverage

New mockk unit tests in BigqueryBatchStandardInsertsLoaderTest:

  • 503 twice then success → loader succeeds, bigquery.writer called 3 times
  • 403 and 404 → ConfigErrorException, bigquery.writer called exactly once
  • 503 always → TransientErrorException with the BigQueryException as cause, called exactly maxOpenAttempts times
  • 400 → BigQueryException rethrown immediately with cause preserved, called once

Not verified locally: Gradle could not resolve dependencies on this machine — Maven Central returned HTTP 429 for software.amazon.awssdk:{s3,sts,sso,ssooidc}:2.22.10 across several spaced retries — so compileKotlin/compileTestKotlin/test never ran. Compilation and test execution are relying on CI here; please treat the CI unit-test result as the first real signal.

Declarative-First Evaluation

Not applicable — Java/Kotlin bulk-load destination, not a declarative/manifest connector.

Breaking Change Evaluation

Not breaking: no schema, spec, state, stream, or data-scope change. Retrying a failed open and reclassifying an exhausted retry as transient only affects failure paths. Versioned as 3.0.25-rc.1 because this connector has enableProgressiveRollout: true.

User Impact

A transient BigQuery backend blip while opening a standard-inserts load job no longer fails the sync attempt outright — it is retried for up to ~45s of backoff. If BigQuery is unavailable for longer than that, the attempt fails as a transient error with a readable message instead of a raw 503 classified as system_error, which should also stop this paging Sentry as a system error.

Can this PR be safely reverted and rolled back?

  • YES 💚

Devin session — requested via /ai-fix on https://github.com/airbytehq/oncall/issues/13286 by the Airbyte oncall workflow.

devin-ai-integration Bot and others added 3 commits August 12, 2026 14:05
Co-Authored-By: bot_apk <apk@cognition.ai>
Co-Authored-By: bot_apk <apk@cognition.ai>
Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

Copy link
Copy Markdown
Contributor

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

PR Slash Commands

Airbyte Maintainers (that's you!) can execute the following slash commands on your PR:

  • 🛠️ Quick Fixes
    • /format-fix - Fixes most formatting issues.
    • /bump-version - Bumps connector versions, scraping changelog description from the PR title.
      • Bump types: patch (default), minor, major, major_rc, rc, promote.
      • The rc type is a smart default: applies minor_rc if stable, or bumps the RC number if already RC.
      • The promote type strips the RC suffix to finalize a release.
      • Example: /bump-version type=rc or /bump-version type=minor
    • /bump-progressive-rollout-version - Alias for /bump-version type=rc. Bumps with an RC suffix and enables progressive rollout.
  • ❇️ AI Testing and Review (internal link: AI-SDLC Docs):
    • /ai-prove-fix - Runs prerelease readiness checks, including testing against customer connections.
    • /ai-canary-prerelease - Rolls out prerelease to 5-10 connections for canary testing.
    • /ai-review - AI-powered PR review for connector safety and quality gates.
  • 📝 AI Documentation:
    • /ai-docs-review - AI-powered documentation review for PRs with connector changes.
    • /ai-create-docs-pr - Creates a documentation PR for connector changes, stacked on the current PR.
  • 🚀 Connector Releases:
    • /publish-connectors-prerelease - Publishes pre-release connector builds (tagged as {version}-preview.{git-sha}) for all modified connectors in the PR.
    • /enable-autopilot-rollouts - Enables autopilot progressive rollouts for the modified connector(s) in the PR, remediating "autopilot rollouts not enabled for {connector-name}" auto-merge blockers. Sets defaultRolloutMode: autopilot and enableProgressiveRollout: true, preserving any existing autopilotConfig.
      • Optional args: connector=<CONNECTOR_NAME> (defaults to the modified connectors in the PR), strategy=fast|slow|default (defaults to fast).
      • Example: /enable-autopilot-rollouts or /enable-autopilot-rollouts connector=source-faker strategy=slow
  • ☕️ JVM connectors:
    • /update-connector-cdk-version connector=<CONNECTOR_NAME> - Updates the specified connector to the latest CDK version.
      Example: /update-connector-cdk-version connector=destination-bigquery
  • 🐍 Python connectors:
    • /poe connector source-example lock - Run the Poe lock task on the source-example connector, committing the results back to the branch.
    • /poe source example lock - Alias for /poe connector source-example lock.
    • /poe source example use-cdk-branch my/branch - Pin the source-example CDK reference to the branch name specified.
    • /poe source example use-cdk-latest - Update the source-example CDK dependency to the latest available version.
  • ⚙️ Admin commands:
    • /force-merge reason="<REASON>" - Force merges the PR using admin privileges, bypassing CI checks. Requires a reason.
      Example: /force-merge reason="CI is flaky, tests pass locally"
📚 Show Repo Guidance

Helpful Resources

📝 Edit this welcome message.

Co-Authored-By: bot_apk <apk@cognition.ai>
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Deploy preview for airbyte-docs ready!

Project:airbyte-docs
Status: ✅  Deploy successful!
Preview URL:https://airbyte-docs-mq6ihgfqc-airbyte-growth.vercel.app
Latest Commit:72e97de

Deployed with vercel-action

@github-actions

Copy link
Copy Markdown
Contributor

destination-bigquery Connector Test Results

472 tests  +2   421 ✅ +2   1h 54m 52s ⏱️ + 5m 3s
 24 suites ±0    51 💤 ±0 
 24 files   ±0     0 ❌ ±0 

Results for commit 72e97de. ± Comparison against base commit 60ac12c.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants