Idempotency Under Fire: Load-Test Proof That Check-Then-Act Fails, and the Redis+Lua Layered Fix
Duplicate orders and duplicate payments are ticket-generating production incidents; this load-tested breakdown gives Western teams the actual numbers and the layered pattern—front-door atomic guard plus resource-layer constraint—that holds up under concurrency, not just a theory.
A 100-thread concurrency load test deliberately amplifies a 5ms gap between EXISTS and SET, and the non-atomic check-then-act implementation lets all 100 requests through while the atomic versions allow exactly 1. The same test at 200 concurrency with limit=5 shows SETNX still passes only 1 (placeholder semantics), while a Lua sliding window correctly passes 5. That concrete data is the foundation of the article's core claim: idempotency checks must be a single atomic operation.
The article then layers the defense: Redis SETNX with NX and expiration as a fast entry gate, Lua-based sliding window when you need "N times in a window," and a database unique index as the final resource-layer backstop because Redis keys can expire, fail over, or crash before the DB write. It walks through production boundaries like TTL expiry windows (where a duplicate can slip in if the business takes longer than the key lives), idempotency record tables as a persistent alternative, MQ at-least-once handling with unique constraints, why distributed locks are mutual exclusion but not idempotency, and conditional state updates for status-machine flows.
The load-test numbers turn a common warning about check-then-act into a measured fact: at 100 concurrency the race window let every request through, so the 'just add EXISTS' advice is demonstrably not theoretical.
The SETNX vs. sliding-window distinction is a clean reminder that idempotency primitives answer different questions: first-occurrence vs. count-in-window, and mixing them produces either over-rejection or under-protection.
The layered defense (entry atomic guard + resource-layer unique index) reflects a practical reality: any single fast mechanism has an expiry or failover edge, so the cost of duplicates must be caught at the database when it matters.
The TTL-expiry-before-business-finishes scenario is the kind of edge case that escapes casual code review; the proposed trio—generous TTL, unique index backstop, idempotency table for full lifecycle—treats it as a first-class design problem.