Networking
Part 4 of 6 · CDN & edge cachePurge & Generation Tokens
Instant purge races with shields; soft purge plus generation tokens / surrogate keys make invalidation deterministic. Double-purge or bump the token when the shield can refill stale.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Overview
A HIT is only useful if you can stop serving a representation. TTL is passive. Purge is active. Generation tokens change the key so you never have to chase the old object.
Staff interviews go after the hierarchy interaction: instant purge races with shields. If you only delete at the edge, the edge misses into an unpurged shield and re-caches stale. Fastly documents this; every shielded CDN has an equivalent.
Why tokens plus soft purge win
| Mechanism | Behavior | Use when |
|---|---|---|
| TTL expiry | Passive | Immutable hashed assets (app.abc123.js) |
| Hard / instant purge | Object gone; next request is a miss | Must stop serving now (security, legal) |
| Soft purge | Mark stale; SWR revalidates | CMS publish; protect origin |
| Surrogate-key / cache-tag | Invalidate a graph of URLs | Product 42 appears on 50 pages |
| Purge by prefix | Directory-style (Cloudflare) | A path tree, no query strings in the prefix |
| Generation token | Version in URL or key; old object unused | Deploys of JS/CSS; deterministic CI cache-bust |
- 1
CMS publish → stale refill from shield
Hard purge edge only
Edge deletes v1. Next GET misses, shield still holds v1, edge stores v1 again. Users never saw the publish. This is the race.
- 2
Winner: generation in the key, or purge every tier
HTML starts referencing
/p?g=2(or a hashed filename). Shield has no g=2, origin fills v2. Old g=1 ages out. Alternatively purge shield then edge (or twice) so a miss cannot refill stale. - ?
Soft purge when origin must not melt
Mark stale and let SWR plus collapsing refill. Use instant purge only when serving stale is unacceptable. Never purge-all as a deploy step.
Purge by URL, tag, and surrogate-key
URL purge invalidates one request URL (and maybe its variants). Fine for a hotfix. Bad for "this article is embedded in twenty templates."
Tags / surrogate keys are response headers the origin sets, for example Surrogate-Key: product-42 category-shoes or Cloudflare Cache-Tag. One purge API call drops every object that carried the key. That is O(1) group invalidation. Limits exist: Cloudflare documents a cap on tags per object; extra tags may drop silently — then a purge "succeeds" and stale remains.
Prefix purge is a path tree, not a tag graph. Do not put query strings in prefixes. It will not bust ?v= variants you thought you named.
Origin should emit tags at render time (article:88, author:9). The publish pipeline purges those names. Do not scrape sitemaps to build URL lists in CI.
Soft purge vs instant purge
Instant / hard purge removes the bytes. The next client is a miss. Correctness is strong; origin risk is high if the object is hot. Pair with collapsing and a shield or you bought a stampede.
Soft purge marks stale while leaving the body. Waiters get last-known-good; one revalidation runs. This is how you ship a CMS update without a herd. The lie lasts until refresh completes — same contract as SWR.
GDPR "right to be forgotten" is the awkward case: soft purge may keep serving personal data through the SWR window. Use instant purge (every tier) plus a generation bump on any URL that might still be bookmarked, and do not leave SIE sitting on that object.
Architecture
Purge race
- 1
Step 6 Instant purge edge only
- nextEdge empty, shield still v1
- 2
Edge empty, shield still v1
- nextNext miss refills v1
- 3
Next miss refills v1
Mitigations
- 4
Step 7 Purge shield then edge, or purge twice
- 5
Step 8 Bump generation in the cache key
Flow
- 6
Step 1 Client GET
- nextStep 2 Edge has object?
- 7
Step 2 Edge has object?
- yes, freshServe
- noStep 3 Fetch shield
- 8
Serve
- 9
Step 3 Fetch shield
- nextStep 4 Shield has object?
- 10
Step 4 Shield has object?
- yesFill edge — Age must be honored
- noStep 5 Origin
- 11
Fill edge — Age must be honored
- 12
Step 5 Origin
Lesson map
Purge & Generation Tokens
Instant purge races with shields; soft purge plus generation tokens / surrogate keys make invalidation deterministic. Double-purge or bump the token when the shield can refill stale.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB a["Step 1 Client GET"] b["Step 2 Edge has object?"] c["Serve"] d["Step 3 Fetch shield"] a -->|Step 1 Client GET| b b -->|yes, fresh| c b -->|no| d
Generation tokens
A generation token is a version id in the cache key: query (?v=20260920a), path (/static/20260920a/app.js), or a custom key field the CDN hashes.
Changing the token guarantees a miss without waiting for purge propagation. That is why CI/CD loves it for JS/CSS. HTML shells then point at the new URL. Old hashed assets stay until TTL; they are unused, not wrong.
Combine with tags: token for byte-identical deploys, tags for content graphs. Retiring a product line purges product-42; deploying a new bundle only changes app.js hash.
Short 8-hex tokens can collide in huge asset sets. Content hashes (full SHA) or ISO date plus git SHA are boring and sufficient.
Shield race, double-purge, Age
Order of operations after publish:
- Origin now returns v2.
- You hard-purge the edge key.
- A client misses at the edge.
- The shield still holds v1 (purge not applied, or not yet).
- Edge stores v1 with a new TTL. Publish vanished.
Mitigations that interviewers want named:
- Purge every tier (shield POP + edges), not only the URL at "the CDN."
- Double-purge: purge, wait for shield to drop or refill from origin, purge again so edges that recached stale drop it. Fastly's shielding docs describe this class of race.
- Generation token: the miss uses a new key the shield never had.
- Soft purge + Age: mark both tiers stale; do not let the edge treat a shield HIT as
Age: 0.
Deep dive · Vendor APIs, rate limits, purge-all
Fastly: purge by URL, surrogate-key, purge-all (emergency). Cloudflare: files, tags, prefixes, purge everything. Instant global latency is hundreds of milliseconds, not zero — design for "almost now." APIs are rate limited. A CI job that purges 50k URLs per deploy will 429; use tags or tokens instead. Purge-all is how you turn a deploy into an origin incident. Prefer versioned assets so HTML is the only thing you purge, and purge it softly.
Vendor knobs and a publish pipeline
| Feature | Fastly | Cloudflare | Varnish (self-hosted) |
|---|---|---|---|
| URL purge | Purge API / Fastly-Soft-Purge | files on purge_cache | purge vs ban |
| Group purge | Surrogate-Key | Cache-Tag | Ban expressions, custom headers |
| Prefix | Less common; keys instead | Prefix purge (no query in prefix) | Ban on URL regex (expensive) |
| Soft purge | First-class | Stale settings + tag purge | Mark stale in VCL |
| Generation | You put it in the URL/key | Same | Same |
ban in Varnish walks (or lazily matches) objects; a lumpy ban list is a CPU tax. Surrogate keys are an index. Prefer an index when the vendor has one.
CMS publish, one article, 50 URLs. Origin already sent Surrogate-Key: article-88 listing-home. Editor hits publish:
- Origin writes v2 to the database.
- Pipeline soft-purges
article-88andlisting-home(not 50 URLs). - Shield and edges mark stale; SWR plus collapsing refill once.
- If the article contained a legal takedown, switch to instant purge of those keys on every tier, drop SIE, and bump any bookmarked URL's generation.
- Static bundle for the new editor UI is a hashed filename — no purge.
CI/CD assets. Do not call the purge API for JS/CSS. Content-hash the file (app.9f3c.js) or put a deploy generation in the path. HTML is the only object whose URL is stable, so HTML is the only object you routinely purge. If marketing insists on /app.js, you are forced into tokens or purge on every release — that is a cache-key mistake dressed up as invalidation.
Emergency purge-all. Use it when you shipped Cache-Control: public on an authenticated page. Then treat origin QPS as an incident: enable shield if it was off, lengthen SWR only for safe public objects, and do not repeat as a deploy ritual.
Playground: generation vs hard-purge race
Two maps: edge and shield. Origin holds the real body. Watch a single-tier hard purge recache stale, then a generation bump miss all the way to origin.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Expected teaching log: edge-only purge still returns v1 from the shield with zero new origin fetches; purging both tiers (or jumping to g=3) reaches origin.
Interview Q&A
Soft purge vs instant purge?
Answer
Soft purge marks the object stale and keeps serving it while a background revalidation runs (SWR). Instant purge deletes it so the next request misses. Soft purge protects origin; instant purge is for "must not serve this body again."
When do you purge by surrogate-key instead of URL?
Answer
When one logical change touches many URLs (product family, article graph, all images for a post). Tags give O(1) invalidation and keep you under API rate limits. URL purge is a scalpel for one broken path.
How do generation tokens bust the cache?
Answer
They change the cache key (path or allow-listed query). The new key has never been stored, so the request walks to origin (or shield miss → origin). No wait for purge fan-out. HTML must start pointing at the new token or clients keep requesting the old key.
Trade-offs of instant purge at scale?
Answer
Strong consistency, weak origin. Hot keys stampede unless shield + collapsing + jittered refill are in play. Purge APIs rate-limit. Propagation is fast, not instant. Purge-all is an incident factory.
How do Cloudflare cache tags work, and what is the trap?
Answer
Origin sets Cache-Tag (one or more). Purge requests name those tags. Trap: per-object tag caps — extra tags may be dropped, so a later tag purge misses objects you thought were labeled. Also tags are not a substitute for key normalization; they only group objects that were actually stored.
Can you combine generation tokens with surrogate keys?
Answer
Yes. Tokens version immutable bytes (bundles). Keys/tags invalidate a content graph when the CMS publishes. Use both: CI never waits on purge for JS; editors never enumerate 50 URLs.
Walk the shield purge race.
Answer
Edge purged, shield not. Next GET misses at the edge, HITs the shield, stores stale at the edge with a fresh TTL. Mitigation: purge shield and edge, double-purge after refill, or bump generation so the shield key does not exist. Honor Age so a stale shield HIT cannot be born as Age 0.
Why does adding ?v=2 sometimes do nothing?
Answer
The CDN is configured to ignore query strings on that path (common for static assets). The token never entered the key. Put version in the path or allow-list that param. Same class of bug as unsorted vs stripped query on the keys page.
GDPR takedown vs SWR?
Answer
You cannot rely on soft purge plus a day of stale-if-error to stop serving personal data. Instant-purge every tier, drop SIE on that object, and change URLs/tokens that might be cached in browsers too (max-age on the client is another copy you do not purge).
What should a deploy purge?
Answer
Hashed assets: nothing. HTML shells: soft-purge tags, or a generation field on the HTML key. Never purge-all. Watch origin QPS after the purge; have a rate limit and a staged tag list.
BAN vs surrogate-key — why does the index matter?
Answer
A Varnish-style ban is often a predicate the cache must test (immediately or lazily) against objects. That costs CPU and can surprise you under load. Surrogate keys / cache tags are an inverted index: purge article-88 touches only labeled objects. When the vendor offers tags, use them instead of regex bans for routine CMS publishes.
Do generation tokens replace purge for HTML?
Answer
Only if the HTML URL itself changes (rare) or the HTML cache key includes a generation you control (custom key field, not a query the CDN strips). Most sites keep / and /product/42 stable for SEO, so they still purge or tag those shells. Tokens shine for immutable assets the HTML points at.
Pitfalls
| Pitfall | What actually happens |
|---|---|
| Over-purging / purge-all | Hit ratio dies; origin stampede |
| Edge-only hard purge | Shield refill race; stale is reborn |
| Short generation tokens | Collisions; two assets share a bust id |
| Tag cap exceeded | Silent drop; purge "works," content stays |
| Soft-purge window too long | Users see outdated prices/legal copy |
| Token only in ignored query | No busting at all |
| Purge API 429 in CI | Deploys fail or skip invalidation |
Ignoring Age | Shield-stale becomes edge-fresh |
Boxes: edge, shield, origin. Both caches hold v1. Origin flips to v2. Purge edge only, then draw the next GET. Write v1 back onto the edge. On the same board write the two fixes: purge shield, and g=2 in the key. That diagram is the whole lesson.
Go Deeper
- Fastly — Purging
- Cloudflare — Purge by cache tags
- Cloudflare — Purge by prefix
- RFC 9111 — HTTP Caching
Sibling pages: CDN hierarchy · Cache keys · SWR / stale-if-error · TLS / Anycast · Request collapsing