06
Deep Dive — Inventory Under Concurrency
The single hardest problem in e-commerce: 10,000 users click "Buy" on an item with 5 units left — within the same second. How do exactly 5 succeed and 9,995 get a clean "sold out"?
Why Naive Approaches Fail
Pessimistic Lock
SELECT ... FOR UPDATE serializes all 10K requests through one row lock. At 5ms/txn = 50 seconds total. Connection pool exhaustion crashes the entire database.
Optimistic Lock
Version-based check-and-retry. 10K reads, 1 succeeds, 9,999 retry. Cascading retries produce ~50M total attempts — worse than pessimistic.
The Production Solution: Redis Atomic DECR
Redis is single-threaded — every command executes sequentially with no interleaving. A Lua script atomically checks availability and decrements, handling 50,000+ ops/sec — a 250x improvement over database locking.
Reserve with TTL — Redis Lua Script
local available = tonumber(redis.call('GET', KEYS[1]))
if available == nil or available <= 0 then
return {0, "OUT_OF_STOCK", 0}
end
local requested = tonumber(ARGV[1])
if available < requested then
return {0, "INSUFFICIENT_STOCK", available}
end
redis.call('DECRBY', KEYS[1], requested)
redis.call('HSET', KEYS[2], ARGV[2], ARGV[1])
redis.call('EXPIRE', KEYS[2], tonumber(ARGV[3]))
return {1, "RESERVED", available - requested}
Two-Phase Reservation Lifecycle
sequenceDiagram
participant U as User
participant O as Order Svc
participant I as Inventory (Redis)
participant P as Payment Svc
participant DB as Order DB
U->>O: POST /orders/checkout
O->>I: EVAL reserve.lua (DECR + TTL)
I-->>O: RESERVED (10 min TTL)
O-->>U: checkout_session_id + pricing
U->>O: POST /orders (Idempotency-Key)
O->>P: Charge card (idempotent)
P-->>O: Payment SUCCESS
O->>DB: INSERT order + outbox (single txn)
O->>I: Confirm reservation (HDEL)
O-->>U: 201 Order Confirmed
Note over I: If TTL expires before payment:
I->>I: Sweeper: INCRBY (release stock)
Soft vs. hard expiry: The user sees a 10-minute timer (soft). The actual Redis TTL is 15 minutes (hard). The 5-minute buffer ensures reservations never expire while payment is in-flight.
Reconciliation — The Safety Net
Redis is volatile; PostgreSQL is the durable source of truth. A reconciliation job runs every 5 minutes (paused during flash sales), compares Redis counters with PostgreSQL, and corrects drift. An orphaned-reservation sweeper releases reservations that exist in PostgreSQL but are missing from Redis (e.g., after a Redis failover).
| Approach | Throughput | Correct? | Best For |
| Pessimistic Lock | ~200/sec | Yes | Low traffic (<100 concurrent) |
| Optimistic Lock | ~500/sec* | Yes | Moderate contention |
| Atomic SQL UPDATE | ~2,000/sec | Yes | Single-DB architectures |
| Redis Lua (2-phase) | ~50,000/sec | Yes | Flash sales, production |