Skip to content
DBRaven
Pattern · caching

Refresh-Ahead Cache

emerging

Summary

Proactively re-fetch a cached value from the database shortly before its TTL expires, in the background, so a request never has to wait on a cache miss for a key that is still being actively read.

Problem

Passive TTL expiration means every cached key eventually goes cold, and the request that happens to arrive right after expiry pays the full database round- trip synchronously and, if the key is hot, can trigger a stampede of identical concurrent misses; refresh-ahead avoids paying this cost on the request path for keys that are still actively being read.

Description

In passive TTL expiration (the safety net cache-aside relies on), a key sits in the cache until its TTL elapses, then disappears entirely; the next request for that key is a full miss and pays the database round-trip synchronously, in the request path. Refresh-ahead changes this by watching access recency and expiry time: when a cached key is still being read close to its expiry, a background process re-fetches the value from the database and rewrites the cache entry before the old one expires, so the key never actually goes cold while it remains hot.

Contrast with the two mechanisms it is most easily confused with: - Passive TTL expiration: the cache entry simply disappears at expiry; the next

reader pays the full miss cost synchronously. No background work happens.

- Read-through: population happens lazily, in the request path, on a miss — the

reader that causes the miss is the one who pays for it. Read-through's miss

coalescing prevents duplicate concurrent fetches, but it does not prevent the

fetch itself from happening synchronously to a request.

- Refresh-ahead: population happens proactively, out of the request path, before

the entry would otherwise expire. No reader pays a miss cost for a key that is

being kept warm this way, at the cost of running background refresh work for

keys whose next read may not actually happen.

Refresh-ahead is a stampede-avoidance technique in the same family as mutex locking and probabilistic early expiry, all of which exist because a cache key expiring under sustained read load is the single most common trigger for a cache stampede. Refresh-ahead avoids the stampede differently than mutex locking or probabilistic early expiry do: instead of controlling how concurrent requests behave *after* a miss happens, it tries to prevent the miss from happening at all for keys that are still being actively read. It does not eliminate stampede risk entirely: a key that is refreshed just as its refresh window opens, but then receives an unexpected burst of new concurrent readers before the refresh completes, can still stampede exactly as it would under passive TTL expiration. Refresh-ahead is a mitigation for the common case (steady access to a hot key), not a guarantee against every miss-under-load scenario.

Tradeoffs

Miss latency on hot keys
+0.8

A key kept warm by refresh-ahead never presents a synchronous miss to a reader while it is being actively refreshed

Staleness window
-0.2

A refreshed key can still be marginally stale between the refresh and the next write, the same staleness class as any TTL-bounded cache

Stampede avoidance
+0.5

Reduces the common case (steady access to a hot key expiring under load) but does not eliminate a burst of new readers colliding with an in-flight refresh

Operational complexity
-0.5

Requires a background refresh mechanism to build, run, and monitor, on top of the cache itself

Wasted work risk
-0.3

Refreshing keys that are not read again spends database load and cache churn for no benefit

When to use

Access pattern for a key is predictable enough to justify refreshing before expiry

Refresh-ahead spends background work keeping a key warm; if the key's next read is unpredictable or rare, that work is often wasted

The workload cannot tolerate a synchronous miss penalty on hot keys

Refresh-ahead specifically targets the case where a reader hitting a cold hot-key is unacceptable, unlike read-through which still lets the request path pay for the fetch

A background job scheduler or refresh-capable cache client is already part of the operational surface

Refresh-ahead requires something to run the proactive refresh; adopting it purely to avoid one component is usually not worth the tradeoff

When not to use

Access pattern is unpredictable or long-tail (most keys read once or rarely)

Refreshing keys that are unlikely to be read again wastes database load and cache churn for no benefit over passive expiration

The team cannot operate and monitor an additional background refresh process

An unmonitored refresh job that silently stops running degrades invisibly back to passive TTL expiration, but with the added complexity left in place

Database load from proactive refreshes would itself become a capacity problem

Refresh-ahead trades reactive miss load for proactive refresh load; at high key cardinality this can exceed the load it was meant to avoid

Operational Requirements

mandatory

Monitor the refresh job's own health separately from cache hit rate

A silently-stopped refresh process degrades back to passive TTL expiration with no visible signal in cache metrics alone

mandatory

Bound refresh eligibility to keys with recent access, not all cached keys

Refreshing every cached key regardless of recency turns refresh-ahead into unconditional proactive re-population, defeating the point of targeting only hot keys

recommended

Rate-limit or batch proactive refresh queries against the database

Uncapped refresh concurrency at high key cardinality can itself become the load spike refresh-ahead was meant to avoid

Characteristics

Scales on
read
Implementation complexitymedium
Operational complexitymedium
Scaling ceilingRefresh-ahead does not change the cache's memory ceiling; it changes when writes to the cache happen relative to reads. Its real ceiling is the database load generated by proactive refreshes: as the number of actively-read hot keys grows, the background refresh workload grows with it, and at high enough key cardinality the refresh traffic itself becomes a source of database load that must be capacity-planned separately from read traffic.

Technologies

Canonical

redis

Alternatives

memcached

Relationships

Evolves from

cache aside

Complements

cache asideread through cache

Basis

The mechanism and its contrast with passive TTL expiration and read-through are well-established caching concepts, but refresh-ahead is less uniformly named and less commonly natively supported across caching systems than cache- aside, read-through, or write-behind, so confidence is held below those more universally documented entries pending a dedicated sourcing pass.