System design guide

Design a distributed cache

"Design Memcached" or "design a distributed cache" asks you to build the thing you've drawn as a box in every other design. It tests partitioning, replication, memory management, and what clients should do when the cache misbehaves.

1. Clarify the requirements

Ask: Is this a look-aside cache in front of a database, or a primary data store? What's the value size? Do we need data structures (lists, sorted sets) or plain get/set? Is losing cached data acceptable? Single region or multi-region?

Functional requirements

  • get(key), set(key, value, ttl), delete(key).
  • Per-key TTL; eviction when memory is full.
  • Cluster can grow and shrink without a full flush.

Non-functional requirements

  • Latency: sub-millisecond p50 and a few milliseconds p99 inside a datacenter.
  • Throughput: millions of operations per second across the cluster.
  • Availability over consistency: a stale or missing value is acceptable; an unavailable cache shouldn't take the application down.

2. Estimates

QuantityAssumptionResult
Traffic5M reads/s + 500K writes/s at peak5.5M ops/s
Data10 TB of hot data, replication factor 220 TB of memory
Nodes~256 GB usable memory per node~80 nodes (plus headroom: ~100)
Per node5.5M ÷ 100~55K ops/s per node, well within one machine's capacity
Network1 KB average value × 5M reads/s~5 GB/s cluster egress, ~50 MB/s per node

Memory decides the node count, not CPU. That's typical for caches, and it's why value size and compression are worth asking about.

3. High-level architecture

Distributed cache architecture Application servers use a cache client that hashes each key onto a consistent-hash ring to choose a shard. Each shard has a primary and a replica. A config service holds the cluster map and pushes membership changes to clients. On a miss, the application reads the database and populates the cache. membership hash(key) replicate on miss App servers+ cache client Config service(cluster map) Shard A primary A replica Shard B primary B replica Shard C primary C replica Database(source of truth)
Smart clients route by consistent hashing; a config service owns membership; each shard has a replica.

Cache nodes are simple: an in-memory hash table, an eviction policy, TTL handling, and a network protocol. Keep them dumb and fast.

Cache clients (a library in each application server) hash the key, look up the owning shard in the cluster map, and talk to it directly. A config service backed by a consensus store (ZooKeeper, etcd) owns the cluster map, runs health checks, and pushes changes when nodes join, leave, or fail.

4. Partitioning

Use consistent hashing with virtual nodes. Each physical node owns many points on a hash ring; a key belongs to the first node clockwise from its hash. Adding or removing a node moves only about 1/N of the keys, instead of reshuffling everything as hash(key) mod N would. Virtual nodes smooth out the distribution and let bigger machines take more points. The details are in sharding and consistent hashing.

5. Data model and memory management

entry   key (bytes, ≤ 250 B), value (bytes, ≤ 1 MB), flags,
        expires_at, cas_version, lru_prev, lru_next
  • Eviction: LRU via a doubly linked list per memory class, or sampled LRU (pick a few random keys, evict the oldest) to avoid list maintenance on every read.
  • Expiry: lazy (check on read) plus a background sweep that samples keys, so expired entries don't hold memory indefinitely.
  • Fragmentation: slab allocation (fixed-size chunk classes) avoids allocator fragmentation for many small values.
  • Compare-and-set: a version number per entry lets clients update without lost writes.

6. Scaling and failure handling

  • Replication. Each shard has a replica that receives writes asynchronously. On primary failure the config service promotes the replica and updates the cluster map. Asynchronous replication means a failover can lose the last few writes; acceptable for a cache.
  • Hot keys. One celebrity key can saturate a single node. Detect hot keys (client-side sampling), then replicate them to several nodes and read from a random one, or keep a tiny in-process cache in the clients for the hottest few hundred keys.
  • Cache stampede. When a popular key expires, thousands of requests miss at once and hammer the database. Use request coalescing (one request recomputes, others wait), a short lock key, or probabilistic early refresh before expiry.
  • Node failure without replicas. The keys on that node become misses. Make sure the database can absorb a node's share of traffic, and add jitter to TTLs so mass expiry doesn't happen all at once after a restart.
  • Multi-region. Usually one cache cluster per region, each populated from local reads, with invalidations broadcast across regions when the source of truth changes.

7. Trade-offs to call out

  • Client-side routing vs proxy: fewer hops and no extra tier vs simpler clients and central control.
  • Replication vs capacity: replicas double memory cost; skip them if misses are cheap.
  • Consistency with the database: cache-aside with delete-on-write is simple but has race windows; write-through is more consistent but slower on writes. See caching strategies.
  • AP over CP: during a partition, a cache should keep serving possibly stale data rather than refuse requests; see consistency models and CAP.

8. Common follow-up questions

  1. How do you add a node without a spike in misses? Add it with a small weight (few virtual nodes) and ramp up, or warm it by copying keys from the nodes that are giving up ranges.
  2. How do you invalidate a key cached in many places? Delete on write from the service that owns the data, publish invalidation events for other regions, and keep TTLs as a safety net.
  3. What happens if the config service is down? Clients keep using their last known cluster map; membership changes pause, but traffic continues.
  4. How do you monitor it? Hit ratio per key prefix, evictions per second, memory usage, p99 latency per node, and hot-key reports.
  5. Would you build or use Redis/Memcached? In practice, use an existing engine and focus your design on partitioning, failover, and client behaviour; say that explicitly.

FAQ

Should a distributed cache replicate data?

It depends on how expensive a miss is. A pure look-aside cache in front of a database can skip replication: losing a node just means more misses. If the backing store can't absorb that extra load, or the cache holds data that's expensive to recompute, replicate each shard to at least one replica.

LRU or LFU eviction?

LRU is the default and works well for most workloads with recency-driven access. LFU (or a hybrid such as W-TinyLFU) protects popular items from being evicted by one-off scans. Mention both and pick based on the access pattern.

Where does the routing logic live?

Either in a smart client library that hashes keys to nodes using a cluster map, or in a proxy tier. Client-side routing saves a network hop; a proxy simplifies clients and centralises config. Both are common.