System Design Problem

Design a Distributed Lock Manager

Commonly Asked By:UberGoogleAmazonMicrosoft

Interview Setup

Interview Prompt

Design a distributed lock manager that provides mutual exclusion across processes in a cluster. Support lease based locks with TTL, fencing tokens, and high availability without permanent deadlocks.

Clarifying Questions (ask before designing)

QuestionWhy it matters
What correctness level is required: best effort coherency or linearizable mutual exclusion?Redis Redlock is contested for financial paths because etcd or ZooKeeper is required when a stale holder can corrupt durable state.
What is the expected lock acquire QPS and hold duration?A 10,000 acquires/sec workload with 50ms hold times requires connection pooling, whereas long-held locks require background heartbeat renewal. Treat 10,000 QPS as an illustrative workload threshold rather than a universal backend limit.
Does the protected resource support fencing tokens?Without a storage-layer token check, an expired lease holder can still write, making fencing mandatory for linearizable correctness.
Is fairness required or is best effort acquisition acceptable?FIFO wait queues prevent starvation but add coordination overhead compared to try lock with backoff.

Scope

In scope

  • Redlock algorithm
  • Fencing tokens
  • Lease-based locks
  • ZooKeeper vs etcd for coordination
  • Lock contention & fairness
  • Clock drift issues

Out of scope (state explicitly)

  • Application business logic that acquires and releases locks
  • Building ZooKeeper or etcd from scratch (use as coordination backends)
  • Workflow and saga orchestration, as explored in Workflow Orchestration with Temporal, because locks serve as a mutual exclusion primitive rather than a workflow orchestrator
  • Multi region active active lock federation unless staff asks

Functional Requirements

Start by clarifying lock acquisition semantics, TTL lease expirations, and storage-tier fencing tokens before comparing Redis Redlock with consensus engines like ZooKeeper or etcd.

  • Acquire Lock: A client process acquires a named lock with a specified lease duration (TTL) and timeout.
  • Release Lock: The verified lock owner explicitly releases the lock upon completing its critical section.
  • Automatic Expiration: Locks automatically release after their TTL expires to prevent deadlocks when an executor crashes.
  • Mutual Exclusion: At most one process can hold an exclusive lock on a named resource at any given moment.
  • Reentrant Locking (optional): The same owner identity can acquire the lock recursively without causing a self-deadlock.
  • Read-Write Separation (optional): Support concurrent shared read locks alongside exclusive single writer locks.
  • Non-Blocking Try-Lock: Immediate acquisition attempt that returns failure rather than blocking when contested.
  • Lock Inspection: Query which owner holds the lock, when it was acquired, and its remaining lease duration.

Non-Functional Requirements

Interviewers focus heavily on correctness under network partitions. Storage-level fencing tokens prevent stale lock holders from writing, making them essential to establish before discussing Redis SET NX.

  • Linearizable Correctness: For correctness-critical resources whose storage layer enforces fencing, the coordination system must provide linearizable ownership semantics across node failures and network partitions.
  • High Availability: Target high availability (99.99%) through multi node consensus quorums and tested failover, because a lock service outage halts critical transactions.
  • Low Latency: Deliver sub-5ms p99 lock acquisition latency to avoid bottlenecking downstream business logic.
  • Fault Tolerance: Survive node and network partition failures without stranding orphaned locks or granting split-brain ownership.
  • Deadlock Prevention: Automated lease expiration ensures that crashed holders cannot block progress indefinitely.
  • Fairness: Provide optional FIFO ordering for queued lock waiters to prevent starvation under heavy contention.

Capacity Estimations

Distributed lock systems are bounded by correctness rather than high storage volume. Memory demands are modest because lock metadata is compact, but throughput and hold durations dictate whether an in-memory datastore or a consensus cluster fits. The 1M active-lock figure and 100K operations/sec peak are independent capacity assumptions. At a steady 5-second average hold time, 100K new acquisitions/sec would imply roughly 500K concurrently held locks, so the 1M active-lock target represents additional burstiness, other operation types, or peak headroom.

MetricCalculationValue
Active locksGiven concurrent workflows1M
Lock operations / secGiven peak100K
Avg lock hold timeTypical lease5 seconds
Lock metadata sizeowner + token + TTL200 bytes
Total memory1M x 200B200 MB

Architecture Diagram

In the room: evaluate whether optimistic concurrency with version columns or database-level record locks beats a distributed lock manager, because external locks add significant operational complexity.

Walk your interviewer through the architecture along the acquire request path. A client submits a named lock request to the lock manager, which atomically registers the lease with an associated TTL using Redis SET NX EX or an etcd lease. For correctness-critical modes, the coordination backend must also provide a sufficiently ordered source for a monotonically increasing fencing token. Prior to applying state changes, the client presents this fencing token to the protected storage engine, which rejects any operation bearing an outdated token even if a previous lock holder resumes execution. A basic single-node Redis lock does not inherently provide such a fencing token.

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" if lock acquired, or nil if lock is already held by someone else

RELEASE: (must be executed atomically using Lua script)
  if redis.call("GET", KEYS[1]) == ARGV[1] then
      return redis.call("DEL", KEYS[1])
  else
      return 0  -- not the lock holder, so abort deletion
  end

Approach 2: Redlock Algorithm (Distributed Redis Quorum)

Redlock Setup across 5 Independent Redis Nodes:
1. Record initial start timestamp
2. Attempt SET key uuid NX PX ttl on all 5 nodes, preferably in parallel to reduce elapsed time
3. Count successful responses: if >= 3 nodes respond OK and elapsed time < TTL, lock is held
4. If majority quorum is not reached, dispatch DEL commands to all 5 nodes immediately

Fault Tolerance: Can still form a 3-node quorum after 2 host failures, assuming the remaining nodes are reachable and the algorithm's timing assumptions hold

Approach 3: ZooKeeper-Based Lock (Strongest Consensus Correctness)

Lock path: /locks/resource-name/

Algorithm:
1. Create an ephemeral sequential znode under /locks/{resource}/
2. Query all existing child znodes in the path
3. If your znode owns the lowest sequence number, you hold the lock
4. Otherwise, place an event watch on the znode with the next-lower sequence number
5. When that watched znode is deleted, receive event notification and recheck sequence
6. To release: delete your assigned znode
7. If client process crashes: ZooKeeper session expires and auto-deletes the ephemeral znode

Approach 4: etcd-Based Lock (Modern Raft Alternative)

// Grant a 30-second TTL lease
lease, _ := client.Grant(ctx, 30)

// Put key with lease attachment, which auto-expires if heartbeats cease
_, err := client.Put(ctx, "/locks/resource", "owner-id", clientv3.WithLease(lease.ID))

Component Deep Dives

Lock acquisition mechanics, lease safety margins, and storage-tier fencing tokens represent the core architectural pillars of distributed coordination.

Redlock vs ZooKeeper vs etcd: When to Use Which

Selecting the appropriate coordination backend requires matching system safety requirements with latency and operational constraints.

  • Redis SET NX: Single-node execution delivers sub-millisecond latency, but lacks fencing token generation while asynchronous replica replication risks losing active locks during failover. Suitable strictly for advisory coordination.
  • Redlock: Gathers quorum across 5 independent Redis master nodes to survive up to 2 host outages. However, timing anomalies like clock drift and long GC pauses can still cause dual holders, requiring mandatory pairing with storage-layer fencing tokens.
  • ZooKeeper and etcd: Consensus backed engines where ephemeral znodes or lease objects auto release upon session loss. Their ordered metadata can be used to derive fencing tokens, but the protected storage engine must enforce those tokens.

Fencing Tokens (Critical for Correctness)

When the token source is strictly ordered and the protected storage engine enforces it, fencing tokens protect against stale writes during timing anomalies. Without storage enforcement, a stale lock holder whose lease expired during a pause can still issue corrupting writes to persistent storage.

Execution Without Fencing Tokens:
  1. Client A acquires lock with 30s TTL
  2. Client A enters a 60s stop the world garbage collection pause
  3. Lock TTL expires at T=30s
  4. Client B acquires the released lock and writes updated state to storage
  5. Client A wakes up, mistakenly believes it still holds the lock, and overwrites Client B's update

Execution With Fencing Tokens:
  1. Lock service issues monotonic fencing token 33 to Client A
  2. Client A pauses, and the lock expires at T=30s
  3. Client B acquires lock and receives monotonic fencing token 34
  4. Client B writes to storage with token 34, causing storage to record last_seen_token = 34
  5. Client A wakes up and attempts to write with token 33
  6. Storage server evaluates token (33 <= 34) and rejects Client A's stale write

Lock Granularity and Hold Time

Lock granularity directly dictates system concurrency and partition resilience, requiring careful trade-offs between contention overhead and deadlock risk.

  • Coarse-Grained Locking: Locking an entire database table or resource collection simplifies conflict detection but severely constrains throughput.
  • Fine-Grained Row Locking: Locking individual entity rows enables high concurrency, but requires strict global key acquisition ordering to avoid deadlocks when transactions touch multiple rows.
  • Short TTL with Watchdog Heartbeats: Provisioning short lease durations (10 to 30 seconds) coupled with background heartbeat renewal limits the blast radius and recovery delay if a holder crashes.

API Design

Lock Acquisition Interface

The lock manager client exposes explicit lease acquisition, renewal heartbeats, and release primitives with monotonic fencing tokens returned on every successful grant.

TYPESCRIPT
type LockName = string;
type OwnerId = string;
type LockToken = string;
type FencingToken = bigint;
type DurationMs = number;
type EpochMs = number;

export interface LockGrant {
  lockToken: LockToken;
  fencingToken: FencingToken;
  expiresAtMs: EpochMs;
}

export interface DistributedLockClient {
  // Acquire a named lock with lease duration and return a fencing token on success
  acquire(lockName: LockName, ownerId: OwnerId, ttlMs: DurationMs): Promise<LockGrant | null>;

  // Blocking acquisition that waits until granted or waitTimeoutMs elapses
  acquireBlocking(
    lockName: LockName,
    ownerId: OwnerId,
    ttlMs: DurationMs,
    waitTimeoutMs: DurationMs
  ): Promise<LockGrant | null>;

  // Non blocking attempt that returns immediately with acquisition status
  tryAcquire(lockName: LockName, ownerId: OwnerId, ttlMs: DurationMs): Promise<LockGrant | null>;
}

Lock Lifecycle and Inspection

TYPESCRIPT
export interface LockInfo {
  held: boolean;
  ownerId: OwnerId | null;
  fencingToken: FencingToken | null;
  acquiredAtMs: EpochMs | null;
  expiresAtMs: EpochMs | null;
}

export interface DistributedLockManagement {
  // Explicitly release lock only if the caller's lockToken still matches the current owner
  release(lockName: LockName, lockToken: LockToken): Promise<boolean>;

  // Extend the lease only if the caller's lockToken still matches the current owner
  extend(lockName: LockName, lockToken: LockToken, additionalTtlMs: DurationMs): Promise<LockGrant | null>;

  // Query metadata regarding lock holder and remaining lease validity
  info(lockName: LockName): Promise<LockInfo | null>;
}

Common Error Responses

400 Bad Request: invalid input, missing required fields, or malformed JSON payload
401 Unauthorized: missing or invalid authentication token or API key
403 Forbidden: authenticated caller lacks required permissions for this resource
404 Not Found: requested resource ID does not exist
409 Conflict: duplicate write or version conflict, retry with a unique idempotency key
422 Unprocessable Entity: syntactically valid request failed semantic business validation
429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header
500 Internal Error: unexpected server failure, retry safely with an idempotency key
503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff

Data Model

Lock storage models span lightweight Redis lease keys, hierarchical ZooKeeper sequential nodes, and monotonically tracked storage-tier fencing tokens.

Redis Lock Entry

Key:    lock:{resource_name}
Value:  {owner_id}:{uuid_token}    // identifies verified owner and token
TTL:    30 seconds (configured lease window)

ZooKeeper Lock Structure

/locks/
  /resource-A/
    /lock-0000000001    (ephemeral sequential znode owned by leader)
    /lock-0000000002    (ephemeral sequential znode watching node 0001)
  /resource-B/
    /lock-0000000001    (ephemeral sequential znode)

Fencing Token Enforcement (Storage Engine)

SQL
-- When writing to storage, include the monotonic fencing_token
UPDATE user_balance
SET balance = balance - 50,
    last_fencing_token = 42
WHERE user_id = 'user_123'
  AND last_fencing_token < 42;

-- If rows affected == 0, the write was rejected because a newer lock holder already updated the record.

Fault Tolerance

The Martin Kleppmann Problem (Zombie Lock Holders)

A distributed lock with a lease duration cannot guarantee mutual exclusion if a client experiences an unexpected delay, such as a major garbage collection pause, network freeze, or process paging. If the lease expires while the client is paused, another client acquires the lock, resulting in dual active writers.

Scenario:
1. Client A acquires lock (TTL = 30s)
2. Client A enters a long garbage collection pause (60s)
3. Lock auto-expires at 30s due to lease expiration
4. Client B acquires the same lock
5. Client A wakes up and assumes it still holds valid lease ownership
6. Both Client A and Client B concurrently write to the shared resource, causing silent data corruption

Solution: Monotonically Increasing Fencing Tokens
  T=0:   Client A acquires lock with fencing_token = 33
  T=31:  Client B acquires lock with fencing_token = 34
  T=60:  Client A resumes and issues write with fencing_token = 33
  Storage engine validation: fencing_token 33 < last_seen 34 (write rejected)
ConcernSolution
Lock holder crashesTTL auto-expires the lock lease, and ZooKeeper ephemeral znodes are automatically deleted upon session loss
Network partitionZooKeeper and etcd require quorum for linearizable lock updates. Redlock relies on quorum and timing assumptions and does not provide consensus-level split-brain guarantees
Clock drift (Redlock)Enforce bounded clock drift assumptions and deduct a drift safety margin from the TTL validity window
GC pause extends past TTLStorage-level fencing tokens reject stale writes from holders whose leases expired during a pause
Split-brain multiple holdersConsensus-backed coordination engines (ZooKeeper and etcd) enforce strict linearizable ownership
DeadlockBounded lease TTLs guarantee automatic lock reclamation if an executor crashes before releasing

Choosing the Right Approach

Select the lock coordination tier based on the business consequence of concurrent execution and latency tolerance.

ScenarioRecommendation
Efficiency lock (cache dedup, non-critical)Single Redis SET NX EX
Correctness lock (billing, inventory)ZooKeeper or etcd with storage enforced fencing
Advisory coordination where timing risk is acceptableRedlock (5 independent Redis instances)

Additional Considerations

Lock Renewal and Heartbeat Patterns

Redlock quorum coordination, lease extension, and lock acquisition ordering can help reduce deadlocks and contention during long running background tasks, but Redlock does not provide consensus semantics.

Lock acquired with TTL = 30s
Background thread: Every 10s, extend lock lease by 30s
If holder crashes: heartbeat thread terminates and the lock expires naturally
(Redisson implements this pattern as RLock)

Read-Write Locks

Read-write locks permit concurrent read access while ensuring exclusive write access.

  • Read lock: Multiple readers are allowed simultaneously without blocking each other.
  • Write lock: Exclusive access is enforced, blocking both readers and other writers.
  • ZooKeeper implementation: Read locks do not block other reads, while a write lock blocks all subsequent operations until released.

Distributed Semaphore

A distributed semaphore generalizes locks by allowing up to N concurrent leaseholders rather than restricting access to a single client. In ZooKeeper, clients acquire a permit only if the total count of active child nodes remains strictly below N. In Redis, permit acquisition must use an atomic check and decrement, together with an owner token and lease cleanup so a crashed holder cannot permanently consume a permit.

REDIS
ACQUIRE:
  Atomically verify available_permits > 0, then decrement available_permits
  Store an owner token with a lease for the acquired permit

RELEASE:
  Validate the owner token, then increment available_permits

Recovery:
  Expired permit leases are reclaimed so crashed holders do not leak capacity

Advisory vs. Mandatory Locks

Distributed locks differ in how strictly compliance is enforced across systems.

  • Advisory: Cooperative locking where clients must voluntarily query the lock manager before acting on the shared resource.
  • Mandatory: System-enforced locking where the storage engine (such as database row locks) rejects uncoordinated modifications directly.
  • Distributed lock managers operate almost exclusively as advisory mechanisms, making client side discipline and fencing tokens essential.

When NOT to Use Distributed Locks

Locks add latency, failure modes, and operational toil. Prefer alternatives when contention is low or ownership is naturally partitionable: idempotent consumers with dedup keys,optimistic concurrency (version column / CAS), leader election for singleton cron, or partition ownership (Kafka partition = single writer). Use locks only when multiple workers must exclude each other on the same mutable resource and fencing tokens are feasible.

Related Problems and Core Concepts

Distributed coordination and state management intersect with Distributed Job Scheduler and Workflow Orchestration (Temporal). High-concurrency inventory reservation and reservation guarantees are analyzed in Flash Sale System and Ticketing System. For underlying storage and caching mechanisms, refer to Key-Value Store and Distributed Cache. To master the theoretical consensus and coordination primitives, review CAP Theorem and Consistency Models, Replication, Failover, and Leader Election, Redis Patterns for Interview Systems, Distributed Transactions (2PC vs. Saga), and System Design Interview Patterns.

Interview Walkthrough

  • 25-minute cut

    Skip arch50 and arch75 depth unless interviewing for a staff-level role.

    • FR/NFR and lock use cases (3 min)
    • Redis SET NX EX vs Redlock vs ZooKeeper (8 min)
    • Lease TTL and heartbeat renewal (5 min)
    • Fencing tokens for stale holder prevention (5 min)
    • Optimistic concurrency as lock alternative (4 min)
  • Start with use cases: inventory decrement, leader election, and cron singleton execution, noting that not every coordination need requires a lock.
  • Compare Redis Redlock against ZooKeeper or etcd ephemeral sequential nodes, weighing low-latency throughput against consensus backed fencing-token guarantees.
  • Explain lease TTL and heartbeat renewal so crashed holders auto-release without manual operator intervention.
  • Cover fencing tokens to prevent stale lock holders from writing after losing the lease.
  • Mention when optimistic concurrency (version column) beats a distributed lock for low contention writes.
  • Address the common pitfall of using Redis locks without fencing tokens, where a paused process can resume and corrupt shared state after lease expiry.

Engineering Trade-offs

Comparison Summary

Your interviewer will push on lock implementation correctness. Walk through Redlock, etcd, and ZooKeeper and justify your selection based on data integrity and latency requirements.

FeatureRedis SET NXRedlockZooKeeperetcd
ConsistencySingle node only. replica failover can lose locksNo consensus linearizability guaranteeStrong (ZAB consensus)Strong (Raft consensus)
Latency< 1 ms~5-10 ms~5-20 ms~5-10 ms
ComplexityMinimalModerateHigh (JVM tuning and session management)Moderate
FairnessNoNoApplication implemented with sequential znodesApplication implemented with revision-ordered queue
Auto-releaseTTLTTLEphemeral znode session timeoutLease TTL heartbeat
Fencing tokenExternal ordered source requiredExternal ordered source requiredApplication derived from ordered znode transaction or sequenceApplication derived from monotonic revision

Redlock Algorithm: Step-by-Step

Lock acquisition for resource "inventory:item-42":
  1. Generate identifiers:
     lock_key = "lock:inventory:item-42"
     owner_id = UUID "abc-123"
     ttl = 10000ms

  2. Request acquisition across all 5 independent Redis nodes in parallel:
     SET lock_key abc-123 NX PX 10000

  3. Evaluate quorum:
     3 out of 5 instances respond OK (majority quorum achieved)

  4. Compute validity window:
     elapsed = 50ms
     validity = ttl - elapsed - clock_drift_bound
     validity > 0: LOCK ACQUIRED (lease valid for remaining validity window)

ZooKeeper Lock: Why It's Stronger (and Slower)

Why ZooKeeper provides strict correctness:
  - ZAB consensus: CREATE operations are linearizable, ensuring total order guarantees
  - Ephemeral nodes: if a client crashes, the session expires, the znode is deleted, and the lock releases automatically
  - Heartbeat-driven liveness: session keepalive heartbeats replace fragile wall-clock TTLs
  - Monotonic ordering: avoids clock drift issues by relying on zxid logical sequence counters

Operational Latency:
  Acquire: 2 ZooKeeper round-trips (~15-20ms)
  Release: 1 ZooKeeper round-trip (~5-10ms)
  Compare: Redis SET NX in-memory command (~0.5ms)

Lock Renewal: The Watchdog Pattern

main_thread:
  lock = acquire("resource-X", owner_id, ttl=30s)
  watchdog = start_renewal_thread(lock, renewal_interval=10s)
  do_critical_work()  // may take 45 seconds
  watchdog.stop()
  lock.release()

renewal_thread (runs every 10 seconds):
  success = extend_lock(lock.key, lock.lockToken, new_ttl=30s)
  if not success: signal_main_thread_to_abort()

Decision Tree: When to Use Each Approach

1. Is absolute correctness required (such as financial transactions or inventory decrement)?
   - YES: Choose ZooKeeper or etcd with monotonic fencing tokens.
   - NO: Proceed to step 2.

2. Is ultra low latency critical (< 1ms)?
   - YES: Choose Single Redis SET NX (acceptable if underlying operations are idempotent).
   - NO: Proceed to step 3.

3. Is losing lock state during Redis master failover acceptable?
   - YES: A single Redis SET NX lock can be used when duplicate execution is acceptable.
   - NO: Use a consensus-backed coordination system such as ZooKeeper or etcd, because Redlock provides fault tolerance under its timing assumptions rather than consensus semantics.

Summary Guide:
   - Non-critical deduplication: Single Redis instance without fencing tokens
   - Database mutation protection: ZooKeeper or etcd with storage enforced fencing
   - Financial ledgers and stateful resources: ZooKeeper or etcd with end to end fencing enforced by the storage layer

💬Review

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...