Delivery Guarantees#
This page compares what a completed Flink checkpoint means for each sink and what can happen when Flink restores an earlier checkpoint.
The short version is that flushing before a checkpoint makes data durable, but does not by itself prevent a restored job from writing the same record again. BigQuery has checkpoint-coordinated exactly-once write methods. Cloud Tasks also has opt-in, experimental checkpointed task creation within its documented retention and recovery scope; it keeps up with the at-least-once path at the measured rates and adds most of a checkpoint interval to task visibility latency. Some other sinks can make a replay harmless when the record supplies a stable identity, but that is not the same contract as a general-purpose exactly-once sink.
Support boundary#
These connectors rely on the behavior specified in Google’s official documentation for the API and configuration in use. The project is responsible for implementing and testing the connector’s documented behavior within that service specification, including request construction, retries, error handling, checkpointing and recovery. Service limits and guarantees remain those published by Google; this project does not extend them. Follow the official Google documentation linked in each connector section for service semantics and operational limits.
A connector guarantee states its supported configuration and recovery conditions. Behavior outside that scope is not an additional service guarantee, and an ambiguous service response must be handled according to the documented API semantics. Tests verify the implementation against those semantics; successful samples do not establish a stronger service guarantee.
Terms used here#
These terms describe different properties and should not be used interchangeably.
Checkpoint-durable means that a completed checkpoint covers every preceding record the sink did not deliberately skip or route to a dropping failure policy. An eager sink waits for destination acknowledgement before allowing the checkpoint barrier to pass. A staged sink can instead checkpoint pending writes and apply them after completion; checkpoint completion alone then does not mean the destination has received them.
At-least-once means that recovery does not lose a checkpoint-covered record, but may apply a record again when the source and sink restore an earlier checkpoint.
Idempotent or effectively-once effect means that the repeated request is still possible, but the chosen record identity and destination operation make the observable result the same as one write. This property belongs to that operation and schema, not automatically to every use of the connector.
Exactly-once service write means that the sink protocol prevents one logical input record from creating more than one visible destination effect within its documented scope and recovery window.
End-to-end exactly-once also includes the source and everything that consumes the destination. It requires checkpointing, an exactly-once-capable source, preservation of checkpoint state across deployments, and failure policies that do not drop records. A sink cannot provide this property on its own.
Two-phase commit is one way to obtain an exactly-once service write, not the definition of it. A Flink committer only helps when the destination exposes a transaction, an invisible prepared write, or an idempotent commit token that survives recovery. Adding a committer around an eager non-transactional API does not undo a write made before failure.
Current sink matrix#
The eager sinks below call flush() at a checkpoint and wait for outstanding service requests; staged modes transfer work to a committer.
Checkpointing must be enabled in a streaming job for that durability boundary to run.
| Connector and method | Current checkpoint protocol | Effect of replay | Current guarantee |
|---|---|---|---|
BigQuery STORAGE_API_EXACTLY_ONCE | Writer state records BUFFERED streams and explicit append offsets; a committer calls FlushRows after checkpoint completion | An uncommitted tail stays invisible; restored committables can be flushed again idempotently | Exactly-once service writes for fixed or dynamic destinations, subject to the documented stream lifetime and preserved Flink state |
BigQuery FILE_LOADS | Checkpointed staged objects become deterministic load and copy jobs through a committer; batch WRITE_TRUNCATE_DATA overflow adds a terminal query job | Restored jobs reuse deterministic object and job identities | Exactly-once service writes for batch and checkpoint-triggered streaming loads |
BigQuery STORAGE_API_AT_LEAST_ONCE | Stateless writer flushes the default stream before the barrier | The same row may be appended again | At-least-once |
| Pub/Sub sink | Stateless writer flushes SDK publishers and waits for publish acknowledgements | A replay is a new publish with a new service-assigned message ID | At-least-once; no publisher-side idempotent mode |
Cloud Tasks AT_LEAST_ONCE | Stateless writer waits for every CreateTask request | Unnamed tasks can be created again; named tasks return ALREADY_EXISTS while the service remembers the name | At-least-once by default; bounded effectively-once task creation with taskIdExtractor(...) or Table API task-id metadata |
Cloud Tasks DataStream/Table EXACTLY_ONCE | Stateless writer stages immutable named envelopes in committer state; bounded CreateTask calls start after checkpoint completion | Restore reuses original queue, name and Task bytes; every send enforces the persisted recovery deadline | Exactly-once task creation per staged envelope within the documented scope and recovery window; no exactly-once handler execution; experimental after #1246’s assessment |
| Bigtable default bulk sink | Stateless writer sends buffered MutateRows entries and waits for every entry | A regenerated setCell timestamp can add a version; a stable timestamp targets the same version. Aggregate Sum inputs or states can contribute again even at the same timestamp | At-least-once; replay safety depends on the mutation shape |
Bigtable DataStream/Table EXACTLY_ONCE | Stateless writer transfers immutable envelopes to checkpointed committer state; conditional writes start after completion | Restore reuses the original request and marker identity; retained markers suppress repeated effects | Experimental checkpoint-owned row effects within the documented recovery scope; production-service recovery acceptance was recorded on 2026-09-14 under #1319. A row becomes readable one checkpoint interval later at best, plus the commit drain, which is why the Stage 2 assessment declined the performance gate on 2026-09-21 under #1327 and claims no supported workload; at the default commit concurrency its visibility p95 was 3.5 to 7.6 seconds at a one-second interval on distinct rows on a four-processor host (#1464) |
| Bigtable single-row request runtime | Sink surface: stateless writer waits for every CheckAndMutateRow or ReadModifyWriteRow request. Async surface: Flink’s async operator checkpoints un-emitted inputs and replays them | A replayed conditional write re-evaluates its predicate against the state the first attempt left; a replayed read-modify-write applies its increment or append again | At-least-once; neither RPC is idempotent, and the runtime retries neither. Conditional and read-modify-write sink and async entry points are available |
| Spanner sink | Stateless writer consumes BatchWrite responses before the barrier | Spanner documents no replay protection; the selected mutation operation may nevertheless be idempotent | At-least-once; insertOrUpdate, replace, update, and delete effects can be idempotent within their operation constraints |
Follow the connector-specific delivery section for failure-handler behavior, state-loss hazards, and operation-specific caveats.
Write and key-collision semantics#
Flink’s changelog contract and the destination’s key-collision behavior are separate choices.
Declaring a SQL PRIMARY KEY tells the planner how rows relate; it does not uniformly ask a
destination to reject an existing key.
Likewise, an insert-only changelog says which row kinds reach a sink, not whether the service
implements insert-if-absent.
| Connector API | How the identity and write shape are selected | Effect of submitting the identity again | Replay boundary |
|---|---|---|---|
| Spanner Table API | A declared PRIMARY KEY selects insertOrUpdate and key deletes; without one the sink accepts inserts only and uses insert | Replaying one upsert or delete repeats its effect idempotently; insert can return ALREADY_EXISTS | At-least-once submission; BatchWrite does not preserve the order of successive same-key mutation groups |
| Spanner DataStream API | The serializer chooses the key and Mutation operation | Replaying one insertOrUpdate, replace, update, or delete can be idempotent within its operation constraints; insert can return ALREADY_EXISTS | At-least-once submission; replay safety belongs to each mutation, and BatchWrite does not preserve same-key group order |
Bigtable Table API, at-least-once upsert (default) | The one atomic column is always the row key; a declared PRIMARY KEY improves planner handling, while sink.insert-only-input-mode changes only the accepted changelog | The same row key is physically upserted; a stable explicit cell timestamp targets the same version, while an omitted timestamp uses the writer’s wall clock and a Flink replay can create another version | At-least-once submission; this mode performs no atomic existence check |
Bigtable Table API, at-least-once keep-latest | The ordinary row-key/family schema with sink.write-mode = keep-latest; each written cell is deleted across all versions and set again in one row entry | Replay leaves one replacement per targeted cell but can change its timestamp and overwrite newer values; omitted families and undeclared cells remain untouched | At-least-once submission; atomic replacement does not order separate same-row entries or arbitrate event timestamps |
Bigtable Table API, insert-if-absent | The ordinary row-key/family schema with sink.write-mode = insert-if-absent; only INSERT input is accepted | Any stored cell, including undeclared families, selects an empty branch; an absent row receives the input cells atomically | At-least-once submission; an initial insert followed by replay may be ignored or fail according to the empty-branch policy |
Bigtable Table API, conditional | A required row-key-column binds the request identity; the DDL fixes a named predicate and two ordered mutation branches; INSERT-only input without PRIMARY KEY or metadata | Repeating the input reevaluates the predicate and may choose a different branch; both branches are validated before submission | At-least-once submission; a lost acknowledgement can repeat an applied command, and fail-on-empty can then fail replay; no ordering across separate requests |
Bigtable Table API, append or increment | The ordinary row-key/family schema with one operation selected by sink.write-mode; only INSERT input is accepted | Every repeated input submits another append or increment against the latest cell values; timestamps are service-assigned | At-least-once submission without deduplication; replay can apply an already committed append or increment again |
Bigtable Table API, at-least-once aggregate | INSERT-only integer contributions through AddToCell; repeated primary keys remain contributions | A stable timestamp selects an aggregate cell, but replay can add SUM inputs again. MIN, MAX and HLL are unchanged by repeating the same input at the same timestamp without intervening deletion or GC; writer-clock timestamps can create new versions | At-least-once submission; no durable input identity or retraction of earlier contributions |
| Bigtable DataStream API, bulk sink | The serializer chooses the RowMutationEntry row key, mutations, qualifiers, and cell timestamps | Replaying setCell with a stable explicit timestamp targets the same version; a regenerated timestamp can create another version. Aggregate Sum inputs or states can contribute again even at the same timestamp | At-least-once submission; replay safety belongs to the mutation shape |
Bigtable DataStream/Table API, opt-in EXACTLY_ONCE | One random identity per immutable staged row envelope, retained in Flink state | A retained same-row marker makes replay of that envelope a no-op; distinct inputs still have distinct identities | Checkpoint-owned row effects with preserved state and markers; no cross-row visibility or ordering guarantee; the Stage 2 assessment declined the performance gate on 2026-09-21 under #1327 |
| Bigtable single-row request runtime | The request names the row key; CheckAndMutateRow carries a predicate and an ordered mutation list for each branch, ReadModifyWriteRow an ordered list of append and increment rules | Repeating a conditional write re-runs the predicate, so a marker it writes can make the second attempt a no-op; repeating a read-modify-write appends or increments again, with no timestamp to target | At-least-once submission, one attempt per request; a conditional write is the only shape here that can be made replay-safe, and only by what the application puts in the row |
Cloud Tasks Table API, default at-least-once | The sink accepts inserts only; writable task-id metadata optionally selects a stable task identity | A remembered ID returns ALREADY_EXISTS; the existing task is neither compared nor updated | At-least-once submission; bounded effectively-once task creation when task-id is selected |
Cloud Tasks DataStream API, default AT_LEAST_ONCE | taskIdExtractor(...) optionally selects a stable task identity; otherwise Cloud Tasks assigns one | A remembered extracted ID returns ALREADY_EXISTS; an unnamed replay creates another task | At-least-once submission; bounded effectively-once task creation with an extractor |
Cloud Tasks DataStream/Table API, opt-in EXACTLY_ONCE | Stages an immutable named envelope per accepted record; a stable application key is optional | A remembered name returns ALREADY_EXISTS; retries and restore reuse the checkpointed name and bytes | Exactly-once task creation per staged envelope within the documented scope and recovery window; handler execution remains at-least-once |
These key choices alone do not establish a checkpoint-coordinated protocol. Cloud Tasks separately offers the checkpointed mode described below. The eager paths make a particular destination effect replay-safe only within the identity, operation, and retention constraints in the table.
BigQuery and the Storage Write API example#
The BigQuery Storage Write API documentation demonstrates exactly-once writes with an application-created COMMITTED stream and explicit append offsets. The connector uses the same offset-based replay protection, but not that exact stream lifecycle.
STORAGE_API_EXACTLY_ONCE creates one BUFFERED stream per active destination in each writer
subtask and reuses it across checkpoints.
Appends stay invisible until a checkpoint completes, when the Flink committer calls FlushRows up
to the checkpointed offset.
This visibility boundary is necessary because a COMMITTED-stream append would already be visible if
Flink later rolled back to an earlier checkpoint.
The writer persists each active destination’s stream name and next append offset in Flink state.
prepareCommit() emits the highest offset that the checkpoint may make visible, and FlushRows is
safe to repeat after recovery.
This is a Flink two-phase commit built from BigQuery’s buffered-stream protocol, rather than a copy
of the committed-stream sample.
The method still depends on preserved Flink state. Restarting the job without its state while buffered rows or committables are pending can skip up to one checkpoint of data, so deployment tooling must use a savepoint or retained checkpoint rather than a stateless restart.
Why the other services need different mechanisms#
Pub/Sub#
Pub/Sub exactly-once delivery is a subscription-side receive and acknowledgement feature. It does not deduplicate calls made by a publisher.
The service assigns messageId
after accepting a publish, and the publisher cannot supply that ID as an idempotency key.
Pub/Sub also exposes no publish transaction that a Flink committer can prepare and commit.
The connector therefore cannot turn a repeated publish into one physical Pub/Sub message.
A producer-assigned event ID in an attribute can let a downstream consumer deduplicate effects. That is an end-to-end application protocol and the topic may still contain duplicate messages. A transactional outbox can provide a stronger architecture when the source state and outbox share a database transaction, but it is not a connector-only Pub/Sub guarantee.
Cloud Tasks#
Cloud Tasks already exposes the useful service primitive: a caller-chosen task name is rejected with
ALREADY_EXISTS while that name remains in the service’s
deduplication window.
The sink’s taskIdExtractor(...) hashes a stable application key into such a name and treats that
response as success.
The default eager mode uses this mechanism without a Flink committer because repeating the same named creation is idempotent during the window.
It is deliberately described as bounded effectively-once task creation.
Google’s v2beta3 Queue reference documents name protection after deletion or execution for the configured tombstoneTtl.
The sink creates tasks through v2; this field belongs to v2beta3 queue administration and is not a sink option or a field on the v2 Queue resource.
Consult that field and the v2 task-creation reference for their respective configuration and name-reuse semantics.
The default eager mode does not verify queue retention or enforce a bounded recovery protocol.
Once the service releases a name, replay can create another task.
The DataStream and Table EXACTLY_ONCE mode instead stages immutable named tasks and creates them only after their owning checkpoint completes.
Names are hashes of stable application keys when configured, or persisted random identities otherwise.
Restore and rescale reuse checkpointed bytes without rerunning user code, and each attempt checks a durable deadline derived from staging time, retention, clock skew and request timeout.
The committer verifies the fixed queue’s tombstoneTtl by default and fails before sending expired envelopes.
Its explicit expiry overrides accept loss or duplicate risk outside the guarantee.
See checkpointed creation for retained-checkpoint and finite-restart prerequisites, state-loss hazards, heap/checkpoint sizing and the recovery runbook.
Tasks become visible incrementally after completion; old in-flight service effects and a stop-with-savepoint that never reaches FINISHED remain explicit exclusions.
The Table API selects the same protocol with sink.delivery-guarantee = 'exactly-once' and optional task-id metadata.
Cloud Tasks delivers the task handler at least once even when task creation was deduplicated. The handler must therefore be idempotent or maintain its own durable event ledger. Named task creation also performs an extra lookup and Google documents significantly increased latency, so its practical recommendation depends on the measured workload.
Bigtable#
The default bulk sink can make an individual setCell effect idempotent when the serializer writes the same value with a stable explicit timestamp.
That does not cover a writer-clock timestamp, arbitrary mutation shapes, or two legitimate events
that happen to target the same cell version.
That idempotence is a property of Bigtable’s storage model, not an exactly-once sink protocol.
That bulk sink has no committer, no writer state, and no step that makes a replayed record invisible or
rejected; a replayed setCell carrying the same row key, family, qualifier, and timestamp
overwrites the same cell version, and the overwrite is what makes the duplicate disappear.
The Bigtable sink of google/flink-connector-gcp
calls this same effect “Exactly Once out of the box”: its writer flushes at the checkpoint barrier
with no committer, and three of its four built-in serializers stamp each cell with the Flink record
timestamp when the record carries a positive one, so the two connectors offer the same mechanism
under different names.
Aggregate addToCell and mergeToCell mutations have a different replay effect.
Their timestamp identifies the aggregate cell, so a stable timestamp targets the same cell but does not identify or deduplicate an input.
An Int64 Sum can count the input or accumulated state again after replay.
Regenerating the timestamp can target another cell version instead.
An upstream Flink aggregation emitting ordinary upserts remains subject to same-key ordering whenever it emits repeated updates; aggregation removes that collision only if the entire write emits at most one mutation per key.
An immediate keep-latest mutation deletes a column’s versions and then sets its replacement inside one atomic RowMutationEntry.
Separate entries can execute in any order, including for the same row, and replaying an older replacement can delete a newer value.
Neither a batch size of one nor maxVersions(1) GC establishes application order or an immediate ordered-upsert guarantee.
See the aggregate update and column replacement examples, and ADR-0093.
A caller-managed eager marker protocol can protect effects contained in one row.
CheckAndMutateRow can test for an
event marker and, only when it is absent, atomically write both the data mutations and the marker in
that row.
Recovery can submit the same request again and observe the marker without repeating the effect.
Such a caller-managed protocol requires all of the following:
- a stable event ID supplied by the application;
- the marker and every protected mutation in the same row;
- a garbage-collection policy that retains the marker longer than the maximum replay horizon;
- single-cluster routing, which Bigtable requires for single-row transactions; and
- a serializer or request API different from the current
RowMutationEntry, whose public surface does not expose its mutations for wrapping in a conditional request.
It cannot protect mutations spanning rows or tables.
It also replaces the current bulk MutateRows path with one conditional request per row, so
performance is a first-order part of the support decision.
The runtime behind the last of those requirements now exists: the
single-row request runtime
sends CheckAndMutateRow and ReadModifyWriteRow as one request per row, with one attempt each
and no retry.
The public conditional request API can express the predicate and mutations for a same-row marker.
The public read-modify-write sink and async helper apply ordered append and increment rules; they do not implement a marker protocol.
The general conditional API leaves marker identity, retention and protected mutations to callers.
The separate staged mode below owns its envelope identities.
The runtime settles the replay boundary underneath that protocol. On its sink surface a completed checkpoint means every request
up to the barrier was answered. On its async surface — a RichAsyncFunction under
AsyncDataStream — the guarantee is Flink’s: the async operator checkpoints every input whose
result it has not yet emitted and replays those after a restore, so a completed checkpoint means
emitted or replayed, never applied, and a replayed ReadModifyWriteRow applies its increment or
append again. Both are at-least-once, and a failure that ends a request before the service answers
(DEADLINE_EXCEEDED, UNAVAILABLE, ABORTED, CANCELLED) fails the job saying so rather than
retrying a request the service may already have applied.
The experimental committer-based mode (#1211) is defined by
ADR-0163.
It stages immutable mutation envelopes in Flink checkpoint state and applies each through
CheckAndMutateRow after completion, guarded by its own retained marker in the target row.
Completion authorizes individual row commits; it does not make all rows visible atomically or
roll back later committed data when an older snapshot is selected.
The design requires a reserved family without automatic marker deletion and preserved Flink state;
its savepoint limitations, storage cost and required service evaluation are recorded in the ADR.
The DataStream builder and compatible Table write modes expose EXACTLY_ONCE as experimental development functionality.
ADR-0166 allows implementation before evaluation; production service recovery acceptance was recorded on 2026-09-14 under #1319.
The guarantee costs visibility latency, and in the assessment most of it lay beyond the checkpoint wait.
A staged mutation reaches Bigtable only after the checkpoint that owns it completes, so a row becomes readable one checkpoint interval later at best; that part is the guarantee and no implementation removes it.
The rest is the drain of the conditional commits the completion releases.
At the default commit concurrency, with 1 KiB rows on distinct keys and a one-second checkpoint interval on a four-processor task manager host, the Stage 2 assessment measured a staged visibility p95 of 3.5 to 6.6 seconds with one subtask and 7.2 to 7.6 seconds with four, against 23 to 61 milliseconds for the default bulk sink (#1464).
Each staged write is its own CheckAndMutateRow, because Bigtable has no conditional write that covers several rows, so the mode writes fewer rows per second per processor than the batched at-least-once sink; that gap is part of the design.
Writes to a single row are bounded by Bigtable at about 1,600 to 2,200 per second, so a workload that sends a share of its writes to one row drains at most about that rate divided by the share.
The staged mode configuration gives the figures for a larger host and what is known about the drain.
Choose this mode when a replayed row effect is worse than a delayed one, and size the checkpoint interval knowing it sets the floor.
The same staging is why a writer’s retained work grows with payload size and checkpoint interval: everything admitted since the last checkpoint is held, and the writer fails the interval rather than applying backpressure once its configured capacity is reached. The assessment declined the ADR-0104 performance gate on 2026-09-21 under #1327 on visibility latency, so no supported workload is claimed for this mode and it stays experimental. That gate compares against the eager bulk sink, so its ratio combines the unavoidable checkpoint wait with the current drain and measures neither alone. See the staged mode configuration for its deployment and recovery requirements.
Spanner#
Spanner BatchWrite is optimized for
high-throughput writes without a preceding read, and explicitly provides no replay protection.
The current connector keeps one mutation in each reported group so that one bad record can be routed
without failing the other groups.
A stable event ID, a durable ledger row, and the record’s data mutations can instead be committed in one short read-write transaction in the same database. The transaction reads the ledger marker and applies both the data and new marker only when it is absent. This can protect non-idempotent database effects and remains safe when Flink submits the event again.
The transaction must not remain open across a Flink checkpoint. Spanner may abort idle read-write transactions, and a checkpoint has no atomic relationship with the source’s offset commit. The safe design is an eager, retryable service transaction whose stable event ID makes the whole operation idempotent.
This mode would require a stable event ID, a ledger in the same database, ledger retention longer than the replay horizon, and keys distributed well enough to avoid hotspots. Batching several events in one transaction can amortize the read and commit, but one poison record then fails that whole transaction and changes the current per-record failure-routing behavior. That API and isolation policy belong in a connector-specific design ADR if performance justifies an implementation.
Performance decision rule#
Correctness is a prerequisite for every candidate. A mode that produces duplicate protected effects during replay is rejected regardless of speed.
Candidate and current paths are compared in the same region with the same service capacity, payload, key distribution, client concurrency, Flink parallelism, and checkpoint configuration. Every reported cell uses a warm-up and three measured repetitions. More than 10% run-to-run throughput variation is inconclusive and must be repeated or reported as such.
The support gates are:
| Outcome | Throughput against the current path | Candidate p95 latency against the current path |
|---|---|---|
| General support | at least 70% | no more than 2x |
| Constrained opt-in | at least 25% | no more than 4x |
| Decline | below 25% | or above 4x |
Stage 1 screens the service primitive with a 1 KiB payload, evenly distributed keys, bounded asynchronous concurrency, a 10% replay arm, and a deliberately serialized control that verifies the harness can detect a known regression.
Candidates other than Cloud Tasks require a Stage 1 pass before connector-level Stage 2. For Cloud Tasks, ADR-0162 permits implementation before final performance acceptance; #1246 compared unnamed, named and staged paths: the staged path kept up at every measured rate with most of a checkpoint interval added to its p95, and ships as experimental. That mode is judged on the throughput gate only: its latency follows the checkpoint interval by construction, so ADR-0162 does not apply the p95 gate to it. Stage 2 adds 64 KiB payloads, a hot-key distribution, concurrency and parallelism of 1, 4, and 16, and checkpoint intervals of 1, 10, and 60 seconds. The 64 KiB ceiling leaves room for Cloud Tasks metadata under its 100 KB task limit and keeps the cross-service comparison consistent.
Evaluation status#
Stage 1 ran against the real services on 2026-08-13 with the repository’s pinned Google Cloud clients; the Bigtable candidate ran again on 2026-09-05 with evenly distributed keys, and once more the same day under an amended protocol with every repetition in its own JVM. The Cloud Tasks task-identity candidate ran again on 2026-09-06. These results measure the service primitives, not end-to-end Flink jobs. The Cloud Tasks staged runtime ships as experimental after #1246’s assessment of the implemented mode; that assessment does not turn the primitive result above into a pass. The Stage 2 assessment declined the experimental Bigtable mode on 2026-09-21 under #1327, on the visibility latency its guarantee requires rather than on throughput; the mode is retained as experimental development functionality. Apart from the Cloud Tasks checkpointed-creation work in #1238, no non-BigQuery exactly-once implementation or additional performance stage is planned without a concrete non-idempotent requirement that the existing write shapes cannot satisfy.
| Candidate | Stage 1 result | Current decision |
|---|---|---|
| Bigtable same-row conditional marker | Passed on 2026-09-05 under the amended protocol: 146.7% of baseline throughput at 0.65x baseline p95 with run-to-run ranges of at most 2.4%, after the same-day repeat had exceeded the 10% limit twice at 110.0% and 100.4% | The eager marker mode is not built; the conditional write is the commit path of the experimental committer-based mode under #1211; service recovery acceptance was recorded under #1319 on 2026-09-14; Stage 2 declined the connector-level gate on 2026-09-21 under #1327 |
| Spanner 100-record ledger transaction | Inconclusive: observed 44.6% of baseline throughput and 3.12x baseline p95, but keys were increasing rather than evenly distributed | Keep the existing mutation choices; reopen measurement only for a concrete non-idempotent database effect |
| Cloud Tasks task identity | All four planned comparisons in the 2026-09-06 repeat were inconclusive: the one-channel replay warm-up stopped before required repetitions and controls finished; the eight-channel configuration never ran | The existing bounded task-creation behavior remains available. The checkpointed-creation protocol for #1238 is defined in ADR-0158; ADR-0162 permits implementation while preserving the inconclusive result. After #1245’s recovery acceptance and #1246’s assessment, the mode ships as experimental. |
| Pub/Sub publisher | No candidate because the service exposes no publisher idempotency key or publish transaction | No connector-only implementation is planned |
The raw repetitions, replay checks, declined alternatives, and cleanup evidence are in ADR-0104, issue #591, and, for the 2026-09-05 Bigtable runs, #1208 and #1210. The Cloud Tasks repeat record preserves the three completed measured observations, the stopped replay warm-up and verified queue cleanup. Two concurrent requests returned successful responses with the same task name, violating that experiment’s one-success/one-collision oracle; this does not establish two persisted tasks or Flink recovery behavior. The observed admission-limiter waiting also excludes a capacity gate pass under the preregistered rule. The post-run local calibration confirmed a pacing limitation without network requests and checked a wait repair; it does not change the real-service result. The current support decision is tracked by #596.