Skip to content

[Java] Preserve BigQuery failed rows across retries - #39999

Draft
bvolpato wants to merge 3 commits into
apache:masterfrom
bvolpato:bvolpato/java-bigquery-failed-row-retries
Draft

[Java] Preserve BigQuery failed rows across retries#39999
bvolpato wants to merge 3 commits into
apache:masterfrom
bvolpato:bvolpato/java-bigquery-failed-row-retries

Conversation

@bvolpato

@bvolpato bvolpato commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Consecutive BigQuery Storage Write API row errors can duplicate a previously rejected record in Beam's failure output and omit the newly rejected record. After a serialization error, both Java writers compact the retry payload and timestamps but retain the original failsafeTableRows list. A later error index is then applied to the wrong version of that list.

For a write using withFormatRecordOnFailureFunction:

Append request Simulated service response Expected failed original row Current Beam output
[A, B, C] Reject index 0 A A
[B, C] Reject index 0 B A
[C] Success

C is written successfully. B is neither written nor recoverable from the failure output. The Storage Write API contract defines error indexes relative to the current request. This is a controlled reproduction with simulated responses, not a reproduced live BigQuery incident.

Filter failsafe rows alongside payloads and timestamps in both the sharded and unsharded writers. This preserves their positional correspondence through successive retries, including nullable entries used for the protobuf-to-TableRow fallback.

Reproduction and testing

The new StorageApiWriteRetryTest runs public BigQueryIO pipelines with the DirectRunner and a fake append service. It uses a failure formatter that returns original_id records distinct from the serialized rows, rejects one row per append, then delegates the successful append to FakeDatasetService. Assertions cover the failed records, successful output, persisted rows, and the three shrinking append sizes.

./gradlew :sdks:java:io:google-cloud-platform:test \
  --tests org.apache.beam.sdk.io.gcp.bigquery.StorageApiWriteRetryTest

The same regression fails in all three modes against unchanged upstream writer classes and passes with this fix: batch STORAGE_WRITE_API, streaming STORAGE_WRITE_API with a fixed shard, and STORAGE_API_AT_LEAST_ONCE.

The branch passes the normal Gradle compiler checks and a combined test run with the two existing BigQueryIOWriteTest cases testStorageWriteReturnsAppendSerializationError and testStorageWriteWithMultipleAppendsPerStream: 8 passed, 5 inapplicable parameter combinations skipped. spotlessJavaCheck, validateChanges, and git diff --check also pass. The baseline comparison separately compiles the unchanged writer classes with the same regression against released Beam 2.76 dependencies.

Downsides

Each serialization-error retry allocates one additional list of references to the surviving failsafe rows. The normal append path is unchanged; the error-path allocation is proportional to the batch being retried and does not copy the row objects.


  • Describe the bug and include a reproducible regression test.
  • Update CHANGES.md with the behavior change.
  • Apache Individual Contributor License Agreement, if required for this contribution.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant