Redis vs Memcached for Distributed Locking
Distributed locks are leases, not mutexes. The store you pick determines how the lease is granted, how it survives node failure, and what happens when the holding process pauses for longer than the lease. Redis gives you a fast, simple primitive with documented limits. Memcached gives you the same primitive without the multi-node safety net.
The lock primitive in two stores
A distributed lock has three jobs: grant exclusive access to one caller, prevent that caller from holding it forever if it crashes, and release cleanly when the caller is done. Redis ships a single command that does the first two atomically: SET resource_name unique_token NX PX 30000. NX means only set if the key does not exist (mutual exclusion). PX 30000 means expire in 30 seconds (the lease). unique_token is a UUID or per-process identifier you use later to verify ownership.
Memcached's add key flags exptime bytes is the same shape semantically: ADD only succeeds if the key is absent, and the exptime parameter is the lease. The difference is what happens around it. Memcached has no replication, no Sentinel-style failover, no Cluster, no multi-key Lua scripting. If the single Memcached node fails, every lock disappears at the same moment. If you partition the network, callers in the minority partition cannot release locks they hold, and callers in the majority partition will eventually take them.
Redis with a single primary has the same single-point-of-failure profile, but Redis Sentinel and Redis Cluster offer failover, and the Redlock algorithm offers a quorum-based multi-node variant. None of these make Redis safe for linearizable locks (that takes Raft or Paxos), but they do reduce the blast radius. The Redis docs publish the canonical pattern and explicitly tell you what it is and is not for.
Safe lock acquire and release
token := uuid.NewString()
ok, _ := rdb.SetNX(ctx, "lock:order:42",
token, 30*time.Second).Result()
if !ok { return errors.New("locked") }
defer func() {
// Lua script: release only if we still own it
script := `if redis.call("GET", KEYS[1]) == ARGV[1]
then return redis.call("DEL", KEYS[1])
else return 0 end`
rdb.Eval(ctx, script,
[]string{"lock:order:42"}, token)
}()token := uuid.NewString()
err := mc.Add(&memcache.Item{
Key: "lock:order:42",
Value: []byte(token),
Expiration: 30, // seconds
})
if err == memcache.ErrNotStored {
return errors.New("locked")
}
defer func() {
// Get-then-delete races against expiry
item, err := mc.Get("lock:order:42")
if err == nil && string(item.Value) == token {
mc.Delete("lock:order:42")
// Window: lock can expire between Get and Delete
}
}()Redlock and the Aphyr / Kleppmann critique
Single-node Redis locks fail when the node fails. Salvatore Sanfilippo published Redlock in 2014 as a multi-node algorithm: acquire the same lock on N independent Redis primaries (typically 5), require a majority quorum, and account for clock drift when computing the effective lease. Redlock is the closest Redis gets to a consensus protocol without actually being one. Most production Redis lock libraries (Redisson for Java, redlock-py, node-redlock) implement it.
In early 2016 Martin Kleppmann published How to do distributed locking arguing Redlock made unrealistic synchrony assumptions: it assumes bounded clock drift, bounded process pauses, and bounded network delay, none of which hold in practice. A long GC pause could let a lock expire while the holder still thinks they have it, then a second holder acquires, and now two processes are in the critical section. Kleppmann's recommended fix is fencing tokens: every lock grant returns a monotonically increasing token, and the protected resource checks the token before applying writes. Redis cannot natively produce monotonic fencing tokens without a single source of truth, which is exactly what Redlock was avoiding.
Sanfilippo responded that Redlock is documented as a fast mutex with the failure modes Kleppmann describes, and that the answer for systems that need linearizability is not Redis but ZooKeeper or etcd. The pragmatic 2026 stance: use Redis locks when an occasional double-acquire is recoverable (idempotent job runners, leader election with idempotent leader work, cache stampede protection where the cost of two cache rebuilds is paying for one extra). Use a consensus system when correctness depends on at-most-one execution.
Real-world: where Redis locks live
Redis locks are the default for cron deduplication in many production stacks. If you run a Kubernetes deployment with three replicas of a scheduler pod and you want the cron to fire exactly once per minute (not three times), the canonical pattern is each replica tries SET NX PX once per tick, the winner runs the job, the others sleep. The cost of an occasional double-fire is bounded if the job itself is idempotent. Sidekiq, BullMQ, Celery, and most other queue libraries ship optional Redis-backed unique-job locks for this reason.
Cache stampede protection is another. When a hot cache key expires, you do not want ten thousand requests to all rebuild the cache in parallel and overwhelm the underlying database. The pattern is: requests check the cache, miss, then try to acquire a short-lived lock on a rebuild key. The lock winner rebuilds, the losers wait or serve a stale value. Redis SET NX PX works perfectly here because the cost of two rebuilds is one wasted database query, not a correctness violation.
For money movement, ledger updates, exactly-once message delivery without idempotency keys, financial trade execution, and similar correctness-critical workloads, the recommendation across the industry is to either use a consensus store (ZooKeeper Curator recipes, etcd v3 transactions, Consul sessions) or to design the protected resource itself to be idempotent so the lock becomes a performance optimisation rather than a correctness boundary. Memcached for any of these is not a serious option; even Redis is the wrong answer if you need formal safety.
FAQ
Is Redlock safe?
Safe enough for many production uses, but not safe for workloads where correctness depends on the lock being held by exactly one process at all times (e.g. money transfer, exactly-once writes to a database without idempotency keys). Martin Kleppmann and Aphyr published critiques in 2016 arguing that Redlock makes synchrony assumptions that fail under GC pauses and clock skew. Salvatore Sanfilippo (antirez) responded that Redlock is a fast mutex, not a consensus protocol, and the limitation is documented. Use it for what it is.
What is SET NX PX?
Redis SET key value NX PX 30000 sets key to value only if key does not exist (NX) with a 30-second expiry (PX milliseconds). One atomic operation creates the lock, claims it, and sets its lease. Releasing the lock requires a Lua script that verifies the lock value before DEL, to avoid releasing a lock you no longer hold.
Can Memcached do locks?
Yes, via the add command which only succeeds if the key does not exist. memcached add key 0 30 4 then check return code. The semantics are similar to SET NX, but Memcached has no replication, no Redlock-style multi-node consensus, and no built-in Lua scripting for safe release. The lock is fragile: if the single Memcached node fails, all locks vanish silently.
Why is releasing a lock dangerous?
If process A acquires a lock with 30-second lease, hits a GC pause for 35 seconds, then wakes up and DELs the lock, A has just released a lock that process B is now holding. The fix is to store a unique token as the lock value (UUID) and DEL only if the current value matches your token. Lua script for atomicity.
Should I use ZooKeeper or etcd instead?
If correctness matters more than latency, yes. ZooKeeper and etcd are consensus systems (Zab, Raft) that provide linearizable distributed locks with explicit fencing tokens. Redis is faster and simpler but trades formal guarantees. The right answer depends on the cost of a double-acquire in your domain.