System Design: Designing Idempotent APIs
In a distributed system, network failures are inevitable. A common failure scenario is: "The client sends a request -> The server processes it -> The server's response fails to reach the client -> The client retries the request." Without idempotency, this retry would lead to a duplicate charge, order, or record creation.
1. What is Idempotency?
An operation is idempotent if it can be applied multiple times without changing the result beyond the initial application.
- , , are naturally idempotent.
- (Create) is not naturally idempotent.
2. The Idempotency Key Pattern
The standard solution is the Idempotency Key.
- The client generates a unique key (usually a UUID) for the request.
- The client sends this key in the header (e.g., ).
- The server checks if the key exists in a cache or database.
- If key exists: Return the cached original response. Do NOT process the request again.
- If key does not exist: Process the request, store the result, and save the key with an expiry.
3. Storage Strategy
- Short-term: Use Redis to store idempotency keys with a TTL (e.g., 24 hours). This is fast and efficient for high-traffic APIs.
- Long-term: Use a Database (Postgres/DynamoDB) if you need to guarantee uniqueness over a long period (e.g., to prevent duplicate payments across days).
4. Implementing Atomicity
The check-and-set operation must be atomic.
- Bad: (Race condition possible).
- Good: Use a database transaction or a Redis Lua script to ensure that "Check existence and insert" happens in a single, non-interruptible step.
5. Summary
Idempotency is a non-negotiable requirement for any reliable distributed API. By enforcing the Idempotency Key pattern, you decouple your API's reliability from the stability of the underlying network, allowing your clients to safely retry requests without fear of unintended side effects.
