Skip to content
About
Main content

Scenarios

Longer problems, like those in senior system design interviews. A strong answer estimates with numbers first, names the mechanism, then recommends a fix and says what it costs.

Try each one before you open the answer.
1After a routine restart, your cache comes back empty and the database behind it falls over within minutes. The cache had a 95% hit rate, and peak traffic is 30,000 reads per second. Explain what happened and how you would prevent it.Show the answer

Estimate

At a 95% the database saw 5% of 30,000 = 1,500 reads per second, and it was sized for that. An empty sends it all 30,000: 20 times its normal load, the cache's of 1 / (1 − 0.95).

Diagnose

The database did not fail on its own. Its capacity plan silently included the cache, and the restart removed it.

Fix

Warm the cache before it takes traffic; restart nodes one at a time; cap the database's concurrency and shed load so it survives a cold start; consider a persistent or second-tier cache that survives restarts.

Trade-off

costs time and memory. Whatever you choose, write the cache into the capacity plan as a dependency.

Read moreLeverageWarming

2A colleague proposes doubling the cache's memory to bring down p99 latency. The cache's hit rate is 95%. Do you agree?Show the answer

Measure first

Build the , the hit rate at every cache size, from production traffic, using reuse-distance sampling such as SHARDS. If the curve is flat around the current size, more memory buys nothing.

Diagnose

p99 lies above the 95% line, so it is made of misses. To move p99 the hit rate would have to exceed 99%. With skewed (, α = 1) popularity over N keys, the cache needed for hit rate h grows like C = N^h, so each extra point of hit rate costs a multiplicatively larger cache.

Recommend

Work on the miss path instead: add a tier below, or hedge slow reads. Grow the cache only to a in the curve, and only if the curve shows one.

Read moreThe tail ruleKnees and plateausMeasuring it cheaply: SHARDSThe exchange rate

3Your app servers each have a local NVMe block cache. Someone proposes adding a shared remote cache, such as Redis, between the servers and the database. How do you decide?Show the answer

Compare

A remote hit costs 0.3–1 ms; a local NVMe read about 0.1 ms. The proposed tier is slower than the one above it.

Rule

A new tier pays only if its hit rate h exceeds t_new / t_below: its own access time divided by the access time of the tier it saves you from reaching.

Diagnose

The remote tier sees only what the local caches missed, a filtered stream with the already removed. Its hit rate will be lower than on raw traffic, and it needs a different policy from the tier above.

Recommend

Add it only for what a local cache cannot do: pool capacity across servers, hold derived results, or survive process restarts. And only if its measured hit rate on the filtered stream clears the rule.

Read moreA distributed cache is not a latency tierThe tier-insertion ruleFiltered streams

4Dashboard queries hit the result cache well on weekdays. After the source table moved to streaming ingestion, with a commit every 10 seconds, the hit rate is near zero. Why, and what do you do?Show the answer

Diagnose

Every commit changes the table and invalidates every cached result that reads it: w = 0.1 changes per second. Reads per version, λ / w, are now tiny and fall below the cost-to-saving ratio P / S, so . A dashboard that reads several tables is worse: its change rate is the sum of theirs.

Options

Stop result caching for that table. Cache what does not change instead: file blocks and metadata. Or maintain an incremental , keeping (sum, count) pairs so averages update from each commit's delta.

Trade-off

A view costs maintenance work on every commit, and it is stale between refreshes.

Read moreAmortization: does the cache pay?Result cachingMaterialized views

5A misbehaving crawler requests millions of article IDs that do not exist. Each one misses the cache and hits the database. Design the defence.Show the answer

Negative cache

Cache the "not found" answers with a short , in their own capacity partition with a cap, so absent keys cannot evict real articles.

Bloom filter

For a very large ID space, put a of valid IDs in front, sized for a chosen false-positive rate. It turns away most bad IDs without any lookup.

Correctness

When an article is created, delete its negative entry; otherwise the new article is reported missing.

Failure mode

A stale "not found" is the worst experience here: the content exists, and the user is told it does not.

Read moreNegative caching and Bloom filters

6Reading a table's schema through the catalog takes 200 ms, and every query needs it. How do you cache it safely?Show the answer

Split by change

In open the are immutable: cache them with no expiry. Only the pointer to the current changes; give it a short TTL, so all file listings agree on one snapshot.

Schema safety

Resolve columns by ID, not by name or position, so a stale schema cannot make a reader misinterpret the bytes.

Decide per kind

Stale metadata costs differently by kind: a stale file list hides data (absence), a stale schema can give wrong answers (correctness), stale statistics give bad plans. Choose a TTL for each.

Read moreMetadata caching

7A homepage entry in the cache expires every 10 minutes, and the database spikes each time. 40 web servers share one cache; about 1,500 requests per second hit that key, and recomputing it takes 2 seconds. Fix it.Show the answer

Estimate

By , 1,500 requests per second × 2 s = 3,000 duplicate queries at every expiry.

Options

on each server still leaves 40 recomputes across the fleet: better, but not one. Probabilistic early expiry, scaled by the measured recompute time, needs no lock and works across the fleet for a little extra work. If the homepage may be a few seconds old, , which adds up to one recompute time of staleness.

Also

Add 10–20% to every TTL, so entries filled together, for example at a deploy, do not expire together.

Failure mode

If the origin is already near capacity, the feedback loop can keep it down after the trigger has passed. Add load shedding.

Read moreStampede

8Editors complain that their saves do not show up on the site, which reads through a cache. Engineers propose updating the cache entry on every write. What do you advise?Show the answer

Advise

Do not update. Write the store, then delete the key, and keep a TTL as the floor.

Why

Two concurrent writers can leave the older value in the cache last (the update race). A reader can put the old value back after the delete, during a window as long as one read (populate-after-invalidate).

Read your writes

Give the editor a version token from the write and include it in the key on their later reads. They see their own save without sticky routing.

Trade-off

The first read after each write is a miss. Accept it, or use only for keys that are reliably read right after they are written.

Read moreWrite-invalidate versus write-updateThe cells that must stay emptySession guarantees

9A cache cluster with a 95% hit rate must be restarted for a kernel update. What happens if you simply restart it, and how do you plan it instead?Show the answer

Estimate

is 1 / (1 − 0.95) = 20: the database could see up to 20 times its provisioned load.

Plan

Do not restart all nodes at once. Snapshot and restore the cache if it supports that. Otherwise ramp traffic so that the misses, , stay below the origin's capacity, reading h(t) from the .

Avoid

Replaying yesterday's most popular keys, if popularity shifts from day to day; and shadow traffic, if the origin has no spare capacity.

Safety net

Request coalescing and a load-shedding rule. Record the cache as a dependency of the database.

Read moreWarming and prefetchLeverage: how much do you depend on the cache?

10One cache, three problems: a nightly 400 GB crawl flushes it, a single viral article overloads one cache node, and a second team's dashboards slow down the main product. Design the fix.Show the answer

The crawl

Use an with a frequency (). Only its small admission window, 1% of a 16 GB cache or about 160 MB, can be polluted, however long the scan. Have the crawler send a bypass hint too: it knows it will not reuse what it reads.

The viral key

Keep a near-cache of the 100 hottest keys in each client, with a 5 s TTL. The skew that makes the key hot also makes the hot set small enough to fit.

The tenants

Partition capacity by cost class and by tenant, with admission per partition. Pin entries only where a hard guarantee is needed, accepting that a pinned entry is no longer a cache.

Also

Use with 256 , and reshard with the same warm-up ramp.

Read moreAdmission beats evictionThe thing hashing cannot fixPartitioned capacity

11Review this design: Redis in front of Postgres, 5 million rows, a 10-minute TTL, LRU eviction, and sharding by hash(key) % N. What do you challenge?Show the answer

Ask

  • Does anything write to Postgres without passing through the cache path? Then the TTL is the only freshness.
  • Do cheap and expensive entries share one ?
  • What happens during large scans, with no admission filter?
  • What happens when a node is added or lost? Modulo sharding moves (N − 1) / N of the keys.
  • Which keys are hot? The top key may carry several percent of all traffic.
  • What does Postgres do at 1 / (1 − h) times its normal load if the cache is lost?

Assumptions

What the cache assumes and cannot check: no unknown writers, complete invalidation sets, popularity skew that stays the same, and TTLs that expire independently.

Recommend

Consistent hashing with virtual nodes; W-TinyLFU or cost partitions; jittered TTLs; a warm-up ramp controller; a near-cache for hot keys; and the cache drawn on the dependency diagram.

Read moreNine prohibitions and their escapes

12An SSD cache in front of object storage makes a dashboard fast, but once a day it is very slow for an hour. Why, and how do you fix it?Show the answer

Diagnose

A large scan (a batch job, or an analyst) evicts the hot set: LRU treats every scanned block as recently used. The hit rate falls from about 90% to near zero, so the mean moves from about 5 ms to about 50 ms. The cache's hidden assumption, that the future looks like the past, broke.

Fix

Make admission scan-resistant (do not admit blocks read sequentially, or use ARC, LRU-K or TinyLFU); give batch work a separate cache or a quota; and monitor miss , since .

Trade-off

The scan itself is not cached, which is correct for data read once.

Read moreTiered storage and cachingThe mean is not where you thinkThe unverifiable assumption of a cache