advanced
Distributed locks caveats
Treat Redis locks as leases with fencing, timeout, clock, failover, and idempotency concerns, not a magic mutex.
A Redis lock is usually a lease: `SET resource:lock <token> NX EX <ttl>` — only one holder until TTL expires. It is not a magic cluster-wide mutex; correctness depends on timeout, token verification, fencing, failover behavior, and idempotent workers.
// Acquire
const token = crypto.randomUUID()
const ok = await redis.set('lock:invoice:9', token, 'NX', 'EX', 30)
if (!ok) throw new Error('busy')
// Release — compare token before DEL (Lua for atomicity)
const script = `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`
await redis.eval(script, 1, 'lock:invoice:9', token)
| Risk | Mitigation | |------|------------| | Process pause > TTL | Short work units; extend lease carefully | | Delete wrong lock | Token check in Lua before DEL | | Duplicate execution after expiry | Idempotency keys; fencing tokens to DB | | Failover loses exclusivity | Redlock debate — know cluster limits | | Clock skew | Prefer TTL leases over wall-clock assumptions |
Redlock (multiple independent Redis nodes) reduces some split-brain scenarios but adds complexity and is debated — many teams prefer a dedicated coordination service or database constraints for hard invariants.
On interviews: never claim "exactly-once" from Redis alone; explain lease duration, safe release, and what happens if the worker dies mid-task.
Common pitfalls: `DEL` without token check; long critical sections under one lock; locks without idempotency; using locks where unique constraints or optimistic locking suffice.
The trade-off is low-latency coordination versus fragile correctness edges — Redis locks coordinate best-effort work distribution; hard invariants belong in the data store with fencing.
Checklist:
- Use `SET NX EX` with unique token.
- Release with compare-and-del in Lua.
- Keep leased work shorter than TTL.
- Pair locks with idempotency or fencing.