Chris Collins

Tools · infra

Distributed patterns on Redis

A catalogue of 75 distributed patterns built on Redis — locks, leases and fencing, transactions and Lua, queues and streams, notification, rate limiting, caching, idempotency, counting, data structures, service coordination and cluster-aware design — each with its guarantees, its Lua, its failure modes and 185 labs you can step through: clients racing on a Redis in the page, real Lua included, without the pattern and with it, and on your own server. Plus Redis Cluster and Sentinel, animated.

Added · updated 30 October 2026

Works with

How to use it

People reach for Redis to coordinate distributed programs: a lock here, a queue there, a rate limit, a cache, a dedup key. Each of these is a small protocol between several clients and one shared server, and each has races that only show when the clients interleave badly — and failure modes that only show when something crashes, pauses or fails over. This page is a catalogue of those patterns, organised by the kind of problem first and then by how Redis’s commands solve it. For every pattern it gives the problem and the guarantees wanted, the implementation (the commands, and the Lua where atomicity needs it), runnable labs, the failure modes, what it does and does not guarantee, variants, the equivalent in Postgres where there is one, and sources.

The labs run on a Redis inside the page — the same one the Redis client uses, which now runs Lua scripts and functions with a Lua 5.1 interpreter written for it — so every command and every script is executed for real, not described. The Cluster and Sentinel tabs are the animated simulators of Redis on more than one machine.

How to use it

Patterns. The index on the left groups the 75 patterns by category; type in the search box to filter by name, command (SETNX, XAUTOCLAIM) or idea (fencing, idempotency) and press Enter to open the first match. On a phone the index folds into a list above the pattern. Each pattern page has:

  • The problem, What we want and How Redis does it, with the commands (each links to its page in the Redis client’s reference) and the Lua scripts, with Copy.

  • Run it — the labs. Chips across the top choose one: race (without the pattern: watch it go wrong), fix (with it), failure (a crash, pause, failover or partition the pattern does not survive) and walk-through. The diagram is a sequence chart with a column per client, one for Redis, one for a replica when the lab has one and one for the world outside Redis (a database, a payment provider, a file). Each row is one event: a command with its reply (an arrow to Redis, a heavier one for a script, whose inner commands open under inside the script), a reply that arrives later to a blocked client, a pushed message, a pause, a wait, a failover. Play steps through on its own (½×, 1×, 2×); Step, Back, Reset and End move by hand (→, ← and space work when the lab has focus). Beside it, every key after the current event with its type, TTL and value; the replica’s keys; the state outside Redis; clients that are paused, blocked or cut off; and at the end the verdict — what went wrong, or why it held.

  • Open in the Redis client opens the lab’s commands in the Redis client with each line in console pane A or B (A> …, B> …; a third client goes in pane B), so the interleaving is replayed over two real connections — on its built-in Redis, or on a server you connected there. Pauses and waits become comments: wait that long yourself.

  • Failure modes (crash, failover, restart, clock drift, process pauses, partitions, eviction, hot and big keys, duplicates), It guarantees / It does not guarantee, Variants, See also (the Postgres lab for the same pattern, related patterns) and Sources.

  • For nine patterns the earlier widgets are kept underneath: the four rate limiters on the same traffic (Fixed window), the lock and fencing timeline (Simple lock), Redlock over five masters, stampede protection compared, and command-by-command walk-throughs (cache-aside, leaderboard, streams, reliable queue, sessions).

Run the labs on your own Redis (at the top of the Patterns tab, folded) connects to a real server through the gitwebsite app’s TCP relay or a WebSocket bridge (as in the Redis client). Each lab then has Run on your Redis: its steps are replayed in the same order on your server, one connection per client, and each reply is shown next to the model’s. Blocking commands really block and are released by the other clients' writes; pauses and waits really wait (at most 3 s each). Only the database you choose is used (15 by default); the run refuses if any of the lab’s keys exist there, and deletes them afterwards; a CONFIG SET a lab makes (keyspace notifications) is put back; a lab that loads a FUNCTION library refuses if the server has any libraries, and removes its own. Failover, held-back replication and restarts can only happen in the model, so those steps are skipped and the comparison stops there.

Compare puts every pattern’s guarantees side by side — what makes it atomic, whether it can protect correctness or only saves duplicate work, what a failover does to it, its delivery semantics, what it needs in a cluster, and which failure modes its page discusses — with a filter and a category menu. On a phone each row becomes a card.

Which pattern? asks what you are trying to do, then one or two more questions, and recommends patterns with a sentence on why; Every need at a glance lists all the answers at once.

The Cluster, Sentinel and Hash slots tabs work like the site’s other simulators. Pick a scenario and press Start. Each step does something — sends a command, crashes a node, cuts the network — then plays until it has happened and pauses so you can read what happened and why, with links to the documentation. Next step moves on, Back rewinds (the simulator is deterministic, so it replays the earlier steps), ⏭ jumps to the end of the step and Step ⇥ advances to the next single event. Space plays and pauses; → is the next step. Speed scales everything; Hop time is how long a message takes to cross the diagram at 1×, and Sim clock turns that slowing off. Each tab has a playground with no script.

  • Cluster. At the top the client — with its cached slot map drawn as a strip of all 16,384 slots coloured by owner — and redis-cli, which runs reshardings. Below, one card per master and, under it, its replicas: role, slot ranges, number of keys, configEpoch and currentEpoch, replication offset, and the node’s own slot map (its slots bright, the rest faded; a red dashed outline is a slot it is MIGRATING, green one it is IMPORTING). PFAIL and FAIL flags a node holds, and a replica’s election, appear under it. Commands, replies, MOVED and ASK, replication, FAIL broadcasts, vote requests and votes, SETSLOT and MIGRATE are labelled packets; the cluster-bus pings and pongs are faint dots. Tap the client to send commands, a node to crash, restart or isolate it, redis-cli to reshard. Do something takes a key (or several, comma-separated, for MGET and MSET), gives the client a stale map for that key’s slot, reshards any slot range between two masters, and crashes, restarts or isolates nodes. The Slot map panel lists each node’s view; Client requests shows each command’s path, such as M1 → ASKING+M3.

  • Sentinel. A client, the sentinels and a master with its replicas. Each sentinel shows which nodes it thinks are subjectively down, how many sentinels agree, whether the master is objectively down, its epoch and vote, and whether it is leading a failover. The data nodes show a counter the client increments, their replication offset, replica priority and run id.

  • Hash slots. Type keys, one per line: each one’s hashed part (the hash tag) is marked, with its CRC16, its slot and which master owns it for 3–10 masters; with several keys it says whether a multi-key command over them would be allowed or refused with CROSSSLOT.

The catalogue

  • Mutual exclusion & coordination — Simple lock (SET NX PX), Lock renewal (watchdog), Reentrant lock, Read-write lock, Fair (queued) lock, Counting semaphore, Fencing tokens, Redlock and its critique, Leader election with leases, Distributed barrier, Countdown latch, Run once (SET NX on a job id).

  • Optimistic concurrency & transactions — Check-and-set with WATCH / MULTI / EXEC, MULTI/EXEC semantics (no rollback), Atomic read-modify-write with Lua, Redis Functions, Compare-and-swap on versions.

  • Queues & messaging — List queue (LPUSH / BRPOP), Reliable queue (BLMOVE + processing list), Priority queue, Delayed and scheduled jobs, Retries with backoff and a dead-letter queue, Pub/sub fan-out (fire-and-forget), Sharded pub/sub (SSUBSCRIBE / SPUBLISH), Streams with consumer groups, Recovering pending entries (XAUTOCLAIM), Trimming streams, Exactly-once effect from at-least-once delivery, Stream as an event log (event sourcing), Work stealing.

  • Watching & change notification — Keyspace notifications, Client-side caching with CLIENT TRACKING, Blocking reads as notification, Replication acknowledgement (WAIT / WAITAOF).

  • Rate limiting & admission — Fixed-window rate limit, Sliding-log rate limit, Sliding-window counter, Token bucket, Leaky bucket, GCRA (generic cell rate algorithm), Concurrency limiter (in-flight requests), Distributed circuit breaker.

  • Caching — Cache-aside (lazy loading), Read-through with single-flight loading, Write-through, Write-behind (write-back), Cache stampede protection, Stale-while-revalidate, Tag-based invalidation, Negative caching, Near cache with client-side caching.

  • Idempotency & deduplication — Idempotency keys with stored responses, Deduplication window (SET NX EX, Bloom filter), Exactly-once effect.

  • Counting & analytics — Atomic and sharded counters, Unique counts with HyperLogLog, Daily actives with bitmaps, Bloom, Cuckoo, Count-Min and Top-K, Time-series rollups.

  • Data-structure patterns — Leaderboards, Sliding-window top-N, Secondary indexes, Autocomplete with lexicographic ranges, Nearby search with GEO (drivers, shops), Session store, Presence: who is online, Feature flags and percentage rollouts, Unique ID generation.

  • Service coordination — Service discovery with TTL heartbeats, Distributed configuration with change notification, Job scheduler (cron) across many servers, Saga and workflow state.

  • Cluster-aware design — Hash tags for multi-key atomicity, Avoiding hot keys, Big keys and how to split them.

Some patterns need a module: the Bloom, Cuckoo, Count-Min and Top-K commands come with RedisBloom (Redis Stack, and built into Redis 8). The page’s Redis has no modules, so those labs build the same structures from core commands in Lua — which is also how you would do it without the module — and say so.

How the labs work

A lab is a list of steps for named clients — commands, Lua scripts, app logic, pauses, waits and events — played in order on the page’s Redis (an emulator of Redis 7.2’s commands with Redis’s own replies and errors, tested against a real server) on a virtual clock, so TTLs and expiry behave as they would. Every client has its own connection, so WATCH, MULTI, subscriptions and CLIENT TRACKING belong to the client that issued them. On top of that the lab engine models:

  • Lua. EVAL, EVALSHA, SCRIPT, FUNCTION LOAD and FCALL run on the Lua interpreter, with redis.call / redis.pcall, the conversions between Lua and Redis replies, read-only scripts, #!lua flags=…, and Redis 7.2’s error messages. A script runs atomically: nothing else happens until it returns.

  • Replication and failover. A lab can have a replica. It receives the primary’s data only when the lab says so (replicate) or a client calls WAIT — which is what asynchronous replication means: an acknowledged write can still be lost. A failover promotes the replica, the clients reconnect to it, and the writes it never received are listed as lost. Functions replicate; the EVAL script cache does not.

  • Blocking commands (BLPOP, BLMOVE, BZPOPMIN, XREAD … BLOCK, XREADGROUP … BLOCK) block the client until another client’s write lets them return, in the order they blocked, as Redis serves them.

  • Pushes — pub/sub messages, keyspace notifications and CLIENT TRACKING invalidations — arrive at the subscribed client as rows of their own, right after the command that caused them. A disconnected client misses them, as on a real server.

  • Process pauses, clock skew, partitions, crashes, eviction and restarts are steps: a paused client does nothing while time passes; a skewed client computes with its own clock while Redis keeps its own; a client cut off gets a timeout; eviction deletes a key as maxmemory would.

  • The world outside Redis is plain state the app steps change (rows in a database, charges at a payment provider), so a lab can end with a verdict on whether the outcome was right.

The labs are deterministic and checked by the tests: every race ends badly, every fix ends well, and every lab is also replayed on a real redis-server, where every reply must match the model’s.

What it models

  • Hash slots. HASH_SLOT = CRC16(key) mod 16384, with the specification’s CRC16 (XMODEM: polynomial 0x1021, initial value 0, not reflected — CRC16("123456789") = 0x31C3) over the key’s bytes, and its hash-tag rule: if the key contains a { and a later } with at least one character between them, only that part is hashed. Slot ranges are divided as redis-cli --cluster create does. The tests check the specification’s test vector and its hash-tag examples.

  • Routing. A node answers a command whose keys are all in one slot it owns. Keys in different slots: -CROSSSLOT. A slot it does not own: -MOVED <slot> <node>. A slot it is MIGRATING: it serves the command if all the keys are still there, answers -ASK <slot> <target> if none are, and -TRYAGAIN if only some are. A node IMPORTING a slot serves a command only after ASKING. A replica answers MOVED to its master. The client updates its map on MOVED (and, by default, refreshes the whole map as most clients do), follows ASK once without changing its map, and on a connection timeout refreshes its map from another node and retries.

  • Resharding follows the steps in the specification and redis-cli: SETSLOT IMPORTING on the target, SETSLOT MIGRATING on the source, MIGRATE key by key (the key is deleted from the source only once the target has it), then SETSLOT NODE on the target — which takes a new configEpoch without agreement — and on the source.

  • Failure detection and failover. Nodes ping each other on the cluster bus. No reply for node-timeout → PFAIL; a node with PFAIL and reports from a majority of the masters that serve slots (within 2 × node-timeout, counting itself if it is a master) marks FAIL and broadcasts it. With cluster-require-full-coverage yes a node answers CLUSTERDOWN while a FAIL master serves slots, and a master that cannot reach a majority of masters does the same. A replica of a FAIL master waits 500 ms + random(0–500 ms) + rank × 1000 ms, increments currentEpoch and asks every master for a vote; a master votes once per epoch, only if it also sees the master as FAIL, and not twice for replicas of one master within 2 × node-timeout. With a majority the replica takes its master’s slots with the new epoch as configEpoch. A node accepts a claim to a slot only with a higher configEpoch than the current owner’s; a master that loses all its slots becomes a replica of the node that took them and resynchronises, discarding writes the new master never received. Replication is asynchronous: a master answers before its replicas have the write.

  • Sentinel. Each sentinel pings the master and replicas; no valid reply for down-after-milliseconds → SDOWN. A sentinel with the master SDOWN asks the others (is-master-down-by-addr); with quorum agreeing it is ODOWN. It then waits a random part of a second, starts a new epoch, votes for itself and asks for votes; every sentinel votes once per epoch, for the first that asks. The winner needs max(quorum, majority of sentinels) votes. It picks a replica — not down, lowest replica-priority (0 is never), largest replication offset, smallest run id — sends REPLICAOF NO ONE, then REPLICAOF to the other replicas, and the new configuration spreads to the other sentinels with its epoch. A failover that finds no leader in failover-timeout is abandoned and retried later. A returning old master is converted to a replica. With min-replicas-to-write N a master refuses writes (NOREPLICAS) unless N replicas have acknowledged within min-replicas-max-lag.

Limits

  • The labs are models of the interleavings that matter, not load tests: one command at a time, in the order shown. The real-server replay keeps that order; it does not race the clients.

  • One Redis. Redlock’s five masters and a cluster’s shards are modelled with separate keys and app steps, which say so; the Cluster tab is the place for real multi-node behaviour.

  • The page’s Lua is Lua 5.1 with the libraries Redis scripts use (string, table, math, cjson, bit); not cmsgpack, struct or the debugger. A script that runs more than five million steps is stopped with an error.

  • Keyspace notification event names follow Redis for the common commands. RedisBloom and RedisTimeSeries commands are not available (their labs use core commands).

  • Cluster and Sentinel timeouts are shortened so failures play out in seconds: node-timeout 1.5 s instead of 15 s, down-after-milliseconds 1 s instead of the sample configuration’s 30 s, failover-timeout 3 s instead of 3 minutes. Network latency is fixed apart from a little jitter.

  • The cluster bus is simplified: every node pings every other at a third of node-timeout, and a ping carries the sender’s whole slot claim and its PFAIL list (real nodes ping a few random nodes each second and gossip about a sample of others). Not modelled: replica migration between masters, manual CLUSTER FAILOVER, READONLY reads from replicas, multiple databases, cluster-bus authentication, persistence and partial resynchronisation (a demoted node always does a full sync). Keys are user:N strings.

  • Sentinel: one monitored master; sentinels find each other and the replicas from the start rather than through hello messages and INFO; the client is a simple Sentinel-aware client.

  • Redlock’s widget acquires one node after another with a fixed latency; the critique of its timing assumptions is described and shown as labs, not simulated in time.

Linking to an example

Links can be written by hand. Add a tab after #: patterns (the default), compare, guide, cluster, sentinel or slots. A link’s settings replace this page’s saved settings (a button restores yours).

Parameter Meaning Example

example

A pattern (table below) — or a Cluster or Sentinel scenario, with its own settings; parameters given with it override them

example=fencing

pattern

The pattern to open; the nine names from before the catalogue still work: rate-limit, lock, redlock, leaderboard, cache, cache-aside, streams, queue, session

pattern=lock#patterns

lab

Which of the pattern’s labs

pattern=lock-simple&lab=unsafe-release#patterns

lstep

How many events of the lab to show

lstep=4

q

A search in the pattern index

q=xautoclaim#patterns

guide

Answers in Which pattern?, as option numbers from 0

guide=0,1#guide

scenario

A Cluster scenario, without its own settings

scenario=failover

sscenario

A Sentinel scenario, without its own settings

sscenario=sentinel-quorum

masters

Masters, 3–5

masters=4

replicas

Replicas per master, 0–2

replicas=2

keys

Starting keys user:1 … user:N, 0–200

keys=100

nodeTimeout

cluster-node-timeout in ms

nodeTimeout=3000

refresh

Client on MOVED: moved (refresh the whole map) or slot (that slot only)

refresh=slot

coverage

cluster-require-full-coverage: true or false

coverage=false

sentinels

Sentinels, 1–5

sentinels=5

quorum

Sentinel quorum

quorum=3

downAfter

down-after-milliseconds

downAfter=2000

minReplicas

min-replicas-to-write

minReplicas=1

maxLag

min-replicas-max-lag in ms

maxLag=2000

priority

replica-priority of R1 and R2: 100,100, 10,100, 100,10 or 0,100

priority=0,100

latency

One-way network latency in ms

latency=60

seed

Random seed

seed=7

speed

Playback speed: 0.25, 0.5, 1, 2, 4 or 8

speed=2

hop

Hop time in seconds (0.1–5), or clock

hop=1

key

Keys for the Hash slots tab, one per line (%0A between them)

key=%7Buser%3A1%7D%3Aname%0A%7Buser%3A1%7D%3Acart#slots

pstep

The step of a walk-through (under cache-aside, leaderboard, streams, reliable queue, sessions)

pstep=6

limiter

The limiter whose code the rate-limiter comparison shows: fixed, sliding-log, sliding-counter or token-bucket

limiter=token-bucket

traffic

Rate-limit comparison traffic: steady, boundary or bursty

traffic=boundary

limit, window

Rate limit and window in ms

limit=10&window=2000

ttl, pause

Lock timeline: TTL and the GC pause in ms

ttl=1000&pause=1500

fencing

Lock timeline: fencing tokens checked by the storage, true or false

fencing=true

Patterns for example= (or pattern=):

example= Opens

lock-simple

Simple lock (SET NX PX) (open)

lock-renew

Lock renewal (watchdog) (open)

lock-reentrant

Reentrant lock (open)

lock-rw

Read-write lock (open)

lock-fair

Fair (queued) lock (open)

semaphore

Counting semaphore (open)

fencing

Fencing tokens (open)

redlock

Redlock and its critique (open)

leader

Leader election with leases (open)

barrier

Distributed barrier (open)

latch

Countdown latch (open)

run-once

Run once (SET NX on a job id) (open)

occ-watch

Check-and-set with WATCH / MULTI / EXEC (open)

occ-multi

MULTI/EXEC semantics (no rollback) (open)

occ-lua

Atomic read-modify-write with Lua (open)

occ-functions

Redis Functions (open)

occ-version

Compare-and-swap on versions (open)

list-queue

List queue (LPUSH / BRPOP) (open)

reliable-queue

Reliable queue (BLMOVE + processing list) (open)

priority-queue

Priority queue (open)

delayed-jobs

Delayed and scheduled jobs (open)

retry-dlq

Retries with backoff and a dead-letter queue (open)

pubsub

Pub/sub fan-out (fire-and-forget) (open)

sharded-pubsub

Sharded pub/sub (SSUBSCRIBE / SPUBLISH) (open)

stream-groups

Streams with consumer groups (open)

stream-claim

Recovering pending entries (XAUTOCLAIM) (open)

stream-trim

Trimming streams (open)

stream-idempotent

Exactly-once effect from at-least-once delivery (open)

event-sourcing

Stream as an event log (event sourcing) (open)

work-stealing

Work stealing (open)

keyspace-notify

Keyspace notifications (open)

client-tracking

Client-side caching with CLIENT TRACKING (open)

blocking-notify

Blocking reads as notification (open)

wait-ack

Replication acknowledgement (WAIT / WAITAOF) (open)

rate-fixed

Fixed-window rate limit (open)

rate-sliding-log

Sliding-log rate limit (open)

rate-sliding-counter

Sliding-window counter (open)

token-bucket

Token bucket (open)

leaky-bucket

Leaky bucket (open)

gcra

GCRA (generic cell rate algorithm) (open)

concurrency-limit

Concurrency limiter (in-flight requests) (open)

circuit-breaker

Distributed circuit breaker (open)

cache-aside

Cache-aside (lazy loading) (open)

read-through

Read-through with single-flight loading (open)

write-through

Write-through (open)

write-behind

Write-behind (write-back) (open)

stampede

Cache stampede protection (open)

swr

Stale-while-revalidate (open)

tag-invalidation

Tag-based invalidation (open)

negative-cache

Negative caching (open)

near-cache

Near cache with client-side caching (open)

idempotency-key

Idempotency keys with stored responses (open)

dedup-window

Deduplication window (SET NX EX, Bloom filter) (open)

exactly-once

Exactly-once effect (open)

counter-sharded

Atomic and sharded counters (open)

hll-unique

Unique counts with HyperLogLog (open)

bitmap-actives

Daily actives with bitmaps (open)

probabilistic

Bloom, Cuckoo, Count-Min and Top-K (open)

timeseries-rollup

Time-series rollups (open)

leaderboard

Leaderboards (open)

top-n-window

Sliding-window top-N (open)

secondary-index

Secondary indexes (open)

autocomplete

Autocomplete with lexicographic ranges (open)

geo-nearby

Nearby search with GEO (drivers, shops) (open)

session-store

Session store (open)

presence

Presence: who is online (open)

feature-flags

Feature flags and percentage rollouts (open)

id-generation

Unique ID generation (open)

service-discovery

Service discovery with TTL heartbeats (open)

config-notify

Distributed configuration with change notification (open)

job-scheduler

Job scheduler (cron) across many servers (open)

saga-state

Saga and workflow state (open)

hash-tags

Hash tags for multi-key atomicity (open)

hot-keys

Avoiding hot keys (open)

big-keys

Big keys and how to split them (open)

Cluster and Sentinel scenarios:

example= Shows

slots

Hash slots, CROSSSLOT, hash tags and MOVED (open)

reshard

A live resharding with ASK and then MOVED (open)

failover

PFAIL, FAIL, the replica election and promotion, the old master’s return (open)

lost-write

An acknowledged write lost by asynchronous replication (open)

minority

A partitioned minority master and its lost writes (open)

playground

A cluster with no script (open)

sentinel-failover

Sentinel’s SDOWN, ODOWN, election and promotion (open)

sentinel-partition

Writes lost on an isolated old master (open; with min-replicas-to-write 1)

sentinel-quorum

Why quorum and majority differ (open)

sentinel-playground

Sentinel with no script (open)

Sources: each pattern lists its own. Across the page: the Redis documentation — Distributed locks with Redis, Transactions, Scripting with Lua, Functions, Pub/sub, Streams, Keyspace notifications, Client-side caching, WAIT, the Redis Cluster specification, the cluster tutorial, High availability with Redis Sentinel and Redis replication, and the command reference for each command used; the Redis source (cluster.c, sentinel.c, redis-cli.c, geohash.c, script_lua.c) where the docs are silent; Martin Kleppmann, How to do distributed locking (2016), and Salvatore Sanfilippo’s reply, Is Redlock safe?; Josiah Carlson, Redis in Action; Brandur Leach on idempotency keys and GCRA; A. Vattani, F. Chierichetti and K. Lowenstein, “Optimal Probabilistic Cache Stampede Prevention”, VLDB 2015; the Lua 5.1 reference manual.

Runs entirely in your browser; nothing you type leaves this page.