CDN Architecture
A CDN puts copies of responses in hundreds of edge locations so a user in Frankfurt gets bytes from Frankfurt instead of Virginia — beating the speed of light by not crossing the ocean — and the design questions are what the cache key is, how the origin is protected from misses, and how a change reaches every edge.
Latency is bounded by distance and origin capacity is bounded by one region. A CDN serves repeated responses from a point of presence near the user, so the round trip drops from ~90 ms to ~10 ms and the origin sees one request per edge instead of one per user.
The speed of light is the problem
Light in fibre travels about 200,000 km/s. Frankfurt to Virginia is roughly 6,500 km of cable, so one round trip is at least 65 ms and in practice ~90 ms. A fresh HTTPS connection costs a TCP handshake plus a TLS handshake — two to three round trips before the first byte — so a 50 KB image from an origin across the Atlantic takes 300 ms before any server work. No amount of server optimisation changes this; only distance does.
A CDN is a fleet of caches at points of presence (PoPs) near users — hundreds of them, in the cities where ISPs interconnect. DNS or anycast routes each user to the nearest PoP; the PoP terminates TLS a few milliseconds away, and if it holds the response, the origin is never contacted. A user in Europe talks to a European edge at ~10 ms; only the first user to ask for an object pays the trip to the origin.
Cache keys and the hit ratio
The edge stores responses under a cache key — by default the host plus path, often plus the query string. Everything that varies the response must be in the key, and nothing else. Include a query string that changes the content (?w=400 for an image size); exclude one that does not (?utm_source=…), or every marketing link is a separate miss. A Vary: Accept-Encoding header splits the key by encoding so gzip and brotli clients each get the right bytes; a Vary: Cookie effectively disables caching, because every user has a different cookie.
Hit ratio is the metric. 95% means the origin sees 1 in 20 requests; 99% means 1 in 100. A low ratio almost always means the key has too much in it (query strings, cookies, headers) or the TTL is too short for the traffic. Measure it per path: assets should be ~100%, HTML pages depend on how much is truly per-user.
GET /img/hero.jpg?w=800 key: host + /img/hero.jpg + w=800 ✓ size matters GET /img/hero.jpg?utm_source=mail key: host + /img/hero.jpg ✓ utm stripped GET /app.js (Cookie: session=…) key: host + /app.js ✓ cookie ignored GET /account (Cache-Control: private) ✗ never stored at the edge
Origin load and origin shielding
With 200 PoPs, a newly published object misses 200 times — once per edge — and a popular purge sends 200 simultaneous requests to the origin. Origin shielding adds one intermediate cache layer: edges miss to a designated shield PoP near the origin, and only the shield talks to the origin. The origin now sees one request per object instead of 200, and a deploy or purge is a gentle ramp rather than a spike. Request collapsing at the edge does the same within one PoP: if 500 users ask for the same missing object in the same 50 ms, the edge sends one origin request and fans the response out — the CDN’s version of the single-flight from Caching Architecture.
Invalidation: purge vs versioned URLs
Two ways for a change to reach the edges. Purge tells the CDN to drop a key (or a tag, or everything); propagation takes seconds to a minute, and until it lands some users get old bytes. Purge-everything on every deploy also resets the hit ratio to zero and hits the origin with the full miss storm. Versioned URLs avoid the problem: app.3f9a1c.js is a different key from app.7b2e40.js, so the new HTML references the new asset, the old one simply stops being requested, and both can be cached for a year with immutable. The rule: hash static assets and never purge them; purge only the small set of unversioned keys (HTML, JSON that must update), ideally by surrogate tag (product-42) so one product edit clears exactly the pages that embed it.
The last trap is the mixed deploy: new HTML served from a fresh edge references app.7b2e40.js, but the origin behind another edge still runs the old build and answers 404. Upload assets before switching HTML, and keep old asset versions available for at least the HTML cache TTL.
Dynamic content at the edge, and what not to put on a CDN
CDNs increasingly run code at the edge: A/B assignment, geo-redirects, auth token validation, personalising a cached page by injecting a small per-user fragment. The pattern is cache the expensive shared part, compute the cheap personal part at the edge. Edge functions have limits — milliseconds of CPU, no persistent connections to your database — so they suit routing and assembly, not business logic.
Some things should not go through a CDN. Anything per-user and sensitive without careful private headers, because one misconfigured key leaks accounts. Write endpoints, which gain nothing and add a hop. Tiny, frequently changing JSON where the TTL would be shorter than the purge propagation. Very large working sets with low reuse — a video library where each file is watched once — because the edge evicts before the second request and every hit is a miss with extra latency. A Bloom Filter of "seen at least once" is how some CDNs decide whether an object is worth storing at the edge at all.
| Content | CDN? | TTL | Why |
|---|---|---|---|
| Hashed JS/CSS/fonts/images | Yes | 1 year, immutable | Never changes under its URL |
| Anonymous HTML / product JSON | Yes | 60 s + stale-while-revalidate | Shared by everyone, tolerates a minute |
| Video segments | Yes | Long | Large, popular, bandwidth-heavy |
| Account page | Edge TLS only | private, no-store | Per-user and sensitive |
| POST /orders | Pass-through | n/a | Write; nothing to cache |
| Once-watched long tail | Rarely | n/a | Evicted before reuse; miss + hop |
Key points
- Distance is latency: ~90 ms across the Atlantic, ~10 ms to a local edge, and every fresh TLS connection pays it two or three times.
- The cache key must contain exactly what varies the response; extra query strings and cookies destroy the hit ratio.
- Origin shielding and request collapsing turn 200 edge misses into one origin fetch.
- Hash static assets and never purge them; purge only unversioned keys, by tag, and upload assets before switching HTML.
- Cache the shared expensive part, assemble the cheap personal part at the edge; keep sensitive per-user data and writes off the cache.
Edge caching by region
How data moves through it
One request or event, hop by hop.
- 1User → DNS/anycast: resolves to the nearest PoP.
- 2User → edge: TLS terminates at the edge; the request is mapped to a cache key.
- 3Edge → shield: on miss, the edge asks the shield PoP; collapsed with concurrent identical requests.
- 4Shield → origin: one fetch; the response’s
Cache-Controldecides how long each tier keeps it. - 5Origin → object storage: the origin reads the asset or renders the page.
- 6Edge → user: response served; subsequent users in the region hit at the edge until the TTL or a purge.
When to use — and when not
- Users spread across regions while the origin lives in one, and page load or media latency is measured as a problem.
- Static assets and media — the majority of bytes on almost every site — regardless of scale; the operational cost is near zero.
- Read-heavy public content with predictable spikes (launches, news) where the origin would otherwise need 10× capacity for an hour a week.
- An internal tool or a single-region user base where the edge is no closer than the origin.
- Per-user, sensitive or write-heavy traffic; the CDN adds a hop and a way to leak data.
- A long-tail working set with no reuse, where hit ratio would sit below ~30% and every request pays edge-plus-origin latency.
Tradeoffs
The cheapest large win in most systems; the costs are staleness measured in TTLs and purge propagation, and a cache-key mistake that either kills the hit ratio or leaks a private response.
How it fails
- Per-user response cached at the edge because
Cache-Controlwaspublicor the cookie was not in the key: user A sees user B’s account. - Query-string tracking parameters in the cache key: hit ratio collapses to 20%, origin load quadruples after a marketing campaign.
- Purge-everything on deploy: hit ratio to zero, 200 PoPs miss at once, origin p99 spikes for minutes.
- New HTML references an asset hash the origin has not published yet: 404s on every page until the deploy completes.
- Edge caches a 500 from the origin for 60 s, extending a 2 s blip into a minute of errors for everyone in that region — cap error TTLs at a few seconds.
How it scales
- Bandwidth scales with the CDN’s footprint, not yours; a launch that would need 40 Gbit/s at the origin is a rounding error at the edge.
- Origin load is bounded by miss rate × object count, largely independent of user count once hit ratio is high.
- Shielding adds one tier; multi-CDN adds a second provider for resilience at the cost of two cache-key configurations to keep identical.
How it interacts with databases, queues, caches, APIs and external systems
- Object storage: the usual origin for assets and uploads; the CDN reads it directly with signed origin access.
- Application origin: serves HTML and JSON with explicit cache headers; every response must declare its cacheability.
- Cache (Redis): the application-side layer behind the origin for the misses that reach it.
- Queue: publish events can trigger tag purges asynchronously after content changes commit.
- External: the CDN provider’s purge API and edge-function runtime are dependencies with their own limits and latencies.