Engineering Atlas › System Design Fundamentals

Preloading a cold cache

12 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

Why cold caches hurt

Backends behind a cache are sized for the miss traffic at a steady hit rate. At 95% hits, the database sees 5% of reads; at 0% it sees 20 times as much. A cold cache taking full traffic is a self-inflicted stampede across every key at once, and often the database cannot serve it.

Google's SRE book names the two cases. A latency cache only speeds things up: the service can carry its expected load with the cache empty. A capacity cache is one the service cannot survive without, and it needs warming, slow load ramps and overprovisioning (Google SRE book, ch. 22). Know which kind you run, and test it the way Amazon suggests: load-test with the cache disabled (AWS Builders' Library).

Choosing what to warm

Access patterns are heavily skewed: a small fraction of keys gets most reads. Sources for the hot set, from best to worst:

  1. Recent access logs or cache key sampling, counting keys over the last hour or day.
  2. A warm peer: another replica of the cache, whose keys can be dumped and copied.
  3. Domain knowledge: today's top products, active users, the home page.
  4. The whole dataset, when it fits in memory and is small.

Ways to deliver

Keeping warmed data correct

Make sure the warm-up actually fills the cache your readers use. DAX keeps item and query results in separate caches, so a Scan cannot warm the item cache that GetItem reads (DAX docs). Warmed entries are as fresh as their source at copy time. Invalidations issued while warming must also reach the new cache, or it will hold values that the rest of the system already invalidated. The safe order is: subscribe the new cache to invalidations first, then load.

Down the stack

Layer 1The contract

A warm-up procedure should promise three things: the new cache reaches a target hit rate (say 90% of steady state) before it serves real traffic; the warm-up never pushes the source above a stated load budget (for example 2,000 extra queries per second on a read replica); and every entry it loads is subject to invalidations issued from the moment warm-up started, so warm data is no staler than normal cached data.

Layer 2The mechanism

Redis persists to RDB snapshots by forking and letting the child write the dataset while the parent keeps serving, relying on copy-on-write pages; on restart it loads the RDB and starts warm (Redis persistence docs). A new Redis replica sends PSYNC to its primary, receives a full RDB transfer, then streams the replication backlog. Memcached 1.5.18 and later supports warm restarts: started with -e pointing at a file on tmpfs, it keeps its item memory in that mapped file and, after a graceful SIGUSR1 shutdown, rebuilds its hash table from it in seconds, or minutes near a billion items. Sets and deletes sent while it was down are simply missed, so pause or buffer writes around the restart (memcached docs). Facebook's cold cluster warmup let a cold cluster's clients, on a miss, read from a warm cluster and populate locally with add; to avoid races with deletes they used a two-second hold-off during which adds for a recently deleted key fail (Facebook NSDI 2013). Netflix's EVCache warms new replicas by dumping keys from an existing replica to object storage and bulk-loading them. CDNs offer prefetch or prepopulate APIs, and an origin shield tier means new edge locations warm from the shield instead of the origin.

Layer 3The machine

Moving 100 GB over a 25 Gbps link takes at least 32 seconds at line rate, but per-key writes are the real limit: a single-threaded Redis shard accepts roughly 100,000 to 200,000 simple writes per second with pipelining, so 100 million keys on one shard take 10 to 15 minutes. Forking a 50 GB Redis process for an RDB snapshot copies its page tables (hundreds of megabytes) and pauses the parent for tens to hundreds of milliseconds, up to a second on a big dataset and a slow CPU (Redis persistence docs); heavy writes during the snapshot duplicate pages and can nearly double memory. Reading the hot set from a database is fastest as sequential range scans on a replica; millions of random point lookups would hit random pages and cost far more I/O.

Why it's built this way

  1. Cache memory is volatile and nodes are replaced constantly, so cold caches are a routine event, not an exception.
  2. Backends are sized for steady-state misses, so a cold cache under full traffic exceeds their capacity.
  3. Access follows a skewed popularity curve, so loading a small hot set restores most of the hit rate cheaply.
  4. Warm-up traffic competes with production load, so it reads from replicas or peers and is rate-limited.
  5. Data keeps changing during warm-up, so invalidations must reach the new cache before loading begins.

Diagram

flowchart LR
    L["Access logs (last 24h)"] --> P["Planner: top keys by count"]
    P --> W["Warmer job, rate-limited"]
    RR["Read replica"] --> W
    W --> N["New cache node"]
    INV["Invalidation stream"] --> N
    N --> G{"Hit rate >= 90%?"}
    G -->|no| W
    G -->|yes| LB["Shift traffic 10% to 50% to 100%"]

Worked example

A user-profile cache serves 40,000 reads per second at a 95% hit rate, so the database handles 2,000 per second and can take at most 6,000. The cache cluster is being replaced by a new one in a different instance family.

Cutting over cold would send 40,000 reads per second to a database rated for 6,000. Not an option.

Plan: access logs over 24 hours show 30 million distinct profiles, but the top 4 million account for 85% of reads. At about 2 KB per profile that is 8 GB, well within the new cluster's 64 GB. The warmer reads from a read replica in batches of 500 ids with WHERE id IN (...), at 80 batched queries per second, roughly 40,000 profiles per second, a load the replica absorbs easily. Loading 4 million takes about 100 seconds; with conservative throttling to protect the replica, 5 minutes.

The new cluster subscribes to the invalidation stream before the warmer starts. After warm-up, the load balancer shifts 10% of reads, then 50%, then 100% over 30 minutes, watching hit rate and database queries. Hit rate starts at about 85% and climbs to 95% as the long tail fills organically. Peak extra database load during the ramp is about 3,000 queries per second, inside the 6,000 limit.

In the wild

Trade-offs

OptionStrengthsWeaknessesChoose when
Preload from logs + replicaTargets real hot set, controllable rateNeeds logs, a planner and a jobPlanned migrations and scale-outs
Snapshot or replica syncExact copy, fastCopies staleness; same software onlyRedis restarts and replica adds
Read-through from warm tierDatabase untouched, organicCross-cluster latency during warm-upNew clusters or regions next to a warm one
Gradual traffic shiftNo list needed, real patternsSlow; needs weighted routingDeploys behind a load balancer
No warming, lazy fillZero effortMiss storm on every cold startSmall caches, backends with headroom

Pitfalls in production

Leadership lens

Check yourself

  1. Why does warming only the top few percent of keys recover most of the hit rate?

    Show answer

    Access is heavily skewed, so a small set of hot keys receives most reads; the long tail fills organically at a rate the backend can handle.

  2. Why must the new cache subscribe to invalidations before loading?

    Show answer

    Otherwise changes made during warm-up never invalidate the copied entries, which stay stale until their TTL expires.

  3. What is the risk of warming every key with the same TTL?

    Show answer

    They all expire together later, recreating a cold cache and a stampede at that moment.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →