Five Redis Distributed Lock Traps and the Go Code That Survives Them
Redis is the default distributed lock for most teams, but the gap between a `SETNX` snippet and a lock that survives timeouts, goroutine leaks, and failovers is wide enough to cause data corruption. The five patterns here—especially the fencing token—are the difference between a lock that mostly works and one that actually protects shared state.
Building a Redis distributed lock in Go starts with an atomic `SET key value NX EX time` and a unique token to prevent one goroutine from deleting another's lock. The unlock path must run a Lua script that checks the token and deletes in a single atomic step. A watchdog goroutine, driven by a ticker and tied to a `context.Context`, renews the lock's TTL so a slow operation doesn't lose the lock mid-flight.
Go's missing goroutine ID forces a different reentrancy design: pass the lock token through the call chain or stash it in `context.Context`. At the Redis level, a hash structure keyed by a UUID-plus-trace-ID field tracks reentry counts. The deepest trap is master-slave failover—an async replication gap can hand the same lock to two callers. Redlock doesn't fix it because it trusts system clocks that can jump.
For strong consistency, the answer is to leave Redis behind for etcd or ZooKeeper and to adopt fencing tokens. Every write carries a monotonically increasing token; downstream systems reject stale tokens, making the lock's expiry irrelevant to correctness.
Go's deliberate hiding of goroutine IDs is not an obstacle to work around with assembly hacks—it forces a design where identity travels with the request context, which is cleaner and more testable.
The watchdog pattern is only as safe as its cancellation wiring; a leaked goroutine that keeps renewing a lock after the caller has moved on is a silent correctness bug.
Martin Kleppmann's critique of Redlock is a reminder that distributed correctness cannot be bolted onto an eventually-consistent data store with client-side clocks—the model itself must provide linearizability.
Fencing tokens invert the trust model: instead of trying to make the lock perfect, you make the resource itself reject stale operations, which is a cheaper and more reliable guarantee.