Edgecdnedgeanycastcache-controlorigin shield

CDNs: The Networking View

A CDN moves the TCP/TLS endpoint and a cache to within a few milliseconds of the client, because the one cost no engineering removes is the speed of light in fibre; what it cannot cache still pays the trip to the origin.

Conceptual
▶ InteractiveInterview question
Progress

The problem

Your origin is in Frankfurt. A user in Sydney opens the page: 16,000 km each way, and the request needs at least three round trips before the first byte arrives. No amount of server tuning changes the distance. Where can you put something closer, what can it answer on its own, and what must still make the trip?

The cost you cannot engineer away

Light in fibre travels at roughly 200,000 km/s — about two thirds of its speed in vacuum, because of the glass’s refractive index. Frankfurt to Sydney is ~16,500 km on the map and more along real cable routes, so one way is ≥ 80 ms and a round trip ≥ 165 ms before a single router has queued anything. New York–London is ~5,600 km: a physical floor of ~56 ms RTT, ~70–75 ms in practice. These numbers are lower bounds set by physics; faster CPUs, more bandwidth and better code do not move them.

A cold HTTPS request from a browser costs: DNS (often cached), one RTT for the TCP handshake (The Three-Way Handshake), one RTT for TLS 1.3 (The TLS Handshake), one RTT for the request and first byte, then further RTTs as TCP slow start ramps up. At 165 ms that is half a second before any content, and a 1 MB page takes several more round trips as cwnd doubles (Congestion Control: Protecting the Network). Halving the distance halves every one of those.

The only way to reduce RTT is to reduce distance. A CDN is a fleet of points of presence (PoPs, edge locations) — hundreds of small data centres inside ISPs and internet exchanges — so that the machine the client connects to is 5–20 ms away instead of 165. Software Architecture teaches when to use one; here we follow the packets.

Getting the client to the nearest edge

The client resolves www.example.com. The record is a CNAME to the CDN (example.cdn-provider.net) whose authoritative servers answer differently depending on where the query comes from. Two mechanisms exist. DNS steering returns the IP of a PoP near the resolver that asked — which is near the user if they use their ISP’s resolver, and can be badly wrong if they use a distant public resolver; the EDNS Client Subnet extension lets a resolver pass part of the client’s address to fix that (Following One Lookup Through Every Cache).

Anycast announces the same IP prefix from every PoP via BGP. Each router on the internet forwards to whichever announcement is nearest in its own routing table, so the client’s packets land at the topologically closest PoP without DNS having to guess (Internet Routing: Autonomous Systems and BGP). The subtlety: "nearest" in BGP terms is fewest AS hops, not fewest milliseconds, and a route change mid-connection can move a TCP connection to a different PoP that has no state for it — which is one reason CDN TCP sessions are kept short or QUIC connection IDs are used.

A request through a CDN
  1. Clientresolves the name; DNS steering or anycast picks the edge
  2. Edge PoP (5–20 ms away)terminates TCP + TLS with the site’s certificate; the handshakes are now short trips
  3. Edge cachecache key lookup: hit → respond immediately; miss → fetch
  4. Origin shield (regional)optional middle tier: many edges → one shield → one origin fetch
  5. Origin (the long trip)over pooled, already-open edge→origin connections; still pays the RTT

TLS at the edge, and what the cache actually keys on

The edge holds the site’s certificate and private key (or a CDN-managed one), so the client’s TLS handshake completes against the edge at short-hop RTT. From the edge to the origin a separate TLS session runs over a persistent, pre-warmed connection pool — the origin handshake was paid once, long ago, not per user. The origin sees the CDN’s IPs and must be told the client’s via X-Forwarded-For or a CDN-specific header, with the same trust rules as any reverse proxy (Forward and Reverse Proxies).

A cache entry is looked up by a cache key: by default host + path + query string, extended by whatever the response’s Vary header names (Accept-Encoding, sometimes Accept-Language). A query string that carries a per-user token, or a Vary: Cookie, makes every request unique and the cache useless. Cache-Control from the origin decides the rest: public, max-age=60 (client and CDN may cache 60 s), s-maxage=3600 (CDN may cache an hour even if browsers may not), private (only the browser), no-store (nobody), stale-while-revalidate=30 (serve the old copy while fetching a new one), immutable (never revalidate — pair it with content-hashed filenames so a change is a new URL).

A cache miss at a busy edge is the dangerous moment: a thousand clients requesting a just-expired object simultaneously would produce a thousand origin fetches. Edges coalesce identical in-flight requests into one, and an origin shield — a designated mid-tier PoP that all edges fetch through — turns N edges’ misses into one origin request. Without shielding, a global site has one origin fetch per PoP per TTL expiry.

Headers on a cacheable response, as returned by the origin and as seen after the edge
HTTP/1.1 200 OK
Cache-Control: public, s-maxage=86400, max-age=300, stale-while-revalidate=60
ETag: "5f3a9c"
Vary: Accept-Encoding
Content-Encoding: br

# edge adds:
Age: 1832                      # seconds since the edge fetched it
X-Cache: HIT                   # vendor-specific; HIT | MISS | REVALIDATED | STALE

TTL vs purge, and what the CDN cannot fix

A long TTL means more hits and a smaller origin, and a stale object served for up to that long after a change. A short TTL means fresh content and an origin that sees a request per PoP per TTL. Purge (invalidate by URL, tag or everything) breaks the trade: cache long, purge on deploy. Purges propagate to hundreds of PoPs in seconds, not instantly, and a purge storm during a rollback can briefly send every edge to the origin at once — exactly the stampede shielding exists to prevent. Compare cache invalidation in the Database domain: the same problem one layer down.

A CDN cannot shorten a trip it has to make. An uncacheable response — Cache-Control: private on a personalised page, a POST, an API call with a per-user token — still travels edge → origin → edge. What the user gains is smaller than it looks and larger than nothing: the TCP and TLS handshakes are against the near edge (three short RTTs instead of three long ones), the edge→origin connection is already open and its congestion window already large, and the response comes back over a warm path. The one long RTT for the request itself remains. A page that needs five sequential uncacheable API calls from Sydney to Frankfurt pays five long RTTs no matter who is in front of the origin; the fix is fewer round trips or an origin nearer the user (Latency: Same Machine to Cross-Continent).

  • Cacheable static asset: every RTT is short; origin sees one request per PoP per TTL.
  • Uncacheable dynamic request: handshakes are short, the request itself pays one long RTT over a warm connection.
  • Sequential uncacheable requests: one long RTT each; nothing at the edge can collapse them.

Key points

  • Light in fibre is ~200,000 km/s: a cross-ocean RTT of 70–200 ms is a floor set by distance, not a performance bug.
  • A cold HTTPS request needs ≥ 3 RTTs before content; a CDN makes those RTTs short by terminating TCP/TLS at a PoP near the client.
  • Clients reach the edge by geo-aware DNS (near the resolver) or anycast (same IP announced from every PoP; BGP picks the nearest).
  • The cache key is host + path + query + Vary; Cache-Control s-maxage, stale-while-revalidate and immutable control edge behaviour.
  • Request coalescing and an origin shield stop a TTL expiry from becoming a stampede on the origin.
  • Uncacheable requests still pay the edge→origin RTT; the CDN only saves the handshakes and gives a warm connection.

Why does this exist?

Mechanisms are answers to constraints. Open each question before reading the answer.

Why not just make the origin faster?

Because the origin is not where the time goes. A 5 ms server response behind a 165 ms RTT and three handshake round trips is a 500 ms page. Distance dominates, and only moving the endpoint closer changes distance.

Why terminate TLS at the edge rather than pass it through?

A passed-through TLS handshake still crosses the ocean twice. Terminating at the edge makes the handshake a short trip and lets the edge read the request so it can serve it from cache at all.

Why does anycast work for a stateful protocol like TCP?

Mostly because routes are stable for the seconds a connection lasts. When they are not, the connection breaks; CDNs mitigate with short-lived connections, shared state within a PoP, and QUIC connection IDs that survive a path change.

Why an origin shield?

Hundreds of PoPs each missing independently multiply origin load by hundreds. A shield collapses those misses to one, and keeps the origin’s connection pool warm from a single place.

CDN edge

CDN edge: hit, miss, origin
A user in Warsaw asks for an object whose origin lives in Virginia.
Edge cache: emptySimulated
North AmericaEuropeAtlantic · ~110 ms RTTWarsaw PoPFrankfurt PoPVirginia PoPOrigin (Virginia)user
DNS + anycast pick the nearest PoP. cdn.example.com resolves to an anycast address announced from every PoP; BGP delivers the user's packets to the topologically closest one — Warsaw, ~10 ms away. Frankfurt would be next; Virginia is an ocean away.
Elapsed (simulated)
0 µs
Edge RTT
~10 ms
Origin RTT + work
~110 + 25 ms
Average latency at this ratio24 ms

avg = edge RTT + (1 − ratio) × (origin RTT + origin time). A 90% ratio still means one request in ten pays the ocean; a 99% ratio is what makes a static site feel local everywhere.

1/6 · DNS + anycast pick the nearest PoP

How it fails

What the failure looks like from inside real software.

  • Hit ratio near zero: a cache-busting query parameter or Vary: Cookie makes every request a unique key; the origin sees full traffic behind a CDN it pays for.
  • Users see stale content for an hour after deploy: s-maxage=3600 without purge-on-deploy, or the purge was issued for a URL that differs by a trailing slash from the cached key.
  • Origin CPU spikes every hour on the hour: synchronized TTL expiry across PoPs with no shield and no coalescing; the fix is jittered TTLs or stale-while-revalidate.
  • Users behind a public DNS resolver in another country are routed to a distant PoP: DNS steering without EDNS Client Subnet; anycast would not have this problem.
  • Personalised page cached and served to the wrong user: origin omitted Cache-Control: private on a response that varied by session cookie.
  • API in front of a CDN is "still slow from Asia": every call is uncacheable and sequential; the CDN shortened the handshakes but not the five long round trips.