InterviewKata
System design · Worked design D04 of 46 · Warm-up · Long form

Design a distributed cache

Build the cache that every other design leans on: an in-memory key-value service spread over many machines, answering in well under a millisecond, surviving node failures, and growing without downtime. The model is Redis Cluster (and Valkey, its open-source fork). The interesting parts are routing keys to nodes, failing over without split brain, what clients experience while data moves, and the hot keys and stampedes that hurt the databases behind it. It follows the interview framework phase by phase.

1. The prompt

"Design a distributed cache like Redis or Memcached that many services share. Clients set and get values with expiry times; it must grow beyond one machine."

A cache looks simple until you ask what happens when nodes are added, when one dies, when one key gets a hundred times the traffic of the rest, and when the whole thing restarts empty in front of a database that can't take full load. Those questions are the interview.

2. Frame the problem

Is it a pure cache, or does anyone rely on it as the only copy of data?

A cache: every value can be rebuilt from a database behind it.

Losing a little data on failover is acceptable, so asynchronous replication is fine and persistence is optional.

How much data, and how many operations?

About 2 TB of hot data, and 5M operations a second at peak.

We'll size by both memory and throughput, and see which wins.

Simple strings, or data structures too?

Mostly strings and hashes; some sorted sets and counters.

Redis-style data structures, with operations that run atomically per key.

Do clients need multi-key operations?

Occasionally, on keys belonging to the same user.

Keys that are used together must be able to live on the same node.

How fast?

p99 under 1 ms within a zone.

No extra network hop on the hot path: clients should reach the right node directly.

Must it grow and shrink without downtime?

Yes. Teams add capacity regularly.

Data must move between nodes while clients keep working.

Many teams sharing one cluster?

Yes, several services use it.

One team's huge keys or hot traffic mustn't slow everyone else.

3. What it must do

  1. A client can set, get and delete values, with an optional expiry (TTL).
  2. The cache evicts the least useful keys when memory is full.
  3. Operators can add or remove nodes while clients keep working.

Not now:

4. How well it must do it

TargetWhy this number
Very fast: p99 under 1 ms within a zone speedThe cache exists to be faster than the database by orders of magnitude.
Scale: 2 TB and 5M operations a second, growing without downtime load shapeEvery product feature adds keys and traffic.
Self-healing: a failed node is replaced by its replica within ~15 s, automatically surviving failuresDuring failover, the database behind takes the misses.
Never very stale: after failover, at most a few seconds of writes may be lost; never serve data that's minutes old consistency or availabilityA cache may lose recent writes, but serving long-stale data breaks features.
Protects the database: no failure, restart or expiry may send the database more than ~2× its normal load surviving failuresThe worst cache failure is the one that takes the database down too.

5. Numbers that change the design

QuantityWorking→ so we…
Memory2 TB ÷ ~100 GB usable per node (headroom for fragmentation and replication buffers)~20 primaries would do
Throughput5M ops/s ÷ ~150K per node~35 nodes → so throughput, not memory, sets the shard count: ~40 primaries
AvailabilityOne replica per primary, in another zone→ so ~80 nodes in total
Cold restartA 95% hit rate means the database normally sees 5% of reads; an empty cache sends it 100%20× its normal load → so we must never restart cold in front of it

6. The nouns

slot(key)   CRC16(hash tag or whole key) mod 16384
slot map    slot ranges → primary (+ replica), with a config epoch per owner
per node    keys in memory, maxmemory + eviction policy, async replication stream to its replica

7. The contract

The Redis protocol over TCP. Clients cache the slot map and handle two kinds of redirection.

GET key            → value | nil
SET key value EX 600
DEL key
CLUSTER SLOTS      → which node serves which slot range (clients cache this map)
-MOVED 12182 10.0.3.7:6379    (that slot lives elsewhere now: update your map)
-ASK   12182 10.0.3.9:6379    (slot is mid-migration: try there, just this once)

8. First working version

Start with several independent cache servers and let clients pick a server for each key.

8.1 Setting and getting

Each client hashes the key and takes it modulo the number of servers, then talks to that server directly. A get that misses reads from the database and sets the value (cache-aside).

8.2 Eviction

Each server has a memory limit; when it's full, it evicts keys it considers least useful. Redis approximates LRU or LFU by sampling a few keys and evicting the best candidate, which costs far less than a perfect ordering.

8.3 Adding a server

Add it to the list every client uses.

Parked for later (section 9 fixes each)

  1. Changing the server count remaps almost every key: a near-total miss storm.
  2. No replicas: a dead server loses its keys and its share of hits.
  3. Clients must all agree on the server list.
  4. Multi-key commands across servers don't work.
  5. Hot keys and expiring popular keys hammer one server and the database.
  6. A restart starts cold in front of the database.

9. Make it hold up

9.1 Routing keys to nodes scale · protects the database

ApproachWhy
✗ Badhash(key) % number_of_nodes in the clientAdding a node remaps almost every key: a near-total cache miss storm that hammers the databases behind it.
~ GoodConsistent hashing in clients (Memcached-style)Adding a node moves ~1/N of keys. But clients must agree on the node list, and there's no coordinated failover.
✓ GreatFixed hash slots (16,384) owned by nodes, an authoritative slot map maintained by the cluster, and redirects for clients with stale mapsMoving a slot moves exactly its keys, one slot at a time; clients self-correct via MOVED; failover and resharding are coordinated by the cluster.

Smart clients or a proxy? Clients that cache the slot map go straight to the right node: no extra hop, which keeps p99 under 1 ms. A proxy tier (Envoy, twemproxy) makes every client simple and hides topology changes, at a little latency and one more tier to run.

9.2 Moving data while clients keep working scale

Adding capacity means moving slots. Press Play to move one slot from node A to node D while clients keep reading and writing.

During the move, keys already copied live on D and the rest on A. A client asking A for a key that has already moved gets -ASK ("try D, just this once"); once the slot is fully moved, the slot map changes and clients with old maps get -MOVED ("update your map").

9.3 Failover without split brain self-healing · never very stale

9.4 Multi-key operations speed

A command touching several keys only works if they live in the same slot. Hash tags make that possible: only the part inside {…} is hashed, so {user:42}:cart and {user:42}:profile share a slot (try it in section 13). Use them sparingly: putting too much under one tag creates a hot slot.

9.5 Hot keys, stampedes and big keys protects the database · speed

ProblemFix
Hot keyOne viral item gets 200K reads a second, all on one shardAn in-process cache on app servers with a 1–5 s TTL; copies of the key under several names spread across slots; or read that key from replicas
StampedeA hot key expires and thousands of misses hit the database at onceRequest coalescing in the app (one rebuild, everyone waits for it), early probabilistic refresh, or a short lock so one caller rebuilds (Module 09)
Big keyA 50 MB list: one slow command blocks a single-threaded node for everyoneCap value sizes, split collections, and scan for big keys regularly

9.6 Cold starts, persistence and tenants protects the database

9.7 The whole design after the deep dives

A read with a stale client map, a hot key absorbed locally, and a failover, through the final design.

10. Interviewer follow-ups

Traffic grows 10×.

Add primaries and move slots to them (the slot model makes this mechanical). Push the hottest keys into in-process caches. Split clusters by workload, so one service's spike can't take everyone's latency. Watch the busiest shard, not the average.

Memcached or Redis/Valkey?

Memcached is multi-threaded and simple: strings only, no replication or clustering (clients do consistent hashing). Redis and Valkey add data structures, replication and cluster management. For a shared, self-healing cache, Redis or Valkey; for a simple, very high-throughput string cache, Memcached is still excellent.

How do you keep the cache consistent with the database?

Cache-aside with delete-on-write: update the database, then delete the key, and let the next read rebuild it. Use TTLs as a safety net. For tighter freshness, change data capture from the database can delete keys within a second (Module 09).

One node is consistently slower than the others.

Usually a hot slot, a big key or a noisy neighbour on the host. Check per-slot operation counts and slow-command logs. Move hot slots, split big keys, or add in-process caching for the hot key.

The whole cluster goes down.

Bring it back warm (from snapshots, or a warm-up job), and meanwhile protect the database: rate-limit cache rebuilds, serve stale data where allowed, and shed non-essential features. This is a game-day scenario worth rehearsing.

Should we use a managed service?

Usually yes (ElastiCache, Memorystore, Azure Managed Redis): they handle failover, patching and resharding. Run your own only if you need control over versions, topology or cost at very large scale.

How do LRU and LFU differ in practice?

LRU evicts what wasn't used recently, so a one-time scan of many keys can push out popular ones. LFU evicts what's used rarely, which protects steady favourites but adapts slower to new popular keys. Redis approximates both by sampling.

Cross-region?

Keep caches regional and fill them from regional database replicas. Invalidations replicate with the database's change stream. Cross-region cache replication adds latency and conflicts for little benefit.

11. When things break

FailureWhat users seeHow we detect itWhat limits the damage
A primary diesA few seconds of misses or errors for one shardGossip; node timeoutReplica promoted with a new epoch
Network partition isolates a primaryWrites on the minority side rejectedCluster state, epochsMinority primary stops accepting writes after the timeout
Hot keyOne shard slowPer-slot ops, latency by nodeIn-process cache; split or replicate the key
Expiry stampedeDatabase spikeMiss rate and database load jump togetherRequest coalescing, early refresh, locks
Whole cluster restartSlow pages, database overload riskHit rate falls to zeroWarm restart, warm-up jobs, shedding, rebuild rate limits
Memory full with wrong eviction policyWrite errors (noeviction)OOM errorsUse an allkeys policy for caches; alert on memory use

12. Wrap-up and the clock

Trade-offs we made:

What we'd watch:

At 10×:

A 45-minute interview

0–4Frame the problem (pure cache? size? multi-key?)
4–9Must do, how well, numbers (ops/s beats memory)
9–12Nouns and contract
12–19First working version, parked list
19–40Deep dives: routing (9.1), resharding (9.2), failover (9.3), hot keys (9.5)
40–45Follow-ups and wrap-up

A 60-minute interview

0–5Frame the problem
5–11Must do, how well, numbers
11–15Nouns and contract
15–23First working version
23–50Deep dives 9.1–9.6 and the final picture
50–60Follow-ups: consistency with the DB, cold cluster, managed vs self-run
LevelWhat a strong answer shows
Mid-levelSharding by key, replicas, eviction with LRU and TTLs, cache-aside usage.
SeniorWhy modulo hashing fails, slots or consistent hashing, client routing and redirects, async-replication loss on failover, hot keys and stampedes, sizing by ops/s.
Staff+Live resharding (MOVED/ASK), failure detection and epochs, minority-partition behaviour, cold starts and protecting the database, multi-tenant isolation.

Common mistakes:

13. Try it, check yourself, read more

Type a key to see its real Redis Cluster hash slot (CRC16 in the XMODEM variant, mod 16384) and which of 4 nodes owns it. Try {user:42}:cart and {user:42}:profile.

Check: the Redis cluster specification says "foo" maps to slot 12182 and the CRC16 of "123456789" is 31C3; this calculator reproduces both.

What does a hash tag like {user:42} do?

Keys sharing the tag land in the same slot, so multi-key commands work on them.

A client receives MOVED 12182 10.0.3.7:6379. What does it mean?

The slot now lives on another node permanently; ASK, by contrast, is a one-off during migration.

In this design, what decided the number of shards?

2 TB needed ~20 shards, but 5M ops/s needed ~35; always check both.

A hot key expires and 5,000 requests miss at once. What protects the database?

One rebuild instead of 5,000; early refresh avoids the expiry moment altogether.
ResourceWhy
Redis Cluster specificationSlots, redirections, failure detection and failover, from the source.
Scaling Memcache at FacebookRunning caches at enormous scale: leases, regions, cold clusters.
ValkeyThe open-source (BSD) Redis fork, with the same cluster model.