Skip to main content

CDN & Edge Caching at Scale

Level 3: Level 3: Scaling the Data Tiermedium30 mincdnedgeorigin-shield

An L1/L2/origin-shield hierarchy coalesces misses to ~1 fetch per object; version URLs instead of purging, normalize cache keys, and never cache authenticated bodies.

Move bytes closer, and shield the origin

A CDN exists to do two things: move bytes physically closer to users so latency drops, and absorb read traffic so your origin never sees the full load. A user in Sydney fetching from a single us-east-1 origin pays roughly 150 to 250 ms of round-trip time per request; an edge PoP 20 ms away turns that into a snappy response and, because the object is cached, the origin never handles the request at all.

There are two CDN fill models. A pull CDN is lazy: the edge fetches from origin on the first miss for an object, caches it, and serves subsequent hits locally. A push CDN is eager: you publish objects into the CDN ahead of demand. Pull is the default for almost everything because it is self-managing; push is reserved for large predictable launches (a game patch, a video premiere).

The multi-tier hierarchy and the origin shield

The structure that actually protects a fragile origin: many L1 edge caches close to users, a smaller set of L2 regional PoPs behind them, and a single origin shield in front of the origin. The shield is the key trick. When a popular object expires, thousands of edges could each miss and hammer the origin simultaneously. The shield coalesces those misses: it lets one request through to origin, holds the others, and fans the single response back out. On a burst the origin sees thousands of QPS instead of millions. Set stale-while-revalidate so the edge keeps serving the slightly stale object while one background fetch refreshes it.

Pull CDN hierarchy with an origin shield
Step 1 / 3

Stage 1 of 3: Without a CDN, a Sydney user pays roughly 150 to 250 ms of round-trip time to a single us-east-1 origin, and the origin handles every request itself.

Each tier shrinks what the next tier sees; pair the shield with stale-while-revalidate so edges keep serving while one background fetch refreshes.

Invalidation and cache keys

You have three tools. TTL expiry is simplest but coarse. Explicit purge is precise but slow to propagate globally and easy to over-use. The production default is versioned or content-hashed URLs: app.4f9c2a.js instead of app.js. A new deploy is a new URL, so you can cache the old one forever (immutable) and never purge; the HTML that references it gets a short TTL. This sidesteps invalidation almost entirely.

Check yourself
A teammate sets 'Vary: Cookie' on cached pages so that users with different cookies can never share an entry. What happens to the CDN hit rate?

Cache-key normalization decides your hit rate. By default the key is the full URL including query string, so ?utm_source=twitter and ?utm_source=email are two cache entries for one image. Strip tracking params, normalize casing, and only Vary on headers that actually change the body (like Accept-Encoding). Vary on Cookie and your hit rate collapses to near zero.

Interview nuance: the sharpest question is "what can you cache and what must you never cache?" Static assets and public semi-dynamic HTML: yes, with micro-caching (a 1 to 5 second TTL on the homepage still collapses a 100k-QPS spike to ~20 origin fetches/sec). Personalized or authenticated responses: never at a shared edge, or you leak one user's account page to another. Do personalization with edge compute (Cloudflare Workers, Lambda@Edge) that assembles a cached shell plus a small per-user fragment.

Recap: use a pull CDN with an L1/L2/shield hierarchy so the shield coalesces misses down to ~1 fetch per object, prefer versioned URLs over purging, normalize cache keys, micro-cache semi-dynamic HTML with stale-while-revalidate, and never cache authenticated bodies at a shared edge.

Check yourself

You are about to design the CDN layer. Sort each response by how the edge should treat it.

'app.4f9c2a.js', a content-hashed bundle
The public homepage during a traffic spike
A logged-in user's account page body
An API response keyed to an auth token

Apply

Your turn

The task this lesson builds to.

Design content delivery for a media site serving images, video, and semi-dynamic HTML to a global audience, where the origin is fragile and cannot absorb spikes.

Think about

  1. How does an origin shield coalesce fetches to protect the origin?
  2. How do you invalidate: TTL, purge, or versioned URLs?
  3. What dynamic content is cacheable, and what must never be cached?

Practice

Make it stick

A second problem on the same idea, so it survives past today.

Design the edge delivery for a live sports streaming event like a World Cup final peaking at 5 million concurrent viewers, where the origin encoder produces new HLS segments every 2 seconds and cannot be hit by more than a few thousand requests per second. Lead with how a fresh, uncacheable-by-age segment still shields the origin.

Think about

  1. How does a brand-new segment get served to 5M players with roughly one origin fetch?
  2. What TTL does the constantly-updating manifest deserve?
  3. What does the 2-second segment size trade against?