System Design Problem

Design a Distributed Lock Manager

Commonly Asked By:UberGoogleAmazonMicrosoft

  • Acquire lock: A process acquires a named lock with a timeout (lease duration)
  • Release lock: The lock holder explicitly releases the lock
  • Auto-release: Lock automatically released after TTL expires (prevents deadlocks from crashed holders)
  • Mutual exclusion: At most one process holds the lock at any time
  • Reentrant locks (optional): Same process can acquire the same lock multiple times
  • Read-Write locks (optional): Multiple readers OR one writer
  • Try-lock: Non-blocking attempt to acquire; return immediately if unavailable
  • Lock metadata: See who holds the lock, when it was acquired, when it expires
Loading...

Approach 1: Single Redis Node (Fastest but Weakest)

ACQUIRE:  SET lock:{name} {owner_id} NX EX {ttl_seconds}
  NX = only set if Not eXists (atomic), EX = expire after ttl_seconds
  Returns "OK" → lock acquired, nil → lock held by someone else

RELEASE:  (must be atomic — use Lua script)
  if redis.call("GET", key) == owner_id then
      return redis.call("DEL", key)
  else
      return 0  -- not the lock holder; don't delete!
  end

Approach 2: Redlock Algorithm (Distributed Redis: Safer)

Redlock — 5 Independent Redis Nodes:
1. Record start time
2. Attempt SET key uuid NX PX ttl on all 5 nodes (in parallel)
3. Count successes: if >= 3 nodes succeed AND elapsed time < TTL → lock held
4. If quorum not reached, release on all nodes immediately

Survives up to 2 out of 5 Redis instances failing

Approach 3: ZooKeeper-Based Lock (Strongest Correctness)

Lock path: /locks/resource-name/
Algorithm:
1. Create ephemeral sequential znode under /locks/{resource}/
2. Get all children
3. If your znode has the lowest sequence number → you hold the lock
4. If not → set a watch on the znode with the next-lower sequence number
5. When that znode is deleted → you're notified → recheck
6. To release: delete your znode
7. If client crashes: ephemeral znode auto-deleted

Approach 4: etcd-Based Lock (Modern Alternative)

GO
lease, _ := client.Grant(ctx, 30)  // 30-second TTL lease
_, err := client.Put(ctx, "/locks/resource", "owner-id", clientv3.WithLease(lease.ID))