Stale-while-revalidate — and the bug it ships
stale-while-revalidate serves the cached copy instantly and refreshes from origin in the background, removing the user-visible wait at TTL — but it also extends any cached bug for the entire SWR window after you deploy a fix unless you issue an explicit purge.
Revalidation works but the user still waits one origin RTT on every check — unless we let the edge serve stale immediately and refresh in the background. That directive is stale-while-revalidate.
Scene 06
Stale-while-revalidate — and the bug it ships
- Watch
- Try it
- Predict
- Capture
The 'now' cursor sweeps from t=0 across the cell's lifetime. While it's in the green FRESH band, the cell serves v6 instantly. The moment it crosses TTL into the amber STALE·SWR band, the edge KEEPS serving v6 instantly — and a background arrow fires to origin to refresh. When that lands, the cell flips to v7 and subsequent users see the new version. User-visible latency stays ~2 ms across the boundary.
Highlighted lines are the ones running in the diagram right now.
def handleRequest(url):cell = cache.lookup(url)age = now() - cell.storedAtif age < cell.maxAge:return cell.body # FRESH hit, ~2 msif age < cell.maxAge + cell.swr:spawn revalidate(url, cell) # asyncreturn cell.body # STALE, served instantly# past TTL+SWR — blocking origin fetchfresh = origin.fetch(url)cache.store(url, fresh)return fresh.body
def revalidate(url, oldCell):# runs in the background; user has already been servedfresh = origin.fetch(url) # may return v6 or v7cache.store(url, {body: fresh.body,storedAt: now(),maxAge: fresh.cacheControl.maxAge,swr: fresh.cacheControl.swr,})# next request lands in the new FRESH window
def purgeUrl(url):# explicit invalidation at the edgecell = cache.lookup(url)cache.evict(url) # next request misses, fetches v7# without this call, the edge keeps serving the cached# copy until storedAt + maxAge + swr — even after# origin has been deploying the fix the whole time
def recordServe(servedVersion, t):if deployTimeSec is None:return # no deploy yet; nothing to countif t < deployTimeSec:return # served before the fix shippedif servedVersion == PRE_DEPLOY_VERSION:bugCounter += 1 # user saw the bug post-fix
Where this sits in Build a CDN
Scene 06 of 13, in the Freshness act — TTL, revalidation, stale-while-revalidate.. SWR removes the user-visible TTL-boundary wait by serving stale and refreshing in background — and extends any cached bug for the SWR window after deploy.
Up next. Purge is the escape hatch — but purging this URL in one POP doesn't help the other 299 POPs that are still serving v6.