Why You Need a Distributed Lock
Your app runs on ten servers. A nightly job sends invoice emails. Without coordination, all ten servers run it, and every customer gets ten emails. Or two servers process the same payment refund at once.
On one machine, you'd use a mutex. Across many machines, you need a distributed lock: a way for exactly one process, anywhere in the system, to do something at a time.
They're easy to get almost right, and the "almost" is where data gets corrupted.
๐ฏ Quick Answer (30-Second Read)
- A distributed lock lets one process across many machines own a resource at a time.
- Simple Redis lock:
SET lock:job <unique-id> NX PX 30000, then release only if the value is still yours. - Always use an expiry so a crashed process doesn't hold the lock forever.
- But expiry creates a risk: a slow process may still be working after its lock expires. Fencing tokens protect against that.
- Need strong correctness? Use a consensus system (etcd, ZooKeeper) or your database's locks. For efficiency-only locks, Redis is usually fine.
Two Kinds of Locks
Avoid doing the same work twice (e.g. regenerating a report). If it occasionally fails, you waste some effort. A simple Redis lock is fine.
Two holders would corrupt data or move money twice. You need stronger guarantees: fencing tokens, consensus or database transactions.
This distinction, popularised by Martin Kleppmann, decides which tool you should use.
The Simple Redis Lock
import uuid, redis
r = redis.Redis()
lock_id = str(uuid.uuid4())
# Acquire: only if not already set (NX), auto-expire after 30s (PX)
if r.set("lock:invoice-job", lock_id, nx=True, px=30_000):
try:
send_invoices()
finally:
# Release ONLY if we still own it (atomic check-and-delete)
r.eval("""
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
end
return 0
""", 1, "lock:invoice-job", lock_id)
else:
print("Another worker is running the job")Why each part matters:
NX: only one process can create the key.PX 30000: if the holder crashes, the lock frees itself.- Unique value + check-on-release: without this, a process whose lock already expired could delete someone else's lock.
The Dangerous Case: Pauses and Expiry
No lock with a timeout can stop this by itself, because Worker A doesn't know it was paused.
The Fix: Fencing Tokens
Each time the lock is granted, the lock service hands out an increasing number. The storage system rejects writes with a lower token than one it has already seen.
What About Redlock?
Redlock is an algorithm that acquires the lock on a majority of several independent Redis nodes, to survive one Redis server failing. It's described in the Redis documentation. Martin Kleppmann argued it still relies on timing assumptions and doesn't provide fencing tokens, so it isn't safe for correctness-critical locks. Redis's creator, Salvatore Sanfilippo, published a rebuttal. The practical takeaway: use Redis locks for efficiency, and stronger tools when correctness is at stake.
Other Options
| Tool | How | Best for |
|---|---|---|
| Postgres advisory locks | SELECT pg_try_advisory_lock(42) |
Apps already on Postgres; job coordination |
| Postgres row locks | SELECT ... FOR UPDATE SKIP LOCKED |
Job queues where workers claim rows |
| etcd / ZooKeeper | Leases and consensus, with revision numbers usable as fencing tokens | Correctness-critical locks, leader election |
| Redis | SET NX PX |
Fast efficiency locks |
Often the best lock is no lock: a database unique constraint, an idempotency key, or a conditional update (UPDATE ... WHERE version = 5) can prevent duplicates without any separate lock service. (See Idempotency Explained.)
Checklist
- Decide: efficiency lock or correctness lock?
- Always set an expiry
- Use a unique owner value and atomic release
- Keep work inside the lock short, or extend the lock while working
- Use fencing tokens (or database constraints) when correctness matters
- Consider whether idempotency or a unique constraint removes the need for a lock
Frequently Asked Questions
How do you implement a distributed lock in Redis?
Use SET key unique-value NX PX milliseconds to acquire the lock only if it doesn't exist, with an automatic expiry. Release it with a Lua script that deletes the key only if its value still matches your unique value, so you never release another process's lock.
What is a fencing token?
A fencing token is a number that increases each time a lock is granted. The protected resource remembers the highest token it has seen and rejects requests with lower tokens, which prevents a process whose lock expired during a pause from overwriting newer data.
Is Redlock safe?
It's debated. Martin Kleppmann argued Redlock depends on timing assumptions and lacks fencing tokens, making it unsafe for correctness-critical uses. Redis's creator disagreed. Most engineers use Redis locks for efficiency and consensus systems or database guarantees for correctness.
Conclusion
Distributed locks let one process at a time act across many machines. A Redis lock with expiry and safe release is enough to avoid duplicate work. When correctness matters, add fencing tokens or use etcd, ZooKeeper or your database's own locking, and consider whether idempotency can remove the need for a lock entirely.
Sources
- Redis docs: Distributed locks with Redis
- Martin Kleppmann: How to do distributed locking (2016)
- Salvatore Sanfilippo: Is Redlock safe?
- PostgreSQL docs: Advisory locks
Last verified: 2 November 2026.
Related reads: Idempotency Explained ยท CAP Theorem Explained ยท Redis Explained ยท Message Queue Explained
I write about system design and how real products are built. Follow on LinkedIn or X, or invite me to speak.