← Recipes

阅读中文版本

Warm Critical Cache Keys Before Traffic

Preload high-value keys after a deploy or cache flush, coalesce the warm-up, and verify hit ratio without turning startup into a stampede.

~30 mincachingperformanceoperations

Task

Avoid a cold-cache latency spike and origin overload when a deploy, restart, or cache flush removes hot data.

When to use this

A cache flush during peak traffic sends every request for the homepage and top products to the database, causing a latency spike and timeouts.

Prerequisites

  • A cache with a way to set keys and TTLs
  • A list or query for the highest-value keys
  • A way to run the warm-up outside the request path

Steps

1. Select a bounded set of keys by traffic or business value, not the entire dataset.

const hotKeys = await db.query(
  `SELECT id FROM products ORDER BY views_last_7d DESC LIMIT 500`
);

Expected output: The warm-up targets a bounded, high-value set instead of the full database.

2. Warm keys concurrently with a small concurrency limit and reuse the single-flight loader to avoid duplicate origin calls.

await mapLimit(hotKeys.rows, 8, async ({ id }) => {
  const key = `product:${id}`;
  if (await cache.exists(key)) return;
  const value = await singleFlight(key, () => db.loadProduct(id));
  await cache.set(key, JSON.stringify(value), { EX: 300 });
});

Expected output: The cache contains the hot keys before traffic arrives, with bounded database concurrency.

3. Verify hit ratio and origin QPS after the warm-up and compare with a cold start.

curl -s http://localhost:3000/metrics | grep cache_hit_ratio

Expected output: The hit ratio rises before the traffic peak and origin QPS stays within capacity.

Troubleshooting

Problem

Warm-up slows deployment

Solution

Run it after the instance is healthy but before it receives traffic, with a time limit; do not block the entire rollout on a huge list.

Problem

Warm-up causes a stampede

Solution

Use a small concurrency limit and single-flight; multiple instances should share a distributed lock or stagger their warm-up.

Limitations

  • Warmed keys can still be evicted or expire; the TTL and eviction policy remain the real protection.
  • A warm-up list based on historical traffic can miss a new hotspot.

Cold caches hurt most at peak

A restart, deploy, or manual flush empties the cache. The next requests all miss and hit the database at once. If the hot set is known, warm it before the instance receives traffic. Warm a bounded set, not the entire dataset; a warm-up that loads everything is a self-inflicted stampede.

await mapLimit(hotKeys, 8, async (key) => {
  if (await cache.exists(key)) return;
  const value = await singleFlight(key, () => load(key));
  await cache.set(key, JSON.stringify(value), { EX: 300 });
});

Bound concurrency and deduplicate

A warm-up should not open hundreds of database connections or duplicate work across instances. Limit concurrency, use single-flight for duplicate keys, and consider a distributed lock so only one instance warms a given key. Stagger the warm-up across instances if the origin cannot handle the combined load.

Measure the effect

Compare cache hit ratio and origin QPS after a cold start with and without warming. A warm-up that runs after traffic arrives does not prevent the spike. Add it to the deploy sequence: start the process, warm within a time limit, then mark ready. If the warm-up fails, decide whether to serve with a cold cache or wait; do not block readiness indefinitely.

Verify the warm-up

Expected outcome: after the warm-up completes, the target keys exist in the cache, the hit ratio is above the pre-warm baseline before traffic arrives, and origin QPS stays within capacity. Confirm by sampling the hot keys with the cache client, comparing hit-ratio metrics with a cold-start run, and checking that the warm-up duration did not exceed the deploy budget. A key that is still missing points to a TTL, serialization, or key-prefix mismatch.