MODULE 3/LESSON 5
🔴 Redis Architecture

Redlock Algorithm & ZSET Leaderboards

Preventing race conditions with Redlock and building O(log N) leaderboards with Sorted Sets

15 min
In distributed systems, two processes racing to modify shared state cause Race Conditions — inventory goes negative, users get double-charged. Redlock provides a distributed mutex using Redis to ensure only one process holds a lock at a time.

Key Concepts

Redlock Algorithm & Split-Brain

Acquire the same lock key on N/2+1 Redis nodes within a validity window. This quorum approach prevents split-brain scenarios where network partitions cause two clients to both think they hold the lock.

Lock TTL is Critical

Always set a lock expiry (EX 10 seconds). If the lock holder crashes, the lock auto-expires and another process can acquire it. Without expiry, you get permanent deadlocks.

ZSET Leaderboard

ZADD adds a member with score. ZREVRANK returns rank in O(log N). ZREVRANGE returns top-N. A million-user leaderboard query takes microseconds — no SQL ORDER BY needed.

Redlock Quorum and ZSET Leaderboard

Redlock Algorithm (Distributed Lock) Client 1 req lock:item99 Redis 1 OK Redis 2 OK Redis 3 OK Redis 4 Fail Redis 5 Fail Quorum Met (3/5). Lock Acquired! Sorted Set (ZSET) Leaderboard ZSET: leaderboard:global Rank 1 player:kyaw 2300 pts Rank 2 player:aung 1500 pts Rank 3 player:zaw 950 pts

Redlock Implementation (Node.js)

flash-sale.service.tstypescript
1import Redlock from 'redlock';
2
3// Initialize with multiple Redis nodes for safety
4const redlock = new Redlock([redis1, redis2, redis3, redis4, redis5]);
5
6async function purchaseLastItem(userId: string, itemId: string) {
7  // Acquire distributed lock — only one winner
8  const lock = await redlock.acquire(
9    [`lock:inventory:${itemId}`],
10    10_000 // 10 second TTL (Very important to prevent deadlocks)
11  );
12
13  try {
14    const stock = await redis.get(`stock:${itemId}`);
15    if (Number(stock) <= 0) throw new Error('Out of stock');
16
17    // Atomic decrement — only after confirming stock > 0
18    await redis.decr(`stock:${itemId}`);
19    await orderService.createOrder(userId, itemId);
20  } finally {
21    // Always release the lock, even on error
22    await lock.release();
23  }
24}
25
26// ── Leaderboard operations (ZSET) ──
27async function updateScore(playerId: string, score: number) {
28  await redis.zadd('leaderboard:global', score, `player:${playerId}`);
29}
30
31async function getTopPlayers(limit = 10) {
32  // ZREVRANGE: Get top scores (descending)
33  return redis.zrevrange('leaderboard:global', 0, limit - 1, 'WITHSCORES');
34}
35
36async function getPlayerRank(playerId: string) {
37  // ZREVRANK returns 0-indexed rank — add 1 for display
38  return redis.zrevrank('leaderboard:global', `player:${playerId}`);
39}
💡
Senior Architect Insight: Never use a single Redis instance for Redlock — if it fails, all locks are lost or permanently held. The algorithm requires N Redis instances (typically 5) and a quorum of N/2+1 successful acquisitions to be safe against node failures.