Make the sink idempotent so replays never duplicate rows
The core producer-side reliability skill for streams: you cannot stop replays, so the sink must converge. Deduplicate by a business key and event time within the watermark, MERGE on the key, and rank change events by (lsn, ts_ms) — never by arrival time.
At a glance
| When | Kafka → Delta/Iceberg/warehouse; Any replay-prone stream |
|---|---|
| Where | Streaming |
| Why | You cannot stop replays, so the sink must converge to the same state no matter how many times a batch arrives. |
Learning objectives
- Make the sink idempotent rather than trying to make the source exactly-once
- Deduplicate by a business key + event time within the watermark
- Rank change events deterministically by (lsn, ts_ms), never by arrival time
What you need
- A stream writing into Delta/Iceberg or a warehouse table with a stable business key
- [object Object]
Steps
- Name the failure mode — streaming sinks either duplicate on replay or lose rows on failure. (from pattern-streaming-dedup-upsert )
- Make the sink idempotent rather than trying to make the source exactly-once. (from pattern-streaming-dedup-upsert )
- Deduplicate by a business key + event time within the watermark, then MERGE (upsert) on the key — a replay recomputes the same rows and merges to the same state, idempotent by construction. (from pattern-streaming-dedup-upsert )
- Order by event time, not arrival time. (from pattern-streaming-dedup-upsert )
- For change streams, rank by `(lsn, ts_ms)` — never by ingestion time; ranking by LSN makes re-running the same micro-batch produce the same table. (from skill-cdc-merge-designer )
- Deduplicate before merging — keep the latest event per primary key with a window function, then merge once. (from skill-cdc-merge-designer )
- Plan for checkpoint loss — checkpoint lost means duplicates, and the fix is making the sink idempotent. (from project-kafka-spark-delta )
Check yourself
- Replaying the same micro-batch twice produces an identical table — the merge is idempotent by construction.
- The dedupe key is a business key plus event time within the watermark, not an offset and not an arrival timestamp.
Worked example
The pattern in one line — from pattern-streaming-dedup-upsert
stream ─▶ [windowed dedupe by key] ─▶ MERGE (upsert on key) ─▶ table
Ordering and idempotency rules — from skill-cdc-merge-designer
Events arrive out of order. Rank by `(lsn, ts_ms)` — never by ingestion time.
Re-running the same micro-batch must produce the same table. Ranking by LSN
guarantees this; ranking by arrival time does not.
Sourced from
Security scan
Last sweep 2026-10-24: 0 finding(s), 0 marked as examples.
Common gotchas
- Ranking by arrival time breaks idempotency — rank by (lsn, ts_ms) or event time.
- The pattern requires a stable business key and event time; state cost is the price.
- A pure append-only event log with no dedup need does not need this pattern.
Production considerations
- Correct under replay with no reliance on perfect exactly-once — that is the goal, and it is achievable.
- MERGE aggregates on the window key so replays converge.
Diagram
idempotent sink
|
|- stream arrives (at-least-once, replays included)
|
|- windowed dedupe by business key + event time
|
|- MERGE (upsert on key) --> table converges
|
--> replay the same batch --> same rows --> same state
Registry stats
| Verification | community · Oct 21, 2026 |
|---|---|
| Completeness | 81 |
| Trust multiplier | ×1.25 |
| Composite | 34.15 · adequate |
| Freshness | 3 days since last source check |
| Difficulty | intermediate |
| Version | 0.1.0 |
| License | CC-BY-4.0 |
| Security flags | none |
Did this solve your problem?
Make the sink idempotent so replays never duplicate rows