Preventing Flash-Sale Inventory Overselling with Redis Distributed Locks in Go
Deconstructing race conditions during 10,000 requests/second campaign drops: implementing the Redlock algorithm with strict Lua script atomicity.
Max Throughput
42,000 req/sec
Redis Cluster 3-node shard
Oversell Rate
Exact 0.00%
Tested under 50k concurrent requests
Reservation Latency
< 1.8 ms
99th percentile response time
01The Engineering Bottleneck
During promotional flash sales or high-volume wholesale drops, hundreds of concurrent buyers submit checkout requests for the exact same stock SKU. Traditional `SELECT stock FROM inventory WHERE sku = ? FOR UPDATE` causes PostgreSQL/MySQL thread pool exhaustion, database connection pool lockups, and catastrophic overselling when replica lag is introduced.
02Architectural Design & Invariants
We offload the critical reservation path to an in-memory Redis cluster. Using custom atomic Lua scripts, inventory check and decrement execute within a single atomic CPU cycle in Redis. Distributed locks with cryptographic random tokens and auto-renewal goroutines prevent deadlocks while guaranteeing that exactly zero items can ever be oversold.
03Production Implementation Blueprint
gopackage inventory
import (
"context"
"github.com/redis/go-redis/v9"
)
// Atomic Lua script: Checks stock >= requested, decrements, and creates reservation key
const reserveStockLua = `
local stock_key = KEYS[1]
local reservation_key = KEYS[2]
local requested_qty = tonumber(ARGV[1])
local order_id = ARGV[2]
local ttl_seconds = tonumber(ARGV[3])
local current_stock = tonumber(redis.call('get', stock_key) or '0')
if current_stock >= requested_qty then
-- Deduct immediately in memory
redis.call('decrby', stock_key, requested_qty)
-- Store order reservation with 15-minute expiration
redis.call('setex', reservation_key, ttl_seconds, requested_qty)
return 1 -- Success
else
return 0 -- Insufficient stock
end
`
type InventoryManager struct {
rdb *redis.Client
}
func (m *InventoryManager) ReserveStock(ctx context.Context, sku string, orderID string, qty int) (bool, error) {
stockKey := "stock:" + sku
resKey := "res:" + orderID + ":" + sku
res, err := m.rdb.Eval(ctx, reserveStockLua, []string{stockKey, resKey}, qty, orderID, 900).Result()
if err != nil {
return false, err
}
return res.(int64) == 1, nil
}04Architectural Invariants & Rules of Thumb
- Never execute `GET` followed by application logic followed by `SET` in distributed environments; race conditions are guaranteed.
- Use Redis Lua scripts which run single-threaded and atomically to guarantee thread-safe checks and decrements.
- Set an explicit 10-15 minute TTL on reservation keys so abandoned carts automatically release locked inventory back to the general pool.
- Write confirmed orders asynchronously to the relational database using Kafka or RabbitMQ event sourcing.
Facing similar architecture bottlenecks in your business?
We design and implement custom ERPs, high-throughput databases, and air-gapped private AI systems tailored for high-concurrency enterprise workloads.