Knowledge Guide
HomeSystem DesignScalable Systems (Advanced Topics)

hard Exactly-Once is a Myth — Idempotency & Dedup

Why exactly-once delivery cannot exist

Consider any sender that must do two things: apply an effect (charge a card, enqueue a job) and durably record that it did. These are two separate steps, so a crash can land between them. When the sender restarts it finds no “done” record and cannot tell which world it is in:

Add an unreliable network and the receiver has the mirror problem: a lost ack is indistinguishable from a lost message. There is no observation either side can make to collapse the ambiguity. Hence you can pick at-most-once (never retry — may lose) or at-least-once (retry — may duplicate), but exactly-once delivery is impossible. What you can build:

at-least-once delivery + idempotent effect = effectively-once. You stop trying to deliver exactly once and instead make a duplicate harmless.
Top: two workers both SELECT for key K, both find nothing, both charge, resulting in a double charge. Bottom: both INSERT key K first; one succeeds and charges, the other hits a unique violation and skips, charging once
Top: two workers both SELECT for key K, both find nothing, both charge, resulting in a double charge. Bottom: both INSERT key K first; one succeeds and charges, the other hits a unique violation and skips, charging once

The trap inside the fix: the dedup race

“Effectively-once” hinges on a dedup check: have I already processed this idempotency key? The naive implementation is check-then-act — and that is itself a race. Two duplicate deliveries of the same key K land on two workers at the same time:

stepWorker AWorker B
1SELECT ... WHERE id=K → not found
2SELECT ... WHERE id=K → not found
3charge $50
4charge $50 ← double charge
5record Krecord K

Both reads ran before either write, so both passed the “never seen it” check. This is the same read-modify-write hazard as counter++. The dedup must be atomic, not read-then-write.

Make the dedup atomic

Let the database serialize the contenders on the key itself:

-- one atomic step: the DB rejects the second inserter
INSERT INTO processed(idempotency_key) VALUES (:k)
  ON CONFLICT (idempotency_key) DO NOTHING;   -- key has a UNIQUE constraint
-- if a row was inserted, this worker won the key: do the effect (same txn).
-- if 0 rows inserted, a duplicate already owns it: skip / return stored result.

Equivalents: a UNIQUE constraint on the key (the second INSERT fails with a unique violation), or an atomic conditional write (DynamoDB PutItem with attribute_not_exists, Redis SET key val NX, or a compare-and-set). The rule: exactly one writer can win the key, decided by the storage layer — never by a prior SELECT. Commit the key row and the effect in the same transaction so they cannot diverge.

Pitfalls

The judgment layer

At-least-once + idempotent vs at-most-once. At-least-once (retry until acked, dedup on the consumer) is the default for anything that must not be lost — payments, orders, state changes — and the cost is that every consumer must be idempotent and pay for a dedup store. At-most-once (fire-and-forget, no retry) is legitimate when a lost message is cheaper than a duplicate and volume is huge — metrics samples, best-effort telemetry, cache warmers — where deduping every event would cost more than the occasional gap is worth.

Idempotency-key store + TTL trade-off. Keys must be retained at least as long as the maximum retry window (client retries + queue redelivery + replays). Too short a TTL and a late retry arrives after the key expired → the duplicate slips through and re-applies the effect. Too long and the store grows without bound. Size the TTL to the real worst-case redelivery horizon (often hours to a day), not the happy-path latency. Store choice trades durability for speed: a DB unique index is durable and co-transactional with the effect but adds write load; Redis SETNX + TTL is fast and self-expiring but is not in the same transaction as the effect, so you must reason about the ordering carefully.

Takeaways


Synthesized from the Two Generals / FLP intuition, Kleppmann, Designing Data-Intensive Applications (Ch. 11, “exactly-once”), the Stripe idempotency-key API design, and Kafka’s idempotent-producer / transactional-offset semantics. Re-authored/Deepened for this guide.

🤖 Don't fully get this? Learn it with Claude

Stuck on Exactly-Once is a Myth — Idempotency & Dedup? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Exactly-Once is a Myth — Idempotency & Dedup** (System Design) and want to truly understand it. Explain Exactly-Once is a Myth — Idempotency & Dedup from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Exactly-Once is a Myth — Idempotency & Dedup** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Exactly-Once is a Myth — Idempotency & Dedup** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Exactly-Once is a Myth — Idempotency & Dedup** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes