Skip to content
advanced Phase 86 · Distributed Advanced

Distributed Locks

Distributed locks with Redis, lock contention, lock timeout, and distributed coordination

45m
0 problems
Topic Progress 0%

Redis Distributed Locks

Lock Acquisition

class RedisLock {
    public function __construct(
        private Redis $redis,
        private string $lockKey,
        private int $timeout = 10,
        private int $retryDelay = 100,
        private int $maxRetries = 3
    ) {}
    
    public function acquire(): bool {
        $token = bin2hex(random_bytes(16));
        
        for ($i = 0; $i < $this->maxRetries; $i++) {
            // SET NX EX = set if not exists with expiry
            $acquired = $this->redis->set(
                $this->lockKey,
                $token,
                ['NX', 'EX' => $this->timeout]
            );
            
            if ($acquired) {
                $this->token = $token;
                return true;
            }
            
            usleep($this->retryDelay * 1000);
        }
        
        return false;
    }
    
    public function release(): bool {
        // Only release if we own the lock
        $script = <<<LUA
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
        LUA;
        
        return $this->redis->eval($script, [$this->lockKey, $this->token], 1);
    }
}

Lock Usage

$lock = new RedisLock($redis, 'order_process_' . $orderId, 30);

if ($lock->acquire()) {
    try {
        $this->processOrder($orderId);
    } finally {
        $lock->release();
    }
} else {
    throw new LockAcquisitionException('Could not acquire lock');
}

Lock Contention

Contention Scenarios

Scenario              | Impact         | Solution
──────────────────────|────────────────|─────────────────
Hot key contention     | Many retries   | Lock splitting
Long-held locks       | Blocking       | Shorter timeouts
Deadlock               | System hang    | Lock ordering
Frequent acquisition  | High overhead  | Lock-free alternatives

Lock Splitting

// BAD: Single lock for all products
$lock = new RedisLock($redis, 'inventory_lock', 10);

// GOOD: Lock per product
$lock = new RedisLock($redis, 'inventory_' . $productId, 5);
// Different products can be updated concurrently

Lock Ordering

// Prevent deadlock with consistent lock ordering
function transferFunds($from, $to, $amount) {
    $locks = [$from, $to];
    sort($locks); // Always acquire in same order
    
    $lock1 = new RedisLock($redis, 'account_' . $locks[0]);
    $lock2 = new RedisLock($redis, 'account_' . $locks[1]);
    
    $lock1->acquire();
    $lock2->acquire();
    
    try {
        // Transfer funds
    } finally {
        $lock2->release();
        $lock1->release();
    }
}

Lock Timeout

Timeout Strategies

Strategy           | Approach               | Use Case
───────────────────|────────────────────────|─────────────
Fixed timeout       | Set expiry on lock     | Simple ops
Lease renewal       | Extend timeout         | Long ops
Watchdog            | Auto-renew while alive  | Unknown duration
Fencing token       | Monotonic counter      | Critical ops

Watchdog Pattern

class WatchdogLock {
    private $renewTimer;
    
    public function acquire() {
        $acquired = $this->lock->acquire();
        if ($acquired) {
            $this->startWatchdog();
        }
        return $acquired;
    }
    
    private function startWatchdog() {
        $this->renewTimer = setInterval(function () {
            // Renew lock every 5 seconds
            $this->redis->expire($this->lockKey, $this->timeout);
        }, 5000);
    }
    
    public function release() {
        clearInterval($this->renewTimer);
        $this->lock->release();
    }
}

Fencing Token

// Monotonic counter prevents stale lock holders
$token = $this->redis->incr('fencing_token_' . $resource);

// Acquire lock with token
$lock = $this->acquireLock($resource, $token);

// Use token in operation
$this->db->update($resource, [
    'data' => $value,
    'fencing_token' => $token
], [
    'fencing_token < ?' => $token // Only if token is newer
]);

Distributed Coordination

Coordination Patterns

1. Leader election: Choose one node as leader
2. Barrier synchronization: Wait for all nodes
3. Distributed queue: Fair task distribution
4. Rate limiting: Global request limiting

Leader Election

function electLeader($candidates) {
    $lock = new RedisLock($redis, 'leader_election', 30);
    
    if ($lock->acquire()) {
        try {
            $leader = $candidates[array_rand($candidates)];
            $this->redis->set('current_leader', $leader, ['EX' => 30]);
            return $leader;
        } finally {
            $lock->release();
        }
    }
    
    return $this->redis->get('current_leader');
}

Distributed Rate Limiting

function checkRateLimit($key, $limit, $window) {
    $now = microtime(true);
    $windowStart = $now - $window;
    
    $script = <<<LUA
        redis.call('zremrangebyscore', KEYS[1], 0, ARGV[1])
        local count = redis.call('zcard', KEYS[1])
        if count < tonumber(ARGV[2]) then
            redis.call('zadd', KEYS[1], ARGV[3], ARGV[3])
            redis.call('expire', KEYS[1], ARGV[4])
            return 1
        end
        return 0
    LUA;
    
    return $this->redis->eval($script, [
        $key, $windowStart, $limit, $now, $window
    ], 1);
}

Quiz

1. How to safely release a Redis lock?

Question 1 options

2. What prevents deadlock in distributed locks?

Question 2 options

3. What is a fencing token?

Question 3 options

Flashcards

Question

Redis lock acquisition?

Answer

SET key token NX EX timeout - set if not exists with expiry

Question

Safe lock release?

Answer

Lua script: verify ownership, then delete

Question

Deadlock prevention?

Answer

Consistent lock ordering across all nodes

Question

Fencing token?

Answer

Monotonic counter to prevent stale lock holder operations

Revision Notes

Key Takeaways

  • 1. Redis SET NX EX provides atomic lock acquisition
  • 2. Always verify ownership before releasing locks
  • 3. Lock splitting reduces contention on hot keys
  • 4. Consistent lock ordering prevents deadlocks
  • 5. Fencing tokens prevent stale lock holder operations

Interview Tips

  • Explain Redis lock implementation and safe release
  • Discuss lock contention and deadlock prevention
  • Describe watchdog and fencing token patterns

Cheat Sheet

Distributed Locks:
  Acquire: SET key token NX EX timeout
  Release: Lua script verify + delete
  Retry: Loop with delay

Contention:
  Lock splitting: per-entity locks
  Lock ordering: prevent deadlock
  Shorter timeouts: reduce blocking

Patterns:
  Watchdog: Auto-renew while alive
  Fencing token: Monotonic counter
  Leader election: Choose coordinator
  Rate limiting: Global request cap