Sign in
TodayMapLearnPracticeReview
Library
14 MINadvancedCachingNot started

Refresh Ahead

Refresh-ahead proactively refreshes popular cache entries before they expire, so reads of hot keys always hit a fresh value and never trigger a thundering herd. The cache itself (or a background worker) tracks TTL countdowns and re-fetches from the database a configurable margin before expiry. The result: hot keys never miss, latency is uniformly low, and stampede risk collapses — at the cost of background DB load and wasted refreshes for keys that are no longer hot.

Why this matters

Cache stampedes on hot keys are the most common cause of production incidents in cached systems. When a viral post's TTL expires, thousands of concurrent readers all miss at once and stampede the database. Refresh-ahead is the canonical preventive pattern: instead of waiting for expiry, the cache refreshes hot entries in the background just before they would expire, so the foreground read path always sees a fresh hit. It is the strategy that powers news homepages, trending feeds, leaderboards, and any system where a single key can suddenly attract enormous read traffic. Understanding refresh-ahead means understanding how to make a cached system resilient to viral load.

Prerequisites
  • Cache Aside (Lazy Loading)
Related
  • Write Through
  • Cache Aside (Lazy Loading)
  • Cache Sizing
Used in

Foundational.

Lesson

How it works

In refresh-ahead, the cache proactively refreshes entries before their TTL expires. The classic trigger is a TTL threshold: if a key has a 60-second TTL, the system refreshes it when only 5 seconds of life remain. The refresh happens in the background — a worker thread or scheduler re-fetches the value from the DB and overwrites the cache entry, resetting the TTL. Foreground reads continue to hit the (slightly older but still valid) cache entry; they never see a miss and they never wait for a DB fetch.

This is the strategy that solves the cache stampede for read-only hot keys. Recall that write-through eliminates the stampede for write-heavy hot keys — the writes keep the cache warm. But for read-only hot keys (a viral post nobody is editing, a leaderboard, a homepage), there are no writes to keep the cache warm, and the TTL will eventually fire. Refresh-ahead is the answer: refresh before expiry, never let the cache miss for hot keys.

The trick is identifying which keys are hot. Refreshing every key before expiry defeats the purpose — most keys are never read again, and refreshing them is pure waste. The two common approaches are: (a) refresh only keys that have been read at least N times in the last interval (popularity tracking), and (b) refresh only keys whose TTL is about to expire and that were read recently (lazy refresh). Both aim to refresh only the keys that, if expired, would cause a stampede.

Three ways to trigger the refresh.

The refresh-ahead scheduler can be triggered in three ways, with different trade-offs:

  1. Time-based (TTL margin). Each key has a TTL; a background scheduler periodically scans for keys whose TTL is below the refresh threshold (e.g., 5 seconds remaining) and refreshes them. Simple, but scans every key — wasteful if most keys are cold.

  2. Popularity-based. Track per-key read counts in a sliding window. Refresh only keys above a popularity threshold. This focuses refresh work on keys that actually cause stampedes, but the popularity tracking itself is a data structure you have to maintain (often a sorted set or a count-min sketch).

  3. Read-triggered (lazy refresh). On a read, if the TTL is below the refresh threshold, return the cached value and trigger an async refresh. The current read still hits; the next read sees a fresh value. This is the cheapest to implement because it requires no background scheduler — refresh is piggybacked on real reads. The downside is that the first read after the threshold sees the slightly stale value (though not as stale as cache-aside).

Most production systems use (2) or (3). Variant (3) — sometimes called stale-while-revalidate at the cache level — is built into HTTP's Cache-Control: stale-while-revalidate and into CDN behaviors, and is the easiest to add to an existing cache-aside system.

stale-while-revalidate is refresh-ahead for HTTP

The HTTP Cache-Control directive stale-while-revalidate=30 says: "after this response becomes stale, you may serve it for up to 30 more seconds while you revalidate in the background." That is exactly refresh-ahead with a 30-second refresh window. CDNs (Cloudflare, CloudFront, Fastly) implement this natively. It is the cheapest possible refresh-ahead: no popularity tracking, no background scheduler, just a per-response directive. If your cache layer is a CDN or HTTP cache, this is the first thing to reach for.

The waste problem and the cold-key problem.

Refresh-ahead has two failure modes that pull in opposite directions:

  • Over-refreshing: if you refresh every key before expiry, you spend DB load on keys that are never read again. A key that was hot an hour ago but is now cold still gets refreshed — pure waste. The cost scales with the size of the cache, not with the read traffic.
  • Under-refreshing: if you only refresh popular keys, a key that suddenly becomes viral in the gap between refresh and expiry still stampedes. Refresh-ahead protects against steady-state hot keys, not against sudden spikes.

The standard mitigation for over-refreshing is to combine refresh-ahead with a popularity signal: only refresh keys that have been read at least N times in the last interval. This bounds the refresh work to the actual hot set. The standard mitigation for under-refreshing (sudden spikes) is to also have stampede protection on the read path — cache locking (single-flight) or probabilistic early expiration — so that even if a key slips through, the stampede is bounded to one DB fetch.

The deeper insight is that refresh-ahead optimizes for steady-state hot keys, not for spikes. If your access pattern is bursty — a key goes from 0 reads to 10,000 reads in a second — refresh-ahead will not help, because the key was not popular before the burst. For bursty traffic, you need stampede protection (single-flight) plus a short TTL, or pre-warming based on predictive signals.

When to use refresh-ahead:

  • Hot, read-only keys with steady-state traffic. Leaderboards, trending lists, homepage feeds.
  • Keys whose expiry causes stampedes you have observed in production. Don't add it preemptively — add it when you see the stampede.
  • HTTP/CDN-cached responses. stale-while-revalidate is free; use it.
  • When you can identify the hot set in advance. Pre-warming at scheduled times (e.g., refresh homepage every minute during peak hours).

When NOT to use refresh-ahead:

  • When keys are rarely read twice. Refreshing them is pure waste — let them expire and let cache-aside re-populate on the rare read.
  • When access patterns are bursty. Refresh-ahead protects steady-state, not spikes. Use single-flight instead.
  • When the DB cannot handle the refresh load. Refresh-ahead adds background DB load proportional to the hot set size; if the DB is already at capacity, this hurts rather than helps.
  • When freshness does not matter. If a 5-minute stale window is fine, cache-aside with TTL is simpler and equally effective.
Check yourself
core

A leaderboard key has a 60-second TTL and is being read 5,000 times/sec. Without refresh-ahead, what happens at t=60s?

Pick one answer.

Check yourself
solid

You enable refresh-ahead on every key in the cache. What problem will you observe, and how do you fix it?

Pick one answer.

Check yourself
interview

A previously-cold key suddenly becomes viral and gets 100,000 reads/sec. You have refresh-ahead enabled with popularity tracking. Will refresh-ahead save you from the stampede when the TTL expires?

Pick one answer.

Engineering mental model

Mental model. Think of Refresh Ahead as a deliberate boundary in a system. The boundary exists because something becomes harder to manage when everything is done in one place: latency, scale, failure isolation, consistency, cost, or team ownership. The useful question is not “what does Refresh Ahead mean?” but “what pressure makes this boundary worth introducing, and what new failure mode does it create?”

Design lens

Before choosing Refresh Ahead, name the workload, the critical user path, the dominant bottleneck, the failure you are trying to absorb, and the trade-off you are willing to accept. If you cannot name those five things, the design is probably premature.

Original NO CAP systems visual for Refresh Ahead.
Image unavailable. Original NO CAP systems visual for Refresh Ahead.
Refresh Ahead: a compact system-thinking visual.— Original NO CAP visual.
// Pseudocode
request = receive()
result = refresh_ahead(request)
return result

// Production questions:
// 1. What happens on timeout?
// 2. Can this operation be retried safely?
// 3. What is the bottleneck?
A minimal engineering sketch for reasoning about Refresh Ahead.

Back-of-the-envelope reasoning

Numerical lens: write down traffic, payload size, read/write ratio, peak multiplier and durability target before choosing a component. The numbers should justify the architecture.

Interactive sandboxdeterministic

Interactive thought experiment: Refresh Ahead

Change the variables below and predict what breaks first in Refresh Ahead. The production lab can later reuse these same inputs.

System pressure6%
Try this

Change one variable at a time. Predict the failure mode first, then move the slider and see whether your mental model matches the simplified system response.

Hint

If you are stuck on Refresh Ahead, start by drawing the request path and marking every network hop, stateful component, queue, cache and failure boundary. Then estimate where the system will saturate.

Check yourself
solid

You increase traffic by 10× in a system using Refresh Ahead. What should you inspect first?

Pick one answer.

Check yourself
interview

Which statement is the safest engineering habit when using Refresh Ahead?

Pick one answer.

Try this
interview

You have dashboards for traffic, latency, errors and saturation. You can change the architecture, but every change has operational cost.

Production scenario: your system uses Refresh Ahead, traffic suddenly spikes, and p99 latency doubles. What is your first move?

Interview drill

Answer this without notes: When would you choose Refresh Ahead, and when would you intentionally avoid it? Mention at least one bottleneck it addresses, one failure mode it introduces, and one alternative. Then quantify the workload you are designing for.

Engineering lens

For Refresh Ahead, reason in this order: what data is hottest, where can it live closer to the caller, what makes it stale, and what happens on a miss or cache failure. A cache is an optimization boundary, not the source of truth.

Numerical sanity check

A useful first-order model is cache_load = request_rate × (1 - hit_rate). If traffic is 20,000 req/s and the hit rate is 90%, roughly 2,000 req/s still reaches the origin before considering misses caused by expiration or eviction.

Check yourself
interview

Do not optimize for a memorized definition. Reason from the workload and failure mode.

You have a read-heavy Refresh Ahead path. Traffic doubles overnight. What metric would you inspect first, and what would convince you to add another cache layer?

Pick one answer.

Trade-offs

What you gain, what you pay

Pros
  • +Eliminates the cache stampede for steady-state hot keys.
  • +Reads of hot keys are uniformly fast (never miss, never wait for DB).
  • +Pairs naturally with `stale-while-revalidate` for HTTP/CDN caches — free at the protocol level.
  • +Refreshes can be scheduled off-peak to smooth DB load.
  • +Combined with popularity tracking, refresh work is bounded to the actual hot set.
Cons
  • −Wastes DB load refreshing cold keys unless popularity tracking is added.
  • −Does not protect against bursty spikes (a key that was cold and suddenly becomes viral).
  • −Adds operational complexity — a background scheduler, popularity tracking, monitoring of refresh lag.
  • −Refresh work scales with the hot set size, not with read traffic — a large hot set adds steady DB load.
  • −The hot set must be reasonably stable; rapidly shifting hot sets defeat the popularity signal.
Failure modes

How this breaks in production

  • Over-refreshing cold keys — pure DB load waste, often the first thing teams hit.
  • Sudden viral spike on a previously-cold key — refresh-ahead was not protecting it, stampede occurs.
  • Refresh scheduler falling behind — keys expire before the scheduler reaches them, defeating the purpose.
  • Popularity tracking itself becoming a bottleneck — sorted set or count-min sketch memory growth.
  • Refresh failure not retried — key expires silently and stampedes on the next read.
  • Clock skew between cache and scheduler — refresh fires late, key expires first.
Common mistakes

Don't fall into these traps

  • •Enabling refresh-ahead on every key without popularity gating — pure waste.
  • •Assuming refresh-ahead protects against spikes — it protects against steady-state expiry, not bursts.
  • •Not having a fallback (single-flight) when refresh fails — the stampede returns.
  • •Setting the refresh threshold too close to TTL expiry (e.g., 1 second before) — a slow refresh causes a miss.
  • •Setting the refresh threshold too far from TTL expiry (e.g., 30 seconds before a 60s TTL) — wastes half the cached value's lifetime.
  • •Forgetting that `stale-while-revalidate` already implements refresh-ahead at the HTTP layer — duplicate work.
Where you see it

Real systems using this

News homepages (BBC, NYT, CNN) — refresh headline caches every minute during peak hours.Leaderboards (gaming, fitness apps) — refresh-ahead on the top-N entries.Trending feeds (Twitter, Reddit) — refresh-ahead on the trending list, with single-flight fallback.CDN edge caches — `stale-while-revalidate` is refresh-ahead at the HTTP layer.Configuration and feature-flag services — refresh-ahead to keep hot config values fresh.
Teardowns

How real systems implement this

  • Cloudflare / Fastly CDN edge caches — Both CDNs implement `stale-while-revalidate` natively, which is refresh-ahead at the HTTP layer: a stale response is served immediately while the edge revalidates against the origin in the background. The next request sees a fresh response with no origin round-trip.
  • Twitter / X timeline caches — Hot timeline caches (the homepage for high-traffic accounts) use refresh-ahead with popularity-based triggering — only the top N most-read timelines are refreshed before expiry, bounding refresh work to the actual hot set.
  • Reddit ranking / leaderboard caches — Front-page ranking caches are refreshed ahead of TTL during peak hours to ensure reads always hit, with single-flight fallback for keys whose refresh fails or is delayed.
  • Discord presence / channel caches — Hot channel state (recent activity, online member counts) is refreshed ahead of TTL during high-traffic events (large streams, server raids) to keep read latency uniform.
Interview prompts

Practice saying it out loud

  • Q1Your news homepage gets 50,000 reads/sec and the cache TTL is 60s. At t=60s, the cache expires and the DB is overwhelmed. How do you fix this?
  • Q2Compare refresh-ahead, single-flight, and probabilistic early expiration. When would you choose each?
  • Q3You enabled refresh-ahead on every cache key and your DB load went up 30%. Why? How do you fix it?
  • Q4A viral post appears suddenly and your cache misses for the first 10 seconds. Refresh-ahead was enabled. Why didn't it help?
  • Q5How does HTTP `stale-while-revalidate` relate to refresh-ahead? Where does it not apply?
Research

Further reading & references

System Design Primer
Open source
ByteByteGo — Scale from zero to millions
ByteByteGo
System Design Tutorial
GeeksforGeeks
System Design Roadmap
roadmap.sh
Caching reference
Reference
Caching reference
Reference
Caching reference
Reference

Core explanations are original NO CAP material. External references are provided for deeper study and standards.

What next?

Mark as understood once the mental model clicks.

Next recommended

Write Through