Idempotency
Make an operation safe to repeat, so retries and duplicates don't cause damage.
By the end of this lesson, design idempotent operations and use idempotency keys to dedupe retries.
An operation is idempotent if doing it twice has the same effect as doing it once. SET balance = 100 is idempotent; ADD 100 to balance is not. Because networks guarantee at-least-once delivery (retries, duplicate messages, ambiguous timeouts), idempotency is what makes distributed systems safe.
First, see the whole system. Then we’ll open it up.
Idempotency key dedupe
- A client sends an idempotency key; the service checks a key store: if new, process once and save the result; if seen, return the saved result without re-processing.
flowchart LR client[Client] -->|request + idempotency-key| svc[Service] svc -->|key seen?| store[(Key store)] store -->|new| do[Process once, save result] store -->|seen| replay[Return saved result]
For naturally non-idempotent actions (charge a card, send an email), the client supplies an idempotency key; the server records the key with the result, so a retry with the same key returns the original result instead of acting again. This turns unreliable at-least-once delivery into effectively-once behavior — the practical replacement for the myth of exactly-once.
This is where the abstraction starts leaking.
'Exactly-once delivery' doesn't exist over an unreliable network — the ack can always be lost. What you actually build is at-least-once delivery plus idempotent processing. Chasing exactly-once at the transport layer is chasing a ghost.
Duplicate side effects
- Trigger
- A non-idempotent operation is retried or a message is redelivered.
- Symptom
- Double charges, duplicate records, repeated emails.
- Blast radius
- Data integrity and user trust.
- Mitigation
- Idempotency keys, dedupe stores, idempotent operation design.
Deep dive: Partial Failure
How does an idempotency key make 'charge $100' safe to retry?
The server stores the key with the charge result; a retry with the same key returns that stored result instead of charging again.