Content Delivery Networks (CDNs)
A request that has to cross an ocean pays for it. The speed of light is not negotiable: a round trip from Sydney to Virginia is well over 200 ms before your server does any work at all. No amount of faster code fixes distance. The only fix is to move the content closer to the user — and that is exactly what a Content Delivery Network does. A CDN is a globally distributed fleet of caching servers (“edge” or PoP — Points of Presence) placed in hundreds of cities, so the answer is fetched from a machine a few milliseconds away instead of one a continent away.
A CDN is best understood not as a new primitive but as a composition of two blocks you already know: caching plus DNS/anycast routing. It’s a cache hierarchy whose outermost layer has been scattered across the planet, with DNS/anycast steering each user to the nearest edge.
What does a CDN buy us, and what does it cost? It buys dramatically lower latency for distant users and massive origin offload (most requests never reach your servers). It costs you staleness at the edge, a harder invalidation problem (now spread across hundreds of locations), and operational complexity — plus a bill.
How a request reaches the edge
Section titled “How a request reaches the edge” User in Tokyo ──► CDN edge in Tokyo │ cache HIT ──► served in ~5 ms, origin never touched │ │ cache MISS ──► fetch from origin (maybe via a regional ▼ parent cache), store it, then serve. Origin (your servers, one region) — touched rarelyAnycast or GeoDNS routes the user to a nearby edge. If the edge has the content, it serves it immediately. If not, it fetches from your origin (or an intermediate “parent” cache), stores a copy, and serves it — so the next nearby user gets a hit. The first miss pays the long-distance cost; everyone after rides the local copy.
Push vs pull: how content gets to the edge
Section titled “Push vs pull: how content gets to the edge”There are two ways the edge comes to hold your content:
| Pull CDN | Push CDN | |
|---|---|---|
| How | edge fetches from origin on first miss (lazy) | you upload content to the CDN ahead of time |
| Effort | minimal — just set caching headers | you manage what’s where and when |
| First request | pays a miss penalty | already warm |
| Best for | large, mostly-read catalogs (most sites) | a few large files, predictable demand (software releases, video) |
Pull is the default and the simplest: you do nothing special, the edge populates itself on demand based on real traffic, and rarely-requested content never wastes edge storage. The cost is that the first user in each region eats a cache miss. Push front-loads the work — you proactively place content so the first request is already fast — which is worth it when you know exactly what will be hot (a game patch dropping to millions at once) and don’t want any cold-start misses.
Static vs dynamic content
Section titled “Static vs dynamic content”CDNs were born for static content — images, CSS, JavaScript, fonts, video segments — that is identical for every user and changes rarely. This is the perfect cache fit: high reuse, long freshness windows, no personalization.
Dynamic content (a personalized feed, a logged-in dashboard) is the hard case, because it differs per user and changes constantly — there’s nothing to share across requests. Modern CDNs attack this from several angles:
- Cache cacheable dynamic content briefly (micro-caching) — even a 1-second TTL on a hot, public page can absorb a huge spike, because thousands of requests in that second collapse to one origin fetch.
- Edge compute — run logic at the edge (edge functions/workers) so personalization and assembly happen near the user instead of at a far origin.
- Dynamic acceleration — even uncacheable requests benefit, because the CDN routes them over optimized, pre-warmed backbone connections from the nearby edge to your origin, beating the public internet’s path.
The honest line: a CDN’s biggest win is always on cacheable content. Dynamic acceleration helps, but it can’t repeal the speed of light for a request that genuinely must reach your origin and back.
Cache invalidation at the edge
Section titled “Cache invalidation at the edge”The staleness problem from the caching page gets harder with a CDN, because your cached copies now live in hundreds of locations you don’t directly control. Three tools manage it:
- TTL / expiry headers — the default. Content is trusted for N seconds, then revalidated. Simple, but you can’t pull back a copy early.
- Purge / invalidation — explicitly tell the CDN to drop an object everywhere now. Effective but global purges can be slow to fully propagate, and aggressive purging defeats the point of the cache.
- Versioned URLs (cache busting) — the cleanest pattern: never invalidate; instead change the
name. Ship
app.a1b2c3.jsinstead ofapp.js, so a new version is a new URL with no cached copy anywhere, while old URLs can keep their long TTLs harmlessly.
When a CDN isn’t the answer
Section titled “When a CDN isn’t the answer”CDNs shine for read-heavy, cacheable, globally-distributed traffic. They do little for a write-heavy internal API with no reuse, and they add cost and a dependency you must operate. As always, weigh the buy (latency, offload) against the cost (staleness, invalidation, complexity) for your traffic shape — not in the abstract.
The architect’s lens
Section titled “The architect’s lens”A CDN is a big commitment — a third party in front of your whole site — so weigh it with the five questions before you sign up:
- Why does it exist? Because the speed of light is non-negotiable: a Sydney→Virginia round trip is over 200 ms before your server does a thing. A CDN exists to move content physically closer, so the answer comes from an edge a few milliseconds away instead of a continent away.
- What problem does it solve? Distance latency and origin load. Distant users get ~5 ms responses from a nearby PoP, and most requests never reach your origin at all — even a 1-second micro-cache on a hot public page can cut origin load ~50,000× during a spike.
- What are the trade-offs? The same freshness-for-speed bet as caching, only harder: your copies now live in hundreds of edges you don’t control, so invalidation is global and slow (purges propagate; the gold standard is long TTL + versioned URLs so you never invalidate). You also add a bill and a dependency whose bug can go global in a minute (Fastly, June 2021).
- When should I avoid it? When traffic is write-heavy with no reuse (an internal API), or genuinely per-user and uncacheable — dynamic acceleration shaves the path but can’t repeal the speed of light for a request that must reach your origin and come back.
- What breaks if I remove it? Distant users pay the full cross-ocean round trip on every request, and your origin absorbs all the traffic the edges were offloading — the flood the CDN reduced ~50,000× becomes a flood again, hitting servers sized to never see it.
Check your understanding
Section titled “Check your understanding”- Why is a CDN best described as a composition of caching and DNS/anycast rather than a new primitive?
- Contrast push and pull CDNs. Which one makes the first user in a region pay a penalty, and when is push worth the extra effort?
- Why is dynamic, personalized content the hard case for a CDN? How does micro-caching still help a public dynamic page under a spike?
- Explain why “long TTL + versioned URLs” lets you have maximal caching and instant updates at once.
- Describe a way a CDN can amplify a mistake, and one configuration choice that prevents leaking one user’s data to another.
Show answers
- A CDN is just caching plus DNS/anycast routing — it’s a cache hierarchy whose outermost layer has been scattered across hundreds of cities, with DNS/anycast steering each user to the nearest edge. Nothing new, just two known blocks composed.
- A pull CDN lazily fetches from origin on the first miss, so the first user in each region pays a miss penalty; a push CDN is pre-loaded so it’s already warm. Push is worth the extra effort when you know exactly what will be hot and want no cold-start misses — e.g. a game patch dropping to millions at once.
- Dynamic, personalized content differs per user and changes constantly, so there’s nothing to share across requests — the cache’s whole premise (reuse) is gone. But for a public dynamic page, even a 1-second TTL (micro-caching) absorbs a spike, because thousands of requests in that second collapse to a single origin fetch.
- Give the asset a year-long TTL and a content-hashed filename (
app.a1b2c3.js). Edges and browsers cache it effectively forever (maximal caching), and “deploying a change” just means referencing a new hashed URL with no cached copy anywhere (instant update) — so you never have to invalidate at all. - A bad cache header pins broken content globally for the whole TTL, pushing a mistake to every edge as fast as a fix. To prevent leaking one user’s data to another, never cache responses that vary by user without a correct
Vary/cache-key setup, so a personalized response can’t be served to someone else.