Business intelligence analyst auditing Power Query merge cardinality, duplicated rows, and changed totals on dual monitors

Why Did the Totals Grow? Audit Power Query Merge Cardinality

Thu, Sep 17, 2026

A BigQuery writer times out after sending three rows. The process never records an acknowledgement. On restart, should it create a fresh stream, reuse offset 0, advance to 3, or re-read the source and rebuild the batch? Only one of those choices preserves the identity of the write you were already attempting.

This guide is for data engineers, streaming-platform owners, ingestion-service developers, and reliability reviewers using the gRPC BigQuery Storage Write API. The main path is an application-created committed stream with an explicit row offset. The lab target is deliberately narrow: a disposable, append-only BigQuery table that is compatible with Storage Write API ingestion and has no identity columns. It is not tabledata.insertAll, not the Storage Write REST interface, and not a claim that Google coordinates your upstream checkpoint, local journal, and downstream effects in one transaction.

Google documents exactly-once write semantics within an application-created stream when offsets are supplied; the default stream is at-least-once. It also documents asynchronous AppendRows, ordered responses on a connection, and offset errors that can result from client mistakes. Those are service boundaries, not a complete recovery protocol.

The operating model below is therefore explicit about what is documented and what is proposed. The proposed invariant is: destination + stream + row offset + exact batch identity + source range must survive a crash together. Everything else, including fencing, fingerprints, checkpoint ordering, and acceptance gates, is application-owned.

Choose the Exact Boundary of the Ingestion Guarantee

“Exactly once” is only useful after naming the object that is exactly once. For the Storage Write API, the documented control is a row offset on an application-created stream. If the supplied offset matches the next append position, the write proceeds; if the offset has already been written, the service reports ALREADY_EXISTS; if the offset is beyond the current end, it reports OUT_OF_RANGE. The default stream does not accept user-supplied offsets.

That contract does not say that two source records with the same business key are one event. It does not say that offset 0 on stream A is the same operation as offset 0 on stream B. And it does not make a source acknowledgement atomic with the BigQuery append.

Layer

Identity you must name

What can be asserted

Source event

Source-native event ID or business key, when one exists

Application/domain semantics

Source batch

Partition plus exact source range and ordered rows

Producer-owned recovery unit

BigQuery write

Destination table + created stream + row offset + row count

Service write position

Visible row

Stored row plus source/batch metadata you chose to persist

Queryable evidence after a committed append

Downstream effect

Consumer-specific operation or materialization

Separate idempotence/reconciliation problem

For this guide, the delivered unit is a durable mapping from a reproducible source range to one exact serialized batch and one BigQuery write position. “Exactly once” will mean the same intended batch is not appended twice at the same explicit position in the same application-created stream. Google describes committed streams as providing exactly-once delivery within a stream through record offsets.

A replay onto a new stream is a new service identity. A second upstream record that happens to represent the same purchase, sensor reading, or user action is also a distinct write unless your application applies a business-deduplication rule. Keep those boundaries visible in design reviews.

Select the Stream Type Before Designing Recovery

The recovery protocol depends on stream type. Google’s overview distinguishes the default stream from application-created streams, and application-created streams from one another by visibility and commit behavior. The default stream is immediately queryable and at-least-once. A committed application-created stream makes successful appends visible immediately and supports explicit offsets. A pending stream buffers rows until finalization and an atomic batch commit.

Stream choice

Explicit offsets

Visibility

Recovery burden

Appropriate claim

Default

No

Immediate

Lower writer state; duplicates handled elsewhere

At-least-once ingestion

Application-created committed

Yes

Immediate after successful append

Durable stream/offset/batch ledger

Exactly-once write semantics within that stream

Application-created pending

Yes

Only after finalize + commit

Append recovery plus commit-state recovery

Atomic visibility of committed pending streams

This article uses application-created committed streams because the operational question is whether a continuously running producer can preserve write identity across uncertain acknowledgements. That choice is not a statement that default-stream ingestion is wrong. Google’s best-practices guide explicitly says the default stream may be preferable when upstream delivery is already at-least-once or duplicates can be detected after ingestion.

The wider architecture still matters, including backpressure, source retention, table design, and monitoring, but those concerns are broader than this protocol. Refonte Learning’s article on cloud-native pipeline design is useful context for that surrounding system; it does not replace the Storage Write API’s stream and offset contract.

A committed stream does not require a batch-commit step. Finalization is optional for committed streams and closes the stream to further appends. Pending streams follow a different CreateWriteStream → AppendRows → FinalizeWriteStream → BatchCommitWriteStreams workflow. Treat those as different recovery protocols, not interchangeable final steps.

Persist the Source-to-Write Identity Before Sending

The journal is the writer’s memory after the process is gone. It must be durable enough that a restart can answer, without guessing: which source records were intended, what exact rows were serialized, where were they intended to land, and what evidence exists about the outcome? This journal is a proposed application control; Google does not create or coordinate it for you.

A compact schema can look like this:

Field

Stable across retry?

Purpose

source_partition, source_start, source_end

Yes

Reconstruct the exact source range

destination_table, stream_name

Yes

Preserve write namespace

row_offset, row_count

Yes

Preserve stream position

batch_id, payload_fingerprint

Yes

Bind retry to exact intended rows

payload_ref

Yes

Protected pointer to immutable/reproducible payload

serialization_version, schema_version

Yes

Reproduce bytes/field interpretation

owner_epoch

No; may increase on ownership transfer

Fence stale writers

status

No

PREPARED, SENT_UNKNOWN, CONFIRMED, REJECTED, CHECKPOINTED

request/response timestamps and codes

No

Observed evidence, not identity

Write the PREPARED record before calling AppendRows. The stable fields form the BigQuery batch identity for recovery. Observations may change; the intended batch may not. This is also where data contracts and schema ownership provide complementary context: a schema contract can define what fields are valid and who owns change, but it does not implement a durable append journal.

Do not put raw sensitive payloads in ordinary application logs. Store either an immutable protected object reference or enough source coordinates to reproduce the exact ordered rows under an explicit source-retention contract. A fingerprint should be computed over a defined canonical representation, such as versioned protobuf bytes in source order, rather than over whatever JSON rendering a debugger happens to emit.

Proposed pre-send checklist

  • Journal row is durably committed before network send.

  • Stream name and offset are already allocated.

  • Exact ordered row set is immutable or reproducible.

  • Fingerprint algorithm and serialization version are recorded.

  • Owner epoch is current.

  • No source checkpoint has advanced beyond this unconfirmed batch.

Count Rows, Not Requests or Bytes

Offsets count rows. Suppose synthetic batch A contains three rows and starts at offset 0. Synthetic batch B contains two rows, so its intended offset is 3. If both are accepted in sequence, the next append position is 5. Two API requests do not imply offset 2; payload bytes do not imply any offset at all. Google’s committed-stream sample advances the next offset by the number of rows in each batch.

Batch

Rows

Intended offset

Next position if accepted

A

3

0

3

B

2

3

5

Bind the Offset to the Exact Intended Batch

An offset is not a batch hash. Google’s error semantics report whether a stream position is already occupied; the documentation does not state that ALREADY_EXISTS compares your retry payload with the previously accepted payload byte-for-byte. The stronger rule, “treat ALREADY_EXISTS as success only if the journal proves this is the same intended batch,” is therefore a proposed producer invariant, not a BigQuery equality guarantee. The best-practices guide itself warns that offset errors can result from incorrect client offsets.

After a timeout, never re-read a mutable source, produce a different ordered row set, and attach the old offset. Either reproduce the exact batch or stop and reconcile.

Establish One Recoverable Owner for Each Stream

Google documents a transport boundary: an application-created stream can have only one active connection, while the default stream can have multiple connections. That is useful, but it is not the same thing as an application lease that decides which process may mutate your journal, acknowledge the source, or recover an old batch.

Use an explicit ownership protocol around each created stream. One workable proposed design is a durable lease row with a monotonically increasing owner_epoch. A process acquires the lease with compare-and-swap semantics, persists the new epoch, and includes that epoch in every journal state transition. Any update from an older epoch is rejected. The lease store must itself provide the atomic conditional update you rely on; a timestamp in memory is not fencing.

Actor state

May append?

May change journal?

Recovery action

Current owner, current epoch

Yes

Yes

Continue/retry recorded batch

Stale owner, older epoch

No

No

Close writer and stop

New owner, unresolved prior batch

Only after reading journal

Yes

Resume same stream/offset/batch

Owner cannot prove prior identity

No blind replay

Mark hold/reconcile

Escalate with evidence

Local policy example: one active application owner per stream, lease renewed before a fixed locally chosen expiry, and every mutation fenced by owner_epoch. The numeric lease duration is environment-specific; this article does not prescribe one.

On takeover, the new process must read durable state before creating network state. If the journal says stream S has batch A at offset 0 with an uncertain outcome, recovery authority is over that identity. Creating stream S2 merely because the old process died changes the idempotence namespace. Replaying the same source rows at offset 0 on S2 can create another valid append.

The same principle applies when a gRPC connection breaks. Google says connections can be closed and clients may reconnect; the Java and Go libraries can reconnect automatically. A new connection to the same created stream is not the same as a new stream.

Your failover test should therefore include a stale process waking after ownership moved. Expected result: it cannot update the journal or advance the source checkpoint, even if it still has local objects capable of attempting I/O.

Model Append Outcomes Instead of Catching Every Error the Same Way

A reliable writer does not collapse every exception into “retry.” It distinguishes what is known about acceptance. AppendRows is asynchronous, and responses on a bidirectional connection are ordered with the requests sent on that connection. Google recommends nonblocking appends for throughput. For a recovery-first reference implementation, however, start with one in-flight batch per created stream so that an unresolved response cannot be confused with several later allocations. That serialization is a proposed auditability tradeoff, not Google’s highest-throughput recommendation.

Journal state

Evidence

Safe next step

PREPARED

Durable identity, not sent

Send same identity

SENT_UNKNOWN

Request attempted; no conclusive service result

Retry same stream/offset/exact batch

CONFIRMED

Successful append result, or justified retry proves position already occupied by this intended batch

Advance local completion

REJECTED

Conclusive service response says rows not accepted

Repair/reallocate under new batch identity

CHECKPOINTED

Upstream progress durably advanced

Eligible for retention cleanup

The RPC reference says that when an AppendRowsResponse.error is present, the rows for that request were not accepted, and it separately documents row errors for corrupted rows. A transport failure with no conclusive response is different: absence of an acknowledgement does not prove absence of server acceptance. That distinction is an inference from the response contract and is the reason SENT_UNKNOWN exists.

Also distinguish “the API call returned from a local method” from “the backend append is acknowledged.” Google’s Java sample gets an ApiFuture<AppendRowsResponse> and handles completion in a callback; its lower-level examples separately write requests and consume responses. Verify the exact client-library contract you deploy before choosing the moment that moves your journal to CONFIRMED.

Recover an Uncertain Acknowledgement

Assume batch A is durably prepared for stream S, offset 0, three rows, fingerprint H(A). The request is sent, then the process loses the response and crashes. Recovery does not allocate offset 3, regenerate A, or open S2. It reuses S, offset 0, and the exact bytes or reproducible batch represented by H(A).

If the original append never reached acceptance, the retry can succeed at offset 0. If the original append was accepted, retrying the same explicit offset is idempotent and the service can report ALREADY_EXISTS. Google documents offset-specified writes as safe to retry after network errors or server unresponsiveness.

Do not advance the source checkpoint merely because a send operation completed locally. The checkpoint follows conclusive target evidence under the ordering defined later.

Interpret Offset Errors with the Journal in Hand

Google documents ALREADY_EXISTS when the supplied offset has already been written and OUT_OF_RANGE when the supplied offset is beyond the current end of the stream. Its best-practices page also warns that both can arise from wrong client offsets.

Therefore:

Service result

Journal evidence

Decision

ALREADY_EXISTS

Same stream, same offset, same exact intended batch proven

Treat retry as already applied

ALREADY_EXISTS

Batch identity differs or is missing

Hold and reconcile

OUT_OF_RANGE

Known predecessor is unresolved

Recover predecessor first

OUT_OF_RANGE

Journal says predecessor succeeded but offset arithmetic disagrees

Hold; investigate mapping

Other conclusive rejection

Rows documented as not accepted

Mark rejected; repair explicitly

Never “hunt” for an accepted offset by incrementing or decrementing until BigQuery stops objecting. That can turn an accounting defect into silent data loss or duplication.

Work Through a Synthetic Crash-Recovery Ledger

The following lab is synthetic and not executed. Use a disposable, compatible append-only table with no identity columns, and inject failures only in an approved test environment. The expected results follow the documented offset semantics; they are not presented as observed runtime output.

Assume one source partition P7. Batch A covers source positions 1000–1002 and contains three rows. Batch B covers 1003–1004 and contains two. A created committed stream S is assigned to the partition.

Time

Durable journal

Network/service event

Expected recovery meaning

T1

A = PREPARED, S, offset 0, rows 3, H(A)

None

Safe to send A

T2

A = SENT_UNKNOWN

BigQuery accepts A, response is lost

Destination may already contain A

T3

Process crashes

No local acknowledgement persisted

Must not infer rejection

T4

New fenced owner loads A

Retry S, offset 0, exact A

Expected ALREADY_EXISTS if T2 accepted

T5

A = CONFIRMED; B = PREPARED, offset 3, rows 2

Append B

Expected success at offset 3

T6

B = CONFIRMED

Source checkpoint advances through 1004

Next source position is 1005

The critical point at T4 is not the error code alone. The journal proves that the retry is for the same source range, same destination, same created stream, same row offset, same row count, same serialization version, and same fingerprint. Only then does ALREADY_EXISTS become evidence consistent with “our intended batch is already at this position.” Google’s own best-practices statement that ALREADY_EXISTS may be ignored sits beside its warning that wrong offsets also produce offset errors; the journal supplies the missing producer-side proof.

Now test the deliberately unsafe alternative. At T4, suppose the restart creates stream S2, rebuilds A from source, and writes it at offset 0. The Storage Write API sees a valid first write on a different stream. Exactly-once semantics are within a stream, so S2:0 does not inherit the occupied state of S:0. Both committed streams are visible after successful appends. This can yield duplicate destination rows even though each stream individually obeyed its offset contract.

Synthetic acceptance checklist

  • Lost response after server acceptance leaves A recoverable without a new stream.

  • Restart reads the journal before allocating any offset.

  • ALREADY_EXISTS is accepted only after exact-batch proof.

  • B remains at offset 3; it is not moved because A’s response was lost.

  • Source progress moves to 1005 only after both intended batches are confirmed.

  • Creating S2 for unresolved A is classified as a design change requiring reconciliation, not an ordinary retry.

Keep Source Checkpoints Separate from Target Acceptance

The BigQuery append and the upstream source acknowledgement are separate durability domains. Nothing in the Storage Write API documentation coordinates an external queue acknowledgement, Kafka-style source offset, application journal commit, and BigQuery append as one distributed transaction. The safe design is therefore a recovery ordering, not a claim of atomicity across systems. Google’s documentation defines when rows are appended and visible; your source defines when replay remains possible.

A proposed ordering for each source range is:

Order

Durable act

Why it precedes the next

A

Capture exact source range and immutable/reproducible batch

Establish retry identity

B

Persist destination stream, offset, row count and fingerprint

Establish target position

C

Append and obtain conclusive target evidence

Prevent checkpointing an uncertain write

D

Persist CONFIRMED locally

Survive crash before source acknowledgement

E

Advance/acknowledge source checkpoint

Release replay obligation

F

Mark CHECKPOINTED

Enable later cleanup

This is a classic “there may be a duplicate attempt across a crash, so make the target attempt idempotent” pattern. The journal bridges the uncertainty, but it does not make step D and step E atomic. If the process crashes after D but before E, the source may replay the range; the writer recognizes the existing journal identity and does not allocate a new stream position. If it crashes after E but before F, recovery must be able to inspect the source checkpoint and repair local status without replaying data.

The destination schema for this lab can include source coordinates and batch_id as ordinary columns for reconciliation. That is different from generated-key strategy. Refonte Learning’s discussion of BigQuery identity-column ingestion boundaries covers that separate concern; this lab intentionally stays on a compatible non-identity destination.

Test Crashes Around the Source Checkpoint

Run failure injection at the boundaries, not only during the network call.

Injected crash

Durable evidence after restart

Expected action

Before append

PREPARED

Send recorded batch

After possible append, before conclusive local record

SENT_UNKNOWN

Retry same identity

After CONFIRMED, before source checkpoint

Target proof survives

Advance/repair source progress, no new write

After source checkpoint, before local CHECKPOINTED

Source progress is authoritative for release

Reconcile local status; do not replay

Local policy example: require one approved test for every checkpoint boundary and every supported owner-transfer path before production scale-up. The count is a local acceptance rule, not a Google product requirement.

Define a Separate Business-Duplicate Policy

Two different source records can describe the same business event. Example: positions 1002 and 1019 may both carry order_id=O42 because an upstream producer duplicated the event. Writing each record once at distinct BigQuery offsets is fully consistent with the stream guarantee.

Question

Owner

Are repeated source positions illegal?

Source ingestion protocol

Are repeated business keys illegal?

Domain/data-product contract

Should curated tables collapse duplicates?

Transformation/consumer layer

Does BigQuery stream offset detect semantic duplicates?

No; offset guards stream position

Use event IDs, business keys, reconciliation queries, or curated-table logic according to the data contract. Do not advertise BigQuery stream offsets, or generated destination IDs, as universal business-event deduplication.

Handle Schema Rejection Without Reusing a Changed Batch Identity

Schema and validation failures are dangerous because the tempting repair is to “drop the bad row and resend the rest” under the old journal identity. That changes the batch.

The RPC reference is precise for corrupted-row failures: when a request fails for corrupted rows, no rows in that batch are appended, and row-level error information is returned so the caller can remove bad rows and retry. It also says an error response indicates the rows were not accepted.

That supports repair only after a conclusive rejection. It does not justify mutating a batch whose transport outcome is unknown.

Condition

Can payload change?

Offset consequence

Conclusive whole-batch rejection

Yes, as a new batch identity

May reuse the still-empty intended position after journal reallocation

Transport-uncertain outcome

No

Retry exact old identity or hold

Schema updated out of band

Re-serialize only under explicit new identity/version

Reconcile any later prepared offsets

One row removed from a 2-row rejected batch

Yes, new row count = 1

Every later unaccepted allocation may shift

Record both schema_version and serialization_version. Google’s best-practices guide says extra fields not present in the current table schema can produce SCHEMA_MISMATCH_EXTRA_FIELD; after a table schema update, the service detects the change after a short delay, and existing connections generally need to reconnect with the new schema, with Java JsonStreamWriter providing additional automatic behavior.

Suppose rejected batch B was two rows at offset 3, and one row is removed. The repaired batch is not B anymore: give it a new batch_id, new fingerprint, and row count 1. If a later batch C had already been prepared for offset 5, its allocation is now suspect because the new next position would be 4. In the serialized reference design there should be no later in-flight batch, which is exactly why schema repair is easier to audit.

Schema-recovery checklist: prove rejection; freeze old journal record; create replacement identity; recompute row count; invalidate affected future allocations; reconnect if required by the deployed client/schema path; only then resume.

Add Concurrency Only After the Serialized Protocol Passes

Google designed AppendRows to be asynchronous. Responses on a bidirectional connection arrive in request order, and the best-practices guide recommends sending without blocking for maximum throughput. A production writer may therefore need several in-flight appends. The mistake is adding concurrency before the recovery model can name every outstanding batch.

Start from the serialized protocol, then add a bounded pipeline in which offsets are durably allocated in order and each in-flight request has a journal record. A later response must never erase an earlier unresolved state merely because callbacks are processed quickly.

Concurrency control

Required evidence

Bounded in-flight window

Exact list of outstanding batch IDs

Ordered offset allocator

Predecessor row counts and allocations

Response handler

Request-to-journal correlation

Backpressure

Stop allocating when unresolved window is full

Restart

Recover every outstanding identity before new allocation

Local policy example: begin with one in-flight batch per created stream; increase the bound only after crash drills demonstrate deterministic recovery of the full outstanding window. The chosen bound is a deployment policy, not a universal BigQuery recommendation.

Request sizing needs the same restraint. The current BigQuery quota table lists a 20 MB maximum AppendRows request size, while the RPC reference phrases the requirement as a single request being less than 20 MB; larger requests typically return INVALID_ARGUMENT. Treat 20 MB as a hard ceiling, leave encoding headroom, and verify any lower limit imposed by the client you actually deploy.

That 20 MB figure is a service limit, not a recommended target batch size or throughput promise. Google separately recommends larger requests for efficiency, but actual throughput varies with schema, network, server load, quota, and client implementation. Benchmark representative rows and fault behavior in your environment rather than turning documentation examples into an SLO.

For broader platform context around BigQuery and streaming services, GCP data engineering foundations can orient readers to the ecosystem; current limits and recovery behavior should still come from the primary Google documentation above.

Evaluate Pending Streams as a Different Commit Protocol

Pending streams are useful when the unit you need to expose is an atomic batch across one or more streams, but they add a second recovery problem: commit state. Google documents the sequence as create pending streams, append, finalize each stream, then call BatchCommitWriteStreams; data remains invisible until commit, and the commit is atomic. A failed commit can be retried, while per-stream problems are returned in stream_errors.

Pending-stream evidence

Meaning

Recovery

Append accepted, stream not finalized

Buffered write state

Resume/complete appends using preserved identity

Stream finalized

No more rows may be appended

Preserve final row evidence

Commit response successful

Rows become readable

Record commit evidence

Commit outcome uncertain

Do not replay rows to new streams

Reconcile/retry commit

stream_errors present

Commit not clean for all intended streams

Hold and inspect each error

This is not indefinite staging. The current guide says that after finalization and before commit, data can remain buffered for up to four hours, and pending streams must be committed within 24 hours. The quotas page also lists pending-stream byte limits.

The current gRPC quotas page allows up to 10,000 streams per table in each batch-commit call. That is a documented limit, not a target fan-out.

The important recovery distinction is action identity. Retrying a commit for the same finalized pending streams is not the same operation as creating replacement streams and replaying their source ranges. The former preserves the write set; the latter creates new append identities and must be reconciled as such.

Collect Evidence That Can Reconcile the Source and Destination

API success counters are necessary but insufficient. The review question is coverage: can you account for every intended source range and explain every unresolved write identity?

Use metrics whose denominators are explicit.

Metric

Example definition

What it detects

Uncertain-append age

Age of oldest SENT_UNKNOWN journal record

Stuck ambiguity

Confirmation coverage

Confirmed source rows / intended source rows for window

Missing target evidence

Checkpoint coverage

Checkpointed source ranges / confirmed source ranges

Source progress lag

Offset-conflict rate

Offset errors / offset-bearing append attempts

Mapping defects or retries

Rejected-batch rate

Conclusively rejected batches / append attempts

Schema/data quality failures

Orphaned stream count

Created streams with no live owner and unresolved journal state

Ownership/recovery debt

For destination reconciliation, include ordinary source coordinates or a stable event/batch identifier in the target when policy permits. Then compare the expected source range with query results, rather than merely counting successful requests. Google also exposes Storage Write API monitoring through INFORMATION_SCHEMA.WRITE_API_TIMELINE and Cloud Monitoring, but its overview warns that some console AppendRows dashboards reflect connection-level rather than request-level behavior.

Broader data lake and warehouse governance concerns, including retention, access, lifecycle, and stewardship, matter around this evidence plane. Keep the write journal specifically engineered for recovery: protect payload references, minimize sensitive data, define retention after checkpoint completion, and record enough history to explain owner changes and batch replacement.

Evidence gate: a dashboard saying “99.9% requests succeeded” is not proof that every intended event arrived once. Require source-range coverage, unresolved-write age, and an auditable explanation for every changed stream or batch identity.

Run a Controlled Adoption and Failure-Drill Plan

Adopt the protocol by proving recovery before optimizing throughput. The phases below are a proposed local rollout model, not a Google-prescribed deployment sequence.

Phase

Owner

Evidence artifact

Hold trigger

Synthetic fixture

Ingestion developer

Two-batch ledger and expected offset outcomes

Any retry changes stream/offset/batch identity

Isolated end-to-end source

Platform owner

Source-to-journal-to-table reconciliation

Unexplained source-range gap

Failure injection

Reliability reviewer

Crash matrix, owner-transfer results

Stale owner can mutate state

Small production cohort

Service owner

Coverage and uncertainty metrics

Aged unknowns or unreconciled duplicates

Reviewed scale-up

Joint owner/reviewer

Signed acceptance record

New concurrency path lacks recovery proof

Local policy example: scale only when the cohort has zero unexplained uncertain appends at the review point, every injected checkpoint-boundary crash has a documented recovery result, and ownership fencing has rejected a stale writer at least once in the test fixture. These are proposed acceptance gates; choose counts and observation windows appropriate to your system.

Inject at least these classes in non-production first: connection loss before response, process death after backend acceptance, crash after local confirmation but before source checkpoint, stale-owner wake-up, schema rejection, and an intentionally wrong offset. Expected results should be written before the test so the drill distinguishes validation from storytelling.

Do not make “fall back to the default stream” an automatic error path. That silently changes the delivery contract from offset-controlled application-created writes to at-least-once default-stream writes. If an incident requires a mode change, stop, record the boundary, reconcile the affected source range, and obtain the operational approval appropriate to your platform.

Likewise, if an old stream cannot be recovered, do not hide the discontinuity. Create a new stream only as an explicit new epoch, with a documented cutover source position and a reconciliation decision for everything before it.

Build the Data Engineering Foundations for Reliable Ingestion

Storage Write API recovery sits on fundamentals that are broader than one client library: durable pipeline state, streaming and batch ingestion, transformation boundaries, and governance. As of September 17, 2026, Refonte Learning’s live Data Engineering Program lists a three-month period, 12–14 hours per week, and areas including data pipelining, real-time processing, ingesting streaming and batch data, transforming data, and data governance and compliance controls.

Those foundations map naturally to an independent exercise like this one: model a source range, persist a recovery ledger, append to a disposable destination, inject an uncertain acknowledgement, and prove that restart preserves the same stream/offset/batch identity.

Foundation

Reliability exercise

Data pipelining

Model state transitions instead of a linear “send then ack” script

Real-time processing

Separate asynchronous transport from durable acceptance

Streaming and batch ingestion

Compare committed and pending protocols

Transforming data

Version serialization and schema-dependent batch identity

Governance controls

Assign ownership, evidence retention, and approval gates

The useful learning target is not memorizing an error code. It is being able to state what survived a crash, who owns the next decision, and which evidence makes a retry safe.

Answer the Exactly-Once Questions Precisely

The phrase “BigQuery Storage Write API exactly once” should trigger boundary questions, not end them.

Does the default stream deduplicate events?
Do not describe it that way. Google documents the default stream as at-least-once and disallows explicit offsets on it. If your workload accepts that contract and deduplicates elsewhere, the default stream can be the simpler and more scalable choice.

Can every ALREADY_EXISTS be ignored?
No. Google says it is typically a retry condition and its best-practices guide says it can be ignored, but the same guide warns that offset errors can also result from wrong client offsets. Operationally, ignore it only when your durable journal proves the same destination, created stream, offset, source range, row count, serialization version, and exact intended batch.

Does a new stream preserve old offsets?
No. Offsets are positions within a stream. Replaying the same rows at offset 0 of a newly created stream is a new write identity and can create duplicate destination rows.

Is source acknowledgement atomic with the append?
Not in the protocol described here. The Storage Write API governs the BigQuery write. Your source checkpoint and application journal require their own durability and recovery ordering; this article makes no distributed-transaction claim.

Proceed / hold / reconcile checklist

Decision

Required evidence

Proceed

Same destination, same stream, intended offset, exact batch fingerprint, valid owner epoch, known predecessor state

Hold

Missing payload proof, conflicting row count, ambiguous owner, unexplained offset error, uncertain schema mutation

Reconcile

New stream introduced, batch contents changed, source checkpoint moved unexpectedly, business duplicates detected

The practical rule is intentionally strict: preserve write identity before you preserve throughput. A BigQuery stream offset can make a retry idempotent inside its documented boundary. Your job is to keep the source range, stream, offset, exact batch, acknowledgement evidence, and durable checkpoint aligned long enough for that boundary to remain meaningful.