Build a CDN (13 scenes)
Scene 05 · Revalidation — the cheap question with the expensive wait
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.
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
Diagram
On the left, the edge POP holding one cached cell with an **ETag** badge — an opaque version tag the origin attaches to a response so the edge can ask 'is this still v7?'. In the middle, two arrows: the outgoing **conditional GET** carries `If-None-Match: v7`; the incoming reply is either a thin green `304 Not Modified` (no body, but full RTT) or a thick blue `200 OK` (full body). At the bottom, a waterfall strip — the green segment is **wait-for-server** (origin RTT, paid on every revalidation), and the blue segment is body bytes (paid only when the ETag doesn't match).
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.
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.
Implementation
Edge.handleRequest
decides: serve local, revalidate, or bypass
1def handleRequest(url):
2 cell = cache.lookup(url)
3 if cell.cacheControl == 'no-store':
4 return origin.fetch(url) # always full 200
5 if cell.cacheControl == 'no-cache':
6 return revalidate(cell) # ask every time
7 # max-age: serve locally while fresh
8 if cell.fresh:
9 return cell.body # ~2 ms, no RTT
10 return revalidate(cell) # stale -> ask
Edge.revalidate
conditional GET: 304 keeps stored body, 200 replaces it
1def revalidate(cell):
2 headers = {}
3 if cell.etag: # If-None-Match: "v7"
4 headers['If-None-Match'] = cell.etag
5 resp = origin.fetch(cell.url, headers) # full RTT
6 if resp.status == 304:
7 cell.refreshTTL() # body unchanged
8 return cell.body
9 # 200 OK: origin sent a new body + new ETag
10 cell.body = resp.body
11 cell.etag = resp.headers['ETag']
12 return cell.body
Origin.handleGET
compares If-None-Match; sends 304 (no body) or 200 (full body)
1def handleGET(url, headers):
2 asset = store.load(url)
3 clientEtag = headers.get('If-None-Match')
4 if clientEtag and clientEtag == asset.etag:
5 # short-circuit: empty body, ETag in headers
6 return Response(
7 status = 304,
8 headers = { 'ETag': asset.etag },
9 body = None,
10 )
11 return Response(200, asset.headers, asset.body)