In this lesson, you will learn how to use a cache-based key to ensure a transaction is processed exactly once, even if a client sends the same request multiple times due to network retries.
The Problem: The "Double-Click" Dilemma
In distributed systems, a client might send a payment request, but a network hiccup causes the response to be lost. The client, assuming the request failed, sends the exact same request again. Without a guardrail, your database might process the payment twice, leading to unhappy customers and inconsistent ledger states.
The Solution: Idempotent Keys
We use a cache (like Redis) as a gatekeeper to track these "in-flight" requests using a unique identifier, often called an Idempotency Key. This key is typically generated by the client and included in the request header.
The workflow looks like this:
- Check: Before processing, the server checks the cache for the existence of the key.
- Lock: If the key doesn't exist, we set it in the cache with a short Time-To-Live (TTL) (e.g., 30 minutes).
- Execute: If the key was successfully set, proceed with the database transaction.
- Ignore: If the key already exists, the server returns the cached result of the previous attempt or a "409 Conflict" status, preventing redundant database writes.
Implementation Pattern
Using a simple string key in Redis, the logic follows this pattern:
# Pseudo-code for an idempotent transaction
key = "idempotency_key:" + request.header['x-idempotency-key']
# Attempt to set the key only if it doesn't exist (NX)
if redis.set(key, "processing", nx=True, ex=1800):
try:
# Perform the actual SQL transaction
result = db.execute("INSERT INTO payments...")
redis.set(key, result) # Store success for future retries
return result
except:
redis.delete(key) # Allow retry on failure
raise
else:
# Key exists; return the previously cached result
return redis.get(key)
By leveraging the atomicity of cache operations, we create a barrier that ensures critical state-changing operations are strictly idempotent. The cache doesn't replace your primary SQL database; it protects it from accidental duplication by acting as a high-speed filter for concurrent or retry-heavy request streams.
Key Takeaway: Use a unique idempotency key and a cache-based "check-and-set" pattern to prevent duplicate processing of critical transactions.