Build a CDN (13 scenes)
Scene 07 · Purge — URL, surrogate key, or sledgehammer
Three purge flavors: URL (precise, slow at scale), surrogate-key (atomic, fast), zone-wide (sledgehammer that stampedes origin).
Previously
Purge is the escape hatch you reached for at the end of the last scene — but purging a copy in one POP doesn't help the other 299 POPs still serving it, so the question becomes WHICH POPs and WHICH entries one purge call actually invalidates.
Scene 07
Purge — URL, surrogate key, or sledgehammer
Diagram
A world map with 12 POPs; each POP holds a small grid of cached cells colored by surrogate-key tag (blue = product:42, orange = homepage, green = catalog, grey = other). The bottom panel offers three purge flavors: URL purge clears one cell per POP, surrogate-key purge clears every cell sharing the tag, zone-wide purge clears everything. After a deploy, you don't want to wait for TTL — you want to actively tell the cache to forget; that operation is called a purge. **purge** — invalidate cached entries across the CDN before their TTL expires. **surrogate key** — a tag the origin attaches to a response (Fastly term; Cloudflare calls them cache tags); one purge call invalidates every object sharing the tag. The propagation timeline shows p50 (~150 ms) and p99 (seconds) for the wave to reach all POPs; the origin RPS gauge spikes when zone-wide purge forces every POP to refill at once.
12 POPs, each with its own cached cells. Watch the purge wave radiate from the ingress POP.
A URL purge for /logo.png is fired from the Frankfurt ingress POP. Watch the wave ripple outward across all 12 POPs — only the /logo.png cell clears in each one. Origin RPS stays flat: each POP's next request will be a single miss.
Implementation
Edge.purge_handlers
three flavors — same control plane, different match rules
1def purge_url(url):2 p = { kind: 'url', target: url }3 broadcast_purge(p) # one cell per POP45def purge_surrogate_key(tag):6 p = { kind: 'surrogate', target: tag }7 broadcast_purge(p) # every cell sharing tag89def purge_zone():10 p = { kind: 'zone', target: '*' }11 broadcast_purge(p) # every cell, every POP
POP.apply_purge
what each POP does when the purge message arrives
1def apply_purge(p):2 for cell in self.cache:3 if matches(cell, p):4 cell.invalidate() # next request = miss5 ack(p.id, self.pop_id)67def matches(cell, p):8 if p.kind == 'zone': return True9 if p.kind == 'url': return cell.url == p.target10 if p.kind == 'surrogate': return p.target in cell.tags
Control.broadcast_purge
gossip the purge to every POP; track per-POP completion
1def broadcast_purge(p):2 p.id = new_purge_id()3 pending = set(all_pops)4 for pop in all_pops:5 pop.send(p) # bimodal multicast / gossip6 while pending:7 pop_id = wait_for_ack(p.id)8 pending.remove(pop_id)9 # p50 ~150 ms; p99 = slowest POP in the fleet10 return { p50: ack_p50(), p99: ack_p99() }