STANDARDwalkthrough

Idempotency Keys

1 of 8
3 related
A customer clicks 'Pay' and their browser hangs. They click again.
The payment service checks a PostgreSQL UNIQUE constraint on (merchant_id, idempotency_key) before processing. If the key exists, we return the original response without re-charging.
Without protection, we just charged their card twice. Idempotency keys solve this: the client generates a UUID and attaches it to every charge request via the Idempotency-Key header.
Why PostgreSQL UNIQUE instead of Redis dedup? Because the idempotency check and the transaction INSERT must be in the same ACID transaction.
If we checked Redis and then inserted into PostgreSQL, a crash between the two steps would either lose the idempotency record (double charge) or lose the transaction (phantom dedup). Stripe stores idempotency keys for 24 hours, long enough to cover client retries and short enough to bound storage growth.
At 10K TPS, that is 10K×86,400×100B=86 GB/day10K \times 86{,}400 \times 100B = 86\text{ GB/day} of idempotency records, purged daily. The UNIQUE constraint lookup adds ~2ms per request via B-tree index scan.
Trade-off: 2ms extra latency per request, but zero double charges. At $50 average transaction, a single double charge costs more than a year of the 2ms overhead.
Why it matters in interviews
This is the KEY INSIGHT of payment system design. Interviewers want to hear why the idempotency check must live in the same ACID transaction as the charge, not in a separate cache. Explaining the crash window between Redis check and PostgreSQL insert shows you understand distributed failure modes.
Related concepts