Data building blocks
Exactly-once in streams
A stream processor restores its state and rewinds its input after a crash. The state counts each event once, but the output can repeat. End-to-end exactly-once needs an idempotent or transactional sink.
A stream processor keeps state, such as per-key counts or the set of IDs it has seen. After a crash, that state must match the input position it was read from. Otherwise the results drift. Flink and Kafka Streams solve the state part with checkpoints. The output part needs one more step from the sink.
Concept
Before you start. State is what the processor remembers between events. A checkpoint is a saved copy of that state together with the input position it matches. At-least-once delivery means an event may be processed more than once. An idempotent write gives the same result when repeated.
The big idea
Think of reading a book with a bookmark. You save your notes and the page number together. If you lose your place, you go back to the bookmark and read again. Pages after the bookmark are read twice, but the notes were saved only at the bookmark, so they are not doubled.
The bookmark protects the state. It does not protect things you already sent out, such as messages or database writes. Those must be safe to repeat.
Walking through it
A counter has checkpointed count = 1,200 at Kafka offset 5,000. It keeps processing and reaches offset 5,400, where count = 1,600. Then it crashes. Nothing after offset 5,000 was saved.
Restore checkpoint: count = 1,200, rewind input to offset 5,000
Replay 400 events: count = 1,200 + 400 = 1,600
State: counted once, as if no crash happened
Outputs for 5,000-5,400: emitted before the crash, then emitted againFlink does this with barriers. The job manager injects a barrier into each source, along with the source offsets. Each operator snapshots its state when the barrier reaches it, then passes the barrier on. When every sink acknowledges, the checkpoint is complete.
The state is counted once. The output may still repeat, so the sink must handle that:
- Idempotent sink. Upsert rows keyed by
(ad_id, minute). Writing the same row twice leaves one row. - Transactional sink. Flink's two-phase commit writes to Kafka inside a transaction that commits with the checkpoint. Consumers with
read_committednever see uncommitted output. Kafka Streams offersexactly_once_v2for this.
So "exactly-once" in streaming means effectively once: at-least-once delivery, plus an effect that is deterministic and repeat-safe.
Common mistakes
- Writing
count = count + nto the sink. Every recovery adds the replayed events again. - Calling the pipeline exactly-once without naming the sink. The guarantee stops at the sink.
- Unbounded state. Deduplication state with no time-to-live grows until checkpoints slow down.
Check yourself
- In the example, what is the count after recovery? 1,600. The checkpoint holds 1,200, and replaying 400 events adds 400.
- Why is an upsert keyed by
(ad_id, minute)safe to repeat? Writing the same key with the same value again leaves one row, so repeats have no extra effect.
Key takeaways
- Checkpoints save state with its input position, so state is counted once after a crash.
- Replayed input can repeat outputs, so the sink needs to handle repeats.
- Idempotent upserts and transactional sinks are the two options.
- Effectively-once is at-least-once plus repeat-safe effects.
Further learning
Next, see how this design fits into two overall architectures in Lambda vs Kappa.
For the general rules behind repeat-safe writes, see idempotency.
Related
Batch and stream processing
How to move and transform large data sets reliably. Batch jobs run over bounded data, streams run over events as they arrive, and the same ideas of time, state and idempotent writes apply to both.
Idempotency and retries
Making operations safe to retry, with idempotency keys, deduplicating consumers and jittered backoff, so at-least-once delivery never means a double charge.
Delivery semantics
The three promises a messaging system can make (at-most-once, at-least-once, exactly-once) and why the real choice is when the consumer says "done".