Reference · Reliability
Idempotency & Retries
Published
The compressed essence. One page to glance at before an interview or a design review.
Quick definitions
- Safe
- Read-only intent — no intended state change.
GET,HEAD,OPTIONS. → “Safe” is about intent, not “secure” and not “side-effect-free” (a GET may still log). - Idempotent
- N identical requests ≡ one request’s effect on state.
GET,HEAD,OPTIONS,PUT,DELETE. → The property that makes a request safe to retry. POST— not idempotent- Each call can create another resource / charge again. → Needs an
Idempotency-Keyto be retried safely. PATCH— not guaranteed- Depends entirely on the patch document. → A “set field to X” patch is idempotent; an “increment by 1” patch is not.
Method idempotency at a glance
| Method | Idempotent? | Notes |
|---|---|---|
GET | yes | Pure read. |
PUT | yes | Full replace — repeated puts converge to the same state. |
DELETE | yes | Return 204, or 404 when already gone — the resource is absent either way. |
POST | no | Each call can create / charge again — needs an Idempotency-Key. |
PATCH | depends | Idempotent only if the patch doc is (e.g. “set X” yes, “increment” no). |
The Idempotency-Key pattern
Make a non-idempotent operation safe to retry by deduplicating on a client-supplied key. Stripe.
1Client generates a unique key per logical operation (UUID v4) — not per HTTP attempt.
2Client sends it in the
Idempotency-Key request header.3Server, before executing, looks up the key in a durable store.
4Miss → execute inside a transaction that also persists the key + resulting status code + response body + a fingerprint of the request.
5Hit → return the stored response without re-executing.
6Keys expire after a TTL (Stripe ≈ 24h).
7If a retry’s body differs from the stored fingerprint → reject (key reuse for a different request).
Implementation checklist
- Durable store
- A DB table or Redis with a TTL — must survive a process crash mid-request.
- Persist the whole outcome
- Status code + response body + request fingerprint, so replays are byte-identical and reuse is detectable.
- Concurrency control
- Enforce a UNIQUE constraint or lock on the key so two in-flight requests can’t both execute — the loser waits or gets
409. - Scope the key
- Per endpoint + per account, so collisions and cross-tenant leakage can’t happen.
- Apply selectively
- Only on unsafe / non-idempotent operations — payments, order creation, message sends. Skip it for reads and naturally idempotent writes.
Idempotency ≠ exactly-once
Exactly-once delivery is impossible in a distributed system. What you can build is exactly-once effect:
=exactly-once effect = at-least-once delivery + idempotent processing (dedup)
Retry strategy
- Retry only what’s safe
- Idempotent methods, or non-idempotent ones guarded by an
Idempotency-Key. Never blindly retry a barePOST. - Exponential backoff + jitter
- Spread retries over growing, randomized intervals to avoid synchronized retry storms (thundering herd).
- Honor
Retry-After - When the server tells you when to come back (
429/503), obey it. - Cap attempts
- Bound total retries; fail fast rather than hammering a dead dependency.
- Pair with circuit breakers
- Stop retrying entirely once a downstream is clearly down; let it recover.