Revalidation — the cheap question with the expensive wait

When a cached copy goes stale, the edge could ask the origin a yes/no question before re-downloading the whole body — that question is called a conditional GET, and the origin's tiny 'no, it hasn't changed' answer is called 304 Not Modified. Bytes are saved; the round trip is not.

Previously

When the edge's copy goes stale, it does not re-download the whole body — it asks origin a cheap question first.

Scene 05

Revalidation — the cheap question with the expensive wait

  1. Watch
  2. Try it
  3. Predict
  4. Capture
RevalidationCache-Control: max-age=60max-age=60no-cacheno-storePOP · edgecache/logo.pngETag 'v7'served from edge (no origin trip)FRESHorigintruth/logo.pngcurrent ETagcompares If-None-MatchGET /logo.png · If-None-Match: v7WATERFALL · wait-for-server per requestgreen = wait · blue = body bytesno requests yet — issue one to see the waitmax-age: served locally while fresh; conditional GET only on stale.
What to watch for

The cell starts FRESH. After a moment its TTL expires and the next request triggers a conditional GET — the edge asks origin 'is this still v7?'. Origin returns a thin 304 (no body), the edge serves the stored bytes locally, and the cell goes back to FRESH.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Edge.handleRequest
decides: serve local, revalidate, or bypass
def handleRequest(url):
cell = cache.lookup(url)
if cell.cacheControl == 'no-store':
return origin.fetch(url) # always full 200
if cell.cacheControl == 'no-cache':
return revalidate(cell) # ask every time
# max-age: serve locally while fresh
if cell.fresh:
return cell.body # ~2 ms, no RTT
return revalidate(cell) # stale -> ask
Edge.revalidate
conditional GET: 304 keeps stored body, 200 replaces it
def revalidate(cell):
headers = {}
if cell.etag: # If-None-Match: "v7"
headers['If-None-Match'] = cell.etag
resp = origin.fetch(cell.url, headers) # full RTT
if resp.status == 304:
cell.refreshTTL() # body unchanged
return cell.body
# 200 OK: origin sent a new body + new ETag
cell.body = resp.body
cell.etag = resp.headers['ETag']
return cell.body
Origin.handleGET
compares If-None-Match; sends 304 (no body) or 200 (full body)
def handleGET(url, headers):
asset = store.load(url)
clientEtag = headers.get('If-None-Match')
if clientEtag and clientEtag == asset.etag:
# short-circuit: empty body, ETag in headers
return Response(
status = 304,
headers = { 'ETag': asset.etag },
body = None,
)
return Response(200, asset.headers, asset.body)

Where this sits in Build a CDN

Scene 05 of 13, in the Freshness act — TTL, revalidation, stale-while-revalidate.. When stale, the edge sends a conditional GET with the ETag; origin replies 304 Not Modified — body is empty, but the round trip isn't.

Up next. The user still waits one origin RTT on every revalidation — unless we let the edge serve stale immediately and refresh in the background.

All 13 scenes in Build a CDN · Every curriculum

Built with Arqly
Every scene in Build a CDN builds on the one before it.All 13 Build a CDN scenes