Engineering Notes
Home/Databases/Caching/Distributed Caching
Databases · Caching

Distributed Caching

When one cache node isn't enough, keys must be spread across many. Add and remove nodes on a consistent-hashing ring, compare client-side, proxy and cluster-aware routing, and handle hot keys and failover.

🗄️ Databases● Intermediate⏱ 6 min read▶ 2 interactive🛠️ 2 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • When to distribute a cache
  • Partitioning keys: modulo vs consistent hashing
  • Client-side vs proxy vs cluster-aware routing
  • Replication, failover, hot keys and big keys

1When One Cache Node Isn't Enough

A single Redis or Memcached node eventually runs out of one of three things:

  • memory — the working set no longer fits in one machine's RAM
  • throughput — one node (and, for Redis, one main thread) can't serve the request rate
  • availability — when the only node restarts, every request becomes a miss and the database takes the full load (04-caching_strategies/ §6)

A distributed cache spreads keys across many nodes (for capacity and throughput) and/or copies them (for availability).

2Partitioning Keys Across Nodes

Every client must agree on which node owns a key.

Naive modulo hashing — node = hash(key) % N — is simple and balanced, but adding or removing one node changes N, and almost every key moves:

text
N=4 → N=5: a key stays put only if hash % 4 == hash % 5  → ~20% stay, ~80% move

For a cache, "moved" means "miss" — growing the cluster triggers a near-total cache flush and a database stampede.

Consistent hashing places nodes and keys on the same hash ring (0 … 2³²−1); a key belongs to the first node clockwise from it. Adding a node only takes over the keys between it and its predecessor — about 1/N of keys move. Virtual nodes (each physical node placed at 100–200 points on the ring) smooth out the uneven arcs you'd get with a few random points, and let bigger machines take proportionally more vnodes.

Hash slots (Redis Cluster) — a fixed 16,384 slots: slot = CRC16(key) mod 16384; each master owns a range of slots. Resharding moves slots, not individual keys, and clients cache the slot → node map. Keys can be forced into the same slot with a hash tag: only the part inside {...} is hashed, so user:{42}:cart and user:{42}:profile live together and can be used in one multi-key command or Lua script.

3Client-Side vs Proxy vs Cluster-Aware Routing

ApproachHow keys find nodesExamples
client-side shardingthe client library hashes keys to a node listMemcached clients (ketama), ioredis with manual shards
proxyclients talk to a proxy that routestwemproxy, Envoy Redis proxy, mcrouter
cluster protocolnodes know the slot map; wrong node replies MOVEDRedis Cluster
text
client → node A: GET user:{42}:cart
node A → client: -MOVED 5474 10.0.0.12:6379     (slot 5474 lives on node C)
client → node C: GET user:{42}:cart             (and updates its slot cache)

4Replication and Failover

Partitioning alone means losing a node loses its 1/N of the cache. Replication adds copies:

  • Redis: each primary has replicas (asynchronous replication). Sentinel (non-cluster) or the cluster itself detects a failed primary and promotes a replica. Writes acknowledged by the old primary but not yet replicated are lost on failover — acceptable for a cache, worth knowing if Redis holds sessions or locks.
  • Memcached: no built-in replication; nodes are independent, and losing one is just losing that shard's cached data (by design — "it's a cache").

5Hot Keys and Big Keys

Partitioning spreads keys, not load. One celebrity key (product:iphone-launch, a global config blob) still lives on one node and can saturate it.

ProblemMitigation
hot key (reads)in-process L1 cache in front of it (short TTL); replicate the key under N suffixed names (hot:{k}:1..8) and read a random copy; read from replicas
hot key (writes)shard the counter (likes:post1:shard{0..15}) and sum on read
big key (MB-sized value or million-member set)split into smaller keys; avoid KEYS, SMEMBERS on huge sets; use SCAN/HSCAN

6Consistency Across Many Nodes and Many App Instances

  • Multi-key operations work only when all keys are on the same node (hash tags).
  • There's no cross-node transaction; invalidating 50 related keys across nodes is 50 independent operations — design for partial failure (05-cache_invalidation/).
  • App-instance L1 caches must be invalidated by broadcast (pub/sub) — a sharded Redis Cluster supports sharded pub/sub (SPUBLISH) to keep that traffic from going to every node.
Under the HoodWhy Consistent Hashing Moves Only ~1/N of Keys▾

Map each node to several points on a ring by hashing "nodeA#0", "nodeA#1", …, and sort the points. To place a key, hash it and binary-search the sorted array for the first point ≥ the hash (wrapping around to index 0). Lookup is O(log V) for V virtual nodes.

When node E joins, it inserts its points. Each new point takes over exactly the arc between the previous point and itself — keys that previously belonged to whichever node owned the next point clockwise. No other arc changes. With V vnodes per node and N nodes, E ends up owning ≈ 1/(N+1) of the ring, so ≈ 1/(N+1) of keys move, all to E. Removal is the mirror image: only the departing node's keys move, scattered to its clockwise neighbours — which is why vnodes matter: without them, all of a dead node's load would land on a single neighbour.

Redis Cluster avoids the ring altogether by fixing the number of slots: membership changes are explicit slot migrations (CLUSTER SETSLOT … MIGRATING/IMPORTING, MIGRATE key by key), during which the source answers ASK redirections for keys already moved — so clients keep working while data is in transit.

Keys and virtual nodes sit on a hash ring and each key belongs to the next node clockwise; when a new node joins it takes over only the arcs just before its points, so about one Nth of keys move instead of almost all with modulo hashing
Keys and virtual nodes sit on a hash ring and each key belongs to the next node clockwise; when a new node joins it takes over only the arcs just before its points, so about one Nth of keys move instead of almost all with modulo hashing

⚠️ Common Pitfalls

  1. hash % N sharding for a cache — scaling the cluster flushes most of the cache.
  2. Multi-key commands across slots (MGET a b in Redis Cluster) → CROSSSLOT errors; use hash tags deliberately.
  3. Hash-tagging everything into one slot — you've rebuilt a single node, now with extra steps.
  4. Ignoring hot keys — one node melts while the rest idle.
  5. Treating Redis replication as synchronous — acknowledged writes can vanish on failover.
  6. KEYS * in production — blocks the node while it walks every key.

📋 Quick Reference

NeedUse
spread keys, minimal movementconsistent hashing with vnodes / Redis Cluster slots
same-node multi-key opshash tags {...}
survive node lossreplicas + automatic failover
hot read keyL1 cache, key replication, read replicas
hot countersharded counters
iterate keys safelySCAN (never KEYS)

🧠 Key takeaways

  • Distribute when memory or throughput exceeds one node; partition keys deterministically.
  • Consistent hashing (or Redis Cluster's 16,384 hash slots) moves only ~1/N of keys when nodes change.
  • Routing can be client-side, via a proxy (Twemproxy, Envoy), or cluster-aware clients.
  • Replicate for failover; tackle hot keys (local L1, key splitting) and big keys (break them up).

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1What is a distributed cache and why use one?core▾

A cache spread across multiple nodes (Redis Cluster, Memcached pool) and shared by all app instances. It provides more memory than one machine, consistent cached data across instances, and survives individual app restarts.

Follow-ups interviewers ask
  • Versus a local cache? Local is faster (no network) but duplicated per instance and inconsistent.
Q2How are keys distributed across cache nodes?core▾

Consistent hashing (Memcached clients) or hash slots (Redis Cluster's 16,384 slots), so adding or removing nodes moves only part of the keys.

Follow-ups interviewers ask
  • Why not hash % N? Changing N remaps almost all keys, causing a massive miss storm.
Q3How do you handle hot keys in a distributed cache?senior▾

Replicate the hot key to several nodes with suffixes and read randomly, add a small local in-process cache in front for the hottest keys, and split large values.

Follow-ups interviewers ask
  • How do you find hot keys? Redis --hotkeys (with LFU) or proxy-level metrics.
Q4What happens if a cache node fails?core▾

Its keys become misses and are redistributed; the database sees extra load. With replicas (Redis Sentinel/Cluster), a replica is promoted. Protect the DB with request coalescing and rate limits.

Follow-ups interviewers ask
  • Should the app fail if Redis is unreachable? For caching, no: fall back to the DB with timeouts.
Q5What's a two-level (near) cache?core▾

A small in-process L1 cache in each instance in front of a shared L2 distributed cache. It cuts latency and L2 load for hot data, with invalidation via pub/sub and short L1 TTLs.

Follow-ups interviewers ask
  • Main risk? Instances serving different versions until L1 expires.

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.

✅ Check yourself

Answer the questions — feedback is instant.

1. With hash(key) % N, adding one node remaps…
Consistent hashing fixes this.
2. Redis Cluster maps keys to…
CRC16(key) mod 16384.
3. One key gets 200k reads/s and overloads its shard. Mitigation?
Spread or absorb the hot key.
4. Why are big keys (e.g. a 50 MB hash) dangerous?
Break them into smaller keys.