Stale-While-Revalidate Cache

A cache expiry synchronizes callers unless refresh ownership is controlled. Stale-while-revalidate trades bounded freshness for stable origin load.

Caching · TypeScript

Stale-While-Revalidate Boundary

Cache expiry must not synchronize callers into an origin outage.

const cached = await cache.get(key)
if (cached?.freshUntil > Date.now()) return cached.value
if (cached?.staleUntil > Date.now()) {
  if (await lease.tryAcquire(key, 10_000)) {
    void refresh(key).finally(() => lease.release(key))
  }
  return cached.value
}
// A miss also needs shared single-flight; do not fan out origin loads.
return lease.runExclusive(key, () => loadAndCache(key))

Invariant: One refresh occurs per hot key while callers receive bounded-stale data.

Use when: A hot key expiring could stampede the database.

Why this boundary matters

A hot-key expiry can release thousands of identical origin reads. One refresh owner contains that load while bounded-stale data preserves service.

Failure policy

BoundaryAction
Fresh valueServe immediately
Stale but permitted valueServe it and elect one refresher
Cache missLoad with single-flight protection
Refresh failsServe stale only inside the maximum stale window
Maximum stale age exceededFail or load synchronously according to product policy

Trade-offs

Stale-while-revalidate protects origin capacity and latency by weakening freshness. A distributed lock adds failure modes; no lock risks a stampede. Sensitive or rapidly changing data may not tolerate stale service.

Decision rule: Serve stale only when the business cost of bounded staleness is lower than the availability cost of an origin surge.

Further reference

Browse all engineering snippets · Read about cache consistency

>