Idempotency Strategy¶
1. Decision¶
Implement explicit Idempotency Strategies across all API endpoints and asynchronous message consumers, ensuring that executing an operation multiple times with the same input produces the exact same system state and response as executing it once.
2. 30-Second Answer¶
In distributed systems, network timeouts, retries, and At-Least-Once message delivery make duplicate requests inevitable. Without Idempotency, retrying a payment request or order submission causes catastrophic double-charging or duplicate order fulfillment. Idempotency guarantees that \(f(f(x)) = f(x)\). Naturally idempotent HTTP methods (GET, PUT, DELETE) require proper REST semantics. Non-idempotent operations (POST) require explicit Idempotency Keys (UUIDs generated by the client) verified server-side using a two-phase check-and-insert pattern inside an in-memory store (Redis) or database UNIQUE constraint, combined with cached response payloads.
3. Decision Matrix¶
| Option | Best For | Avoid When | Latency | Scalability | Consistency | Operational Complexity |
|---|---|---|---|---|---|---|
| HTTP Idempotency-Key Header (Redis SET NX) | Financial payment APIs, checkout operations, third-party webhooks, mobile POST endpoints | Read-only endpoints (GET), naturally idempotent state replacements (PUT) |
Ultra-Low (<1ms Redis key check + TTL) | High (Stateless API gateway / app tier with Redis cache) | Strict (Prevents duplicate executions globally) | Low to Medium |
Database Unique Constraint (UNIQUE INDEX) |
Relational database entity creations, user registration, account ledger inserts | Ultra-high QPS logging where index maintenance degrades write performance | Low (Enforced at storage engine layer during write) | Scale-up (SQL); Local shard scale-out | Strong (Database engine guarantees atomicity) | Low |
Naturally Idempotent REST Verbs (PUT / DELETE) |
State replacement operations (PUT /users/123), resource deletions (DELETE /orders/456) |
Incremental updates (balance = balance + 100), append-only list operations |
Low | High | Strong | Very Low |
4. Selection Rules¶
| Requirement | Best Choice | Rationale / Example |
|---|---|---|
| Financial Payment API Checkout Endpoint | Idempotency-Key Header |
Client sends UUID header; server deduplicates concurrent retries via Redis |
| Database Row Creation Safety | UNIQUE Constraint |
Add UNIQUE(tenant_id, idempotency_key) to database table schema |
| Asynchronous Message Queue Consumer (Kafka/SQS) | Message Deduplication Table | Check message_id against Redis/DB before executing background task |
State Overwrite Operation (user.email = X) |
HTTP PUT |
Setting a value to X 10 times results in identical state X |
Incremental State Update (counter += 1) |
Idempotency Key + Lock | Non-idempotent operation requires explicit deduplication tracking |
5. Tradeoffs¶
- Duplicate Execution Safety vs Memory Footprint Overhead
- Gain: Completely prevents double-charging, duplicate orders, and state corruption.
- Lose: Must store Idempotency Keys and cached response payloads in Redis/DB for \(N\) days.
- Client-Generated UUIDs vs Server-Generated Hash Identifiers
- Gain (Client UUID): Captures client-intent precisely even if payload attributes change slightly.
- Lose (Client UUID): Requires client SDKs to generate and persist UUIDs across network retries.
- Database Unique Constraints vs Redis Key Pre-Checking
- Gain (DB Constraint): Atomic ACID safety guaranteed by database storage engine.
- Lose (DB Constraint): Aborted duplicate inserts generate DB error logs and consume auto-increment IDs.
- Caching Response Payloads vs Storage Cost
- Gain: Retried requests receive exact identical HTTP response payload instantly without re-processing.
- Lose: Increased RAM/disk storage required to cache JSON response strings alongside keys.
- Short Key TTL vs Long Key TTL
- Gain (Long TTL): Protects against late retries days after initial failure.
- Lose (Long TTL): Accumulates millions of keys in Redis memory.
6. Interview Counter Questions¶
- Which HTTP methods are naturally idempotent according to RFC 7231, and why is
POSTnot idempotent? - How do you design a complete Idempotent API endpoint for credit card processing (Stripe-style)?
- What happens if a duplicate request arrives while the initial request is still actively processing?
- How do you implement Idempotency in an asynchronous Kafka or SQS consumer worker?
- What is the difference between Idempotent Delivery and Idempotent Processing?
- Why is storing the Idempotency Key in Redis after DB processing starts vulnerable to race conditions?
- How do you handle response payload caching for idempotent endpoints?
- What is the
Idempotency-KeyHTTP header specification drafted by the IETF? - Why should Idempotency Keys have a mandatory Time-To-Live (TTL e.g. 24-72 hours)?
- How do you handle idempotency when an operation involves third-party downstream APIs?
7. Gold Standard Answers¶
GET,HEAD,OPTIONS,TRACE(Safe & Idempotent);PUT,DELETE(Idempotent - state replacement);POST,PATCH(NOT Idempotent - creates resources or appends state).- Client sends
Idempotency-Key: <UUID>. Server checks Redis: (1) Key exists & processing \(\rightarrow\) return409 Conflictor wait; (2) Key exists & completed \(\rightarrow\) return cached response; (3) Key new \(\rightarrow\) save keystatus=IN_PROGRESSand process. - Use a 3-state key in Redis (
IN_PROGRESS,COMPLETED,FAILED). If a duplicate request arrives whileIN_PROGRESS, return409 Conflictor block/wait untilCOMPLETEDto return cached response. - Worker extracts
message_idor business payload hash. In a single local DB transaction:INSERT INTO processed_messages (message_id) VALUES (...). If duplicate, DB raises unique violation; worker skips processing and ACKs message. - Idempotent Delivery (Broker setting) prevents duplicate message publishing to topic. Idempotent Processing (Consumer logic) guarantees consumer handles re-delivered duplicate messages safely.
- Check-then-act race: If 2 concurrent requests arrive at the same millisecond, both pass a read check if key isn't stored yet. Solution: Use atomic
SET key lock_token NX EX 86400before starting processing. - Store JSON response body and HTTP status code in Redis mapped to the Idempotency Key. When duplicate request arrives, return the stored status code and JSON body directly.
- IETF Draft Spec: Client sends
Idempotency-Key: <UUID>. Server returns400 Bad Requestif key format is invalid,409 Conflictif concurrent request in progress, and identical response payload on retries. - TTLs (e.g. 24-72 hours) ensure Redis memory is reclaimed after retry windows pass. Most client network retries occur within seconds/hours of original failure.
- Pass the original
Idempotency-Keydownstream to third-party APIs (e.g. StripeIdempotency-Keyheader), ensuring third-party downstream calls are also deduplicated.
8. Common Mistakes¶
- Assuming
POSTendpoints are idempotent without explicitly implementing anIdempotency-Keyframework. - Checking Redis for key existence and setting it in 2 non-atomic steps, allowing concurrent race condition bypasses.
- Executing database updates before checking or locking the Idempotency Key in Redis/DB.
- Returning an error payload on retries instead of returning the original successful cached response payload.
- Generating Idempotency Keys on the server side instead of requiring the client application to generate UUIDs.
9. Keywords¶
Idempotency, Idempotency-Key, \(f(f(x)) = f(x)\), HTTP Verbs, SET NX, UNIQUE Constraint, Deduplication, Stripe API, Redis, Race Condition, 409 Conflict, Retries, IETF Draft, Atomic Check-and-Insert.
10. Cheat Sheet¶
- Core Definition: Executing an operation 1 time or 100 times produces identical system state & response.
- REST Verbs:
GET,PUT,DELETE= Idempotent;POST,PATCH= NOT Idempotent (requires explicit keys). - Client Rule: Client MUST generate the UUID
Idempotency-Keyheader and attach it to network retries. - Server Execution Flow:
- Atomic
SET idempotency:key IN_PROGRESS NX EX 86400in Redis. - If Key Exists &
IN_PROGRESS\(\rightarrow\) Return409 Conflict. - If Key Exists &
COMPLETED\(\rightarrow\) Return Cached JSON Response. - Process Business Logic in DB.
- Update Redis key
status=COMPLETED+ Store JSON Response. - Database Safety: Add
UNIQUE INDEXon(tenant_id, idempotency_key)in database tables as secondary safety net.