Idempotent Reruns: Advanced
What you will be able to do
Idempotency in Streaming Is Harder
Recognize why streaming has no clean partition boundary and identify which streaming patterns are naturally idempotent versus which require explicit engineering.
Why the Partition Trick Does Not Apply
| Property | Batch | Streaming |
|---|---|---|
| Boundary of one run | A clean partition (date, hour, fifteen-minute window) | No clean boundary; processing is continuous |
| Atomic write per run | INSERT OVERWRITE PARTITION; CREATE OR REPLACE | Each event write is independent; partition-level atomicity does not exist |
| Retry semantics | Rerun the partition; previous output replaced | Reprocess from an offset; previous output may already be downstream |
| Backfill | Loop over date partitions; each replaces its slice | Replay from a known offset; downstream must absorb duplicates |
The At-Least-Once Default
Three Sources of Streaming Duplicates
- ▸Producer retries: a producer that does not get an ack republishes the same event
- ▸Consumer crashes between processing and offset commit
- ▸Consumer group rebalances: partition ownership shifts mid-batch and the new owner starts from the last committed offset
The Boundary Problem
| Boundary | Engine Guarantee | End-to-End Guarantee |
|---|---|---|
| Flink internal state -> Flink internal state | Exactly-once via checkpoints | Exactly-once (single engine) |
| Flink -> Kafka sink | Exactly-once via two-phase commit (when configured) | Exactly-once if downstream consumer reads the committed offsets |
| Flink -> Postgres | Idempotent UPSERT or two-phase commit | Exactly-once only if the sink itself is idempotent |
| Flink -> external HTTP API | Engine cannot offer exactly-once; the API is unaware of the transaction | At-least-once at best; the API must be idempotent on its side |
Where Streaming Idempotency Is Easier
- Stateless filter, project, enrich operations
- Aggregations with checkpointed state and idempotent sink
- UPSERT-friendly destination keyed by event ID
- Append-only log destination with deduplication on read
- Stateful aggregations writing to a non-idempotent sink
- Multi-system transactions (database AND queue AND external API)
- Side effects with no idempotency token (sending email, charging a card)
- Joins across two streams with different progress rates
Exactly-Once vs Effectively-Once
Distinguish exactly-once from effectively-once, audit a vendor claim against the actual deployment, and choose the right pattern for the system in question.
Three Delivery Guarantees
| Guarantee | Meaning | Typical Where |
|---|---|---|
| At-most-once | Each event is delivered zero or one times | Fire-and-forget logging; metrics where some loss is tolerable |
| At-least-once | Each event is delivered one or more times | Default for Kafka, Kinesis, Pub/Sub; strong guarantee that no event is lost |
| Exactly-once | Each event is processed once and only once at the boundary in question | Achievable inside a single engine; harder across boundaries |
What Exactly-Once Actually Guarantees
What Effectively-Once Means
Reading Vendor Claims
- ▸Across which boundaries does the guarantee hold?
- ▸What sinks are supported, and is the production sink one of them?
- ▸What configuration is required to enable the guarantee, and is it on?
- ▸What is the latency cost (two-phase commit adds 100ms or more per checkpoint)?
- ▸What is the failure mode if the guarantee is violated; is it visible or silent?
When Effectively-Once Is the Right Choice
- Engine-level guarantee tied to specific connectors
- 100ms or more added latency per checkpoint
- Configuration is fragile; downgrades are possible without alerts
- Bounded to systems that support the engine's transaction protocol
- Destination owns deduplication via UPSERT or dedup keys
- No transaction overhead; latency unchanged
- Broker delivers at-least-once; configuration is the default
- Works across any combination of producer and consumer systems
Exactly-once is a property of a closed system. Effectively-once is a property of an open system that has been engineered to absorb duplicates. The honest characterization of most production deployments labeled exactly-once is closer to 'effectively-once,' and naming it precisely makes the failure modes legible.
- Default to at-least-once delivery plus idempotent sinks for cross-system pipelines
- Reserve engine-level exactly-once for closed-system patterns where the engine owns both source and sink
- Audit exactly-once claims against the actual sink configuration in production
- Believe the marketing word without naming the boundary it applies across
- Configure two-phase commit on a sink that does not support it; the guarantee silently downgrades
- Skip dedup logic on the destination because the broker promises exactly-once; downstream changes invalidate the assumption
2PC, Outbox, Idempotent Consumers
Apply two-phase commit, transactional outbox, and idempotent consumers as the building blocks for streaming idempotency, and combine them across the boundaries of a real architecture.
Two-Phase Commit Across Systems
Where Two-Phase Commit Breaks Down
The Transactional Outbox Pattern
The Idempotent Consumer
How the Three Patterns Combine
| Pattern | Solves | Cost |
|---|---|---|
| Two-phase commit | Engine-to-sink atomicity in a closed system | Latency (100ms+ per checkpoint); requires participant support |
| Transactional outbox | Atomicity between an application's database write and an event publish | Outbox table, tailer process, dedup-on-event-id downstream |
| Idempotent consumer | Duplicates from at-least-once delivery on the consumer side | Dedup state store; lookup cost per event |
A System That Uses All Three
- App writes to database AND publishes to Kafka in same handler
- No atomicity; crash between writes leaks one or the other
- Duplicates and lost events both possible
- The single most common production bug in event-driven systems
- App writes to database; outbox row is part of the same transaction
- Tailer publishes outbox rows to Kafka with at-least-once
- Downstream consumer dedups on event_id
- Effectively-once across the application boundary
- ▸Atomic engine-to-sink in a closed system: two-phase commit
- ▸Atomic application-database-to-queue in any system: transactional outbox
- ▸Tolerating duplicates on the consumer side: idempotent consumer
- ▸All three may apply in different parts of the same architecture
- Use the transactional outbox for any service that writes to its database and publishes events
- Implement idempotent consumers on every cross-system event handler; the broker is at-least-once by default
- Reserve two-phase commit for engines that natively support it; do not roll a custom 2PC
- Use the dual-write anti-pattern; it breaks under any failure that crosses the two writes
- Assume Kafka exactly-once covers writes to a non-Kafka sink; it does not
- Skip the dedup state TTL; an unbounded dedup store eventually becomes the bottleneck
Idempotency = replace, do not append. Write by MERGE or partition-overwrite keyed on a business key, so re-running the job (a retry or a backfill) produces the same rows instead of doubling them.
Replay Infrastructure
Design replay infrastructure that combines retained sources, addressable positions, and idempotent downstreams, and reason about the cost of each.
What Replay Is For
| Replay Trigger | Frequency | Typical Range |
|---|---|---|
| Bug in the consumer logic; reprocess to correct downstream state | Several times per year per active stream | Hours to weeks |
| New consumer being onboarded; needs history | Per consumer launch | Whatever retention allows; typically days to months |
| Source schema correction; downstream needs to reprocess | Occasional | Days to weeks |
| Disaster recovery; a downstream system needs to be rebuilt | Rare but inevitable | Full retained history |
The Source Retention Requirement
Addressable Positions
Table Format Time Travel
Idempotent Downstreams: The Third Requirement
- ▸Source retains history long enough to cover the longest expected replay (Kafka retention, table format snapshot retention)
- ▸Source exposes addressable positions (Kafka offsets, snapshot IDs, timestamps)
- ▸Every downstream consumer is idempotent on the event ID or the business key
- ▸Replay can run on isolated compute that does not interfere with production processing
- ▸Side effects (emails, charges, external API calls) are guarded against re-emission during replay
- Source retention is whatever the broker defaults to
- No way to start a consumer at a specific past position
- Downstreams accumulate state non-idempotently
- A bug-fix replay is a multi-week reconstruction project
- Source retention is sized for the longest expected replay window
- Consumers can be started at any retained offset or snapshot
- Downstreams are idempotent; replay produces correct end state
- A bug-fix replay is a one-day operation with bounded impact
- Size source retention to cover the longest realistic bug-detection-to-replay window, not the median
- Use new consumer group IDs for replay so production offsets are not disturbed
- Run replay on isolated compute capacity to protect production throughput
- Replay through a pipeline whose downstream side effects are not idempotent; the email storm is not theoretical
- Replay over a window large enough to cause source-side rate limits or back-pressure on the broker
- Treat replay as a rare operation; design for it as a feature, not as an emergency
Two Streaming Aggregators
Design two streaming aggregators with opposite guarantee requirements, justify each architectural choice against the use case, and articulate when exactly-once is over-engineering.
Aggregator A: Financial Close (Exactly-Once Matters)
| Component | Choice | Why |
|---|---|---|
| Source | Kafka with 90-day retention | Replay window must cover full quarter detection-to-correction cycle |
| Engine | Flink with checkpointed state and two-phase commit sink | Native exactly-once across engine state and the destination |
| Destination | Iceberg table with primary key on event_id | Idempotent UPSERT absorbs any duplicates that escape 2PC |
| Downstream | Daily snapshot report keyed on revenue_date | Reports are reproducible from the table at any past snapshot |
| Replay | Bounded window with new consumer group; idempotent destination dedupes | Bug-fix replays produce identical output to original runs |
Aggregator B: Page View Counter (At-Least-Once Is Fine)
| Component | Choice | Why |
|---|---|---|
| Source | Kafka with 7-day retention | Replay over a few days is the realistic upper bound; longer is overkill |
| Engine | Kafka Streams with at-least-once and idempotent counter | Operational simplicity; counters are inherently noisy on the millisecond scale |
| Destination | Redis counter keyed by (page_id, hour_bucket) | INCR is fast; small over-counts during failures are acceptable |
| Downstream | Dashboard reading the Redis counter every 5 seconds | Display layer; not used for accounting or billing |
| Replay | Not engineered; if the counter drifts, recompute from raw events as a batch job | Cost of full replay infrastructure exceeds the value of perfect counts |
- Exactly-once at the system level is a hard requirement
- Two-phase commit between Flink and Iceberg
- 90-day retention; bug-fix replays must reach back a quarter
- UPSERT keyed on event_id provides defense in depth
- Cost of architectural complexity is justified by regulatory exposure
- At-least-once with bounded over-counting is acceptable
- Kafka Streams with INCR to Redis counter
- 7-day retention; dashboard never looks back further
- Daily batch reconciliation corrects any streaming drift
- Cost of architectural simplicity is justified by use case tolerance
The Decision Framework
- ▸Outputs are used for accounting, billing, or regulatory reporting
- ▸Duplicates produce real money or real legal exposure, not just noise
- ▸Latency budget can absorb 2PC overhead (typically 100ms+ per checkpoint)
- ▸Team has the operational maturity to run a 2PC system in production
- ▸Outputs power dashboards, analytics, or anomaly detection where small noise is fine
- ▸Bounded over-counting is correctable via a daily batch reconciliation pass
- ▸Latency budget is tight (sub-100ms end-to-end)
- ▸Team prefers operational simplicity over engine-level guarantees
The Closing Principle
The concise statement of the principle: pick the weakest guarantee that the use case can tolerate, then engineer the system to deliver it reliably. Stronger guarantees are not free; they trade architectural complexity for properties the use case may not need.
- Match the idempotency guarantee to the use case; do not apply exactly-once where at-least-once suffices
- Use Lambda-style batch reconciliation when streaming approximations are acceptable in the short window
- Document the chosen guarantee in the pipeline contract; future maintainers need to know what was promised
- Default to exactly-once everywhere; the operational cost is real
- Believe at-least-once is fine when the downstream actually requires precision
- Mix guarantees within a single pipeline without explicit boundary documentation; the chain is as strong as the weakest link
> A payment processor at scale runs both kinds of streaming aggregators in production. The financial close aggregator was blamed last week for a duplicate-revenue incident during a Kafka rebalance; regulatory reports for two days had to be reissued. The page view counter has been running with at-least-once for two years without incident. The principal engineer is asked: 'How should the next generation of streaming pipelines at this company be designed so the financial close architecture is as solid as the page view counter is operable, without over-engineering either one?'
Streaming idempotency, exactly-once claims, and replay infrastructure separate marketing from engineering
- Category
- Pipeline Architecture
- Difficulty
- advanced
- Duration
- 38 minutes
- Challenges
- 0 hands-on challenges
Topics covered: Idempotency in Streaming Is Harder, Exactly-Once vs Effectively-Once, 2PC, Outbox, Idempotent Consumers, Replay Infrastructure, Two Streaming Aggregators
Lesson Sections
- Idempotency in Streaming Is Harder (concepts: paIdempotency)
Batch idempotency rests on a clean boundary: the partition. The pipeline owns a unit of work, the unit corresponds to a slice of the destination, and the slice can be replaced atomically. Streaming has no equivalent. Events arrive continuously; the destination is being written to continuously; there is no obvious moment at which to draw a boundary and say 'the work for this window is now complete and can be replaced.' Streaming idempotency exists, but it is engineered, not inherent, and the engi
- Exactly-Once vs Effectively-Once (concepts: paIdempotency)
Exactly-once is one of the most loaded phrases in streaming. Vendor marketing has used it for so long that the engineering meaning has eroded. The honest framing: exactly-once is achievable inside a closed system where the engine controls every read, write, and offset commit. End-to-end exactly-once across systems is generally not achievable; what gets advertised under that name is more precisely called effectively-once, which is at-least-once delivery combined with idempotent consumers. The dis
- 2PC, Outbox, Idempotent Consumers (concepts: paIdempotency)
Three patterns recur in streaming idempotency engineering: two-phase commit, transactional outbox, and idempotent consumers. Each addresses a specific source of duplicates. Each has a cost that constrains where it applies. A senior engineer reaches for the right one without confusing them, because the pattern that solves consumer-side duplicates does not solve producer-side ones, and vice versa. Naming each precisely is the prerequisite for combining them correctly. Two-Phase Commit Across Syste
- Replay Infrastructure (concepts: paStreamProcessing)
Replay is the streaming-world equivalent of backfill. It is the act of reprocessing events from a known offset or timestamp to correct downstream state. Replay is harder than batch backfill because there is no clean partition to overwrite, and easier because the source is often retained in a log that supports random access. Designing for replay requires three pieces of infrastructure: a retained source, addressable positions, and idempotent downstream consumers. Without all three, replay is a ma
- Two Streaming Aggregators (concepts: paIdempotency)
The patterns become concrete on real workloads. Two streaming aggregators sit at opposite ends of the idempotency-cost spectrum. The first is a financial close aggregator that produces daily revenue numbers used in regulatory reporting; exactly-once is a correctness requirement, and the cost of getting it wrong is real money and real regulatory exposure. The second is a page view counter that powers a real-time engagement dashboard; at-least-once is sufficient, the dashboard tolerates noise, and