{"post":{"slug":"distributed-locks-explained-2026","title":"Distributed Locks Explained in 2026: How They Work","excerpt":"Distributed locks coordinate exclusive access across machines, not just threads. This guide explains leases, fencing tokens, and how to pick between Redis, etcd, and a plain database lock.","category":"code","date":"July 31, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">A</span> distributed lock coordinates exclusive access to a resource across multiple processes or machines, the same way a mutex coordinates threads inside one process, except the failure modes are worse, because processes crash, networks partition, and clocks drift independently of each other. The core guarantee you actually want is simple to state and surprisingly hard to deliver: at most one caller holds the lock at any moment, and a caller that has lost the lock cannot keep acting as though it still holds it. Almost every real incident with distributed locks traces back to that second half being ignored.\n\n## How it works\n\nThe basic protocol:\n\n```\n1. Client asks: \"give me the lock for resource X, for 30 seconds\"\n2. Lock service grants it (or refuses if already held)\n3. Client does its work\n4. Client releases the lock (or it expires after 30s if the client never returns)\n```\n\n```python\nimport redis\n\nr = redis.Redis()\n\nacquired = r.set(\"lock:invoice-42\", \"worker-7\", nx=True, ex=30)  # SET NX EX: only if unset, 30s TTL\n\nif acquired:\n    try:\n        process_invoice(42)\n    finally:\n        r.delete(\"lock:invoice-42\")  # release\nelse:\n    # someone else holds it; retry later or skip\n    pass\n```\n\nThis basic version has a real gap: if `process_invoice` runs longer than 30 seconds, the lease expires while the worker is still running, another worker acquires the same lock, and now two workers are processing invoice 42 concurrently.\n\n## Why a TTL alone is not enough\n\nThe fix is a fencing token, a monotonically increasing number handed out with each lock grant. The resource being protected, whether a database row, a file, or a downstream API, checks that the token is higher than the last one it saw, and rejects writes from a stale holder even if that holder still believes it holds the lock.\n\n| Approach | Protects against | Fails against |\n| --- | --- | --- |\n| Lock with no TTL | Nothing on crash, hangs forever | Any crash or forgotten release |\n| Lock with TTL only | Crashed holders, lease expires | A paused holder that resumes after expiry and still writes |\n| Lock with TTL and fencing token | Stale writes after expiry | Nothing, if the protected resource enforces the token |\n\nWithout fencing, a TTL only bounds how long you wait for a crashed holder. It does not stop a slow-but-alive holder from acting after its lease is gone.\n\n## Choosing an implementation\n\n| Option | Consistency model | Good fit |\n| --- | --- | --- |\n| Single Redis instance (`SET NX EX`) | Best-effort, can lose the lock on failover | Non-critical, low-stakes coordination |\n| Redlock (multiple Redis instances) | Stronger, still debated for edge cases | Medium-stakes, already running Redis |\n| etcd or ZooKeeper | Strong, backed by a proven [consensus algorithm](/blog/consensus-algorithms-explained-2026) | High-stakes coordination, [leader election](/blog/leader-election-explained-2026) |\n| Database row lock (`SELECT ... FOR UPDATE`, advisory lock) | As strong as your database | Already have a relational database in the path, such as protecting a [migration](/blog/how-to-write-a-database-migration-2026) from running twice |\n\nFor most application-level coordination, such as preventing a scheduled job from double-running or deduplicating a webhook handler, a database advisory lock or single Redis instance is enough. Reach for etcd or ZooKeeper when the cost of two holders acting at once is genuinely severe, such as a financial double-spend or split-brain writes.\n\n## Common mistakes\n\n**No TTL on the lock.** If the holder crashes before releasing, the lock is held forever and every other caller is blocked indefinitely. Always set an expiry.\n\n**No fencing token.** A TTL bounds the wait but does not stop a delayed, still-alive holder from writing after its lease has effectively passed to someone else, as shown in the table above.\n\n**Treating \"I have the lock\" as \"I will finish before it expires.\"** Long-running work under a lock needs either a TTL with generous headroom, a lock-renewal heartbeat mechanism, or a redesign to shrink the critical section.\n\n**Using a distributed lock where a database transaction would do.** If everything you're protecting lives in one database, a transaction or `SELECT ... FOR UPDATE` is simpler and just as correct; reserve a dedicated lock service for coordination that spans multiple systems.\n\n## FAQ\n\n**Is Redlock safe?**\nIt's debated. Redlock is reasonable for reducing the chance of concurrent access in non-critical paths, but a well-known critique from Martin Kleppmann shows it can fail under clock jumps or long pauses. For correctness-critical locking, use a consensus-backed store, such as etcd or ZooKeeper, with fencing tokens instead.\n\n**What's the difference between a distributed lock and leader election?**\nThey're related mechanisms with different lifetimes. A lock is typically held briefly for one operation; [leader election](/blog/leader-election-explained-2026) elects one long-lived coordinator that stays leader until it fails or steps down.\n\n**Can I just use a database unique constraint instead of a lock?**\nFor many cases, yes. A unique constraint plus a transaction achieves the same \"only one winner\" outcome without a separate lock service, and it's simpler to reason about.\n\n**How long should a lock's TTL be?**\nLong enough to comfortably cover the expected work, short enough that a crash doesn't block others for an uncomfortable amount of time. Pair a modest TTL with heartbeat renewal for work whose duration is variable.\n\n## Where to go next\n\n- [Leader election explained in 2026](/blog/leader-election-explained-2026)\n- [Consensus algorithms explained in 2026](/blog/consensus-algorithms-explained-2026)\n- [How to write a database migration in 2026](/blog/how-to-write-a-database-migration-2026)","hue":314,"metaTitle":"Distributed Locks Explained 2026: Redis, etcd","metaDescription":"Distributed locks explained for 2026: what they guarantee, Redlock and fencing tokens, and when a database lock beats a dedicated lock service.","keywords":["distributed locks explained 2026","redlock algorithm","fencing tokens distributed lock","redis distributed lock","etcd distributed lock","mutual exclusion distributed systems","database advisory lock","distributed lock service comparison","preventing race conditions across services"],"tldr":["**A distributed lock gives mutual exclusion across processes, not just threads** — only one caller across your entire fleet holds the lock at a time.","**Every distributed lock needs a lease, or TTL** — a lock without an expiry will hang forever if the holder crashes before releasing it.","**A lease alone isn't safe against pauses and clock drift** — use a fencing token so a stale lock holder can't clobber state after its lease has effectively expired.","**Redis is the common pragmatic choice; etcd and ZooKeeper are the strict-consistency choice** — pick based on how much correctness a false lock grant would actually cost you.","**Skip:** assuming a lock acquired means the holder is still alive and fast — a pause or network partition can make a held lock effectively abandoned while your code still thinks it's safe."],"readTime":"4 min read","wordCount":908},"related":[{"slug":"advisory-locks-explained-2026","title":"Advisory Locks: Mutual Exclusion Without a Lock Table","excerpt":"You need exactly one worker running a job. A lock row in a table almost works and leaks on crashes. Advisory locks solve it properly using the database you already have.","category":"code","date":"August 14, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":308},{"slug":"leader-election-explained-2026","title":"Leader Election Explained in 2026: How It Works","excerpt":"Leader election lets a cluster agree on a single coordinator without splitting into two. This guide explains how the mechanism works and why most systems delegate it to etcd or ZooKeeper.","category":"code","date":"August 6, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":316},{"slug":"what-is-a-microservice-2026","title":"What is a microservice in 2026? The small-service approach explained","excerpt":"A microservice is one small, independently deployable program that does a single job and talks to others over the network. This guide explains how the pattern works and when it actually pays off.","category":"code","date":"June 23, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":205}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"design-patterns-still-worth-knowing-2026","title":"Design Patterns Still Worth Knowing in 2026","excerpt":"Not every Gang of Four pattern survived contact with modern languages and frameworks. An opinionated 2026 verdict on which still earn their complexity, which got absorbed, and which to retire.","category":"code","date":"July 31, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":320},"older":{"slug":"distributed-transactions-microservices-2026","title":"Distributed Transactions in Microservices: 2026 Guide","excerpt":"Splitting a system into services means splitting its data too, so an atomic commit is no longer free. Here is how to choose between blocking coordination, compensation, and event-driven design.","category":"code","date":"July 31, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":304}}