Skip to content
Learn/Distributed Locking

Distributed Locking

Coordinating exclusive access across processes

Key Takeaways

  • SET NX (set-if-not-exists) is the atomic primitive for lock acquisition — it guarantees only one client wins
  • Always set a TTL on locks so crashed clients don't hold locks forever (deadlock prevention)
  • Use a Lua script for release to make the ownership-check-and-delete atomic — two separate commands have a race condition
  • Store a unique owner ID as the lock value so only the holder can release it (safety property)

What is a Distributed Lock?

A distributed lock provides mutual exclusion across multiple processes or machines. When two instances of your service both try to process the same payment, send the same notification, or write to the same resource, a distributed lock ensures only one proceeds while the other waits or backs off.

Why It Matters

Without coordination, concurrent access to shared resources causes:

  • Double processing: two workers handle the same job
  • Data corruption: concurrent writes overwrite each other
  • Duplicate side effects: sending the same email twice, charging a card twice

In a single-process application, you'd use a mutex or synchronized block. In a distributed system with multiple instances, you need a lock backed by shared state — typically Redis, ZooKeeper, or a database.

How Redis Locks Work

The simplest Redis lock uses three properties:

  • Mutual exclusion: SET key value NX — only succeeds if the key doesn't exist
  • Liveness (deadlock prevention): SET key value PX ttl NX — key auto-expires after the TTL
  • Safety (ownership): Store a unique owner ID as the value, verify it before releasing

Acquire

code
SET lock:resource-123 owner-abc PX 5000 NX

Returns OK if acquired, nil if already held.

Release (Lua script for atomicity)

lua
if redis.call(class="text-pass">"get", KEYS[1]) == ARGV[1] then
  return redis.call(class="text-pass">"del", KEYS[1])
else
  return 0
end

The Lua script is critical — without it, there's a race condition between checking the owner and deleting the key.

Common Mistakes

  • Using DEL without checking ownership: Another client might have acquired the lock between your GET and DEL, and you'd release *their* lock
  • Forgetting the TTL: If a client crashes while holding the lock, no other client can ever acquire it — a permanent deadlock
  • Using SETNX + EXPIRE as two commands: There's a gap between SETNX and EXPIRE where the client could crash, leaving a lock with no expiry. Use SET key value PX ttl NX as a single atomic command
  • Assuming locks survive Redis restarts: By default, Redis keys are lost on restart. For critical locks, consider Redis persistence or a more robust system like ZooKeeper

Practice This Concept

Apply what you've learned by solving this challenge.

SystemTrials — Backend Engineering Practice That Tests Like Production