Notes
The problem
The problem. A user in Pune talking to an origin in Virginia pays about two hundred milliseconds per round trip, and connecting plus TLS plus the request is three or more round trips before the first byte. The origin also serves every byte to everyone: a million users loading two megabytes each is two terabytes of egress. And any attack lands straight on your servers.
- India → US origin: ~200 ms
- per round trip
- TCP + TLS + request = 3+ RTTs
- before the first byte
- origin serves every byte:
- 1M users × 2 MB = 2 TB
- per page view wave
- one DDoS hits you directly
Routing to the edge
How does the user reach a nearby edge? Two ways. DNS-based routing: your domain points at the CDN's nameservers, which answer with a nearby, healthy point of presence based on the resolver's location and current load. Or anycast: every point of presence announces the same IP address, and internet routing naturally delivers packets to the closest one. Many CDNs combine the two.
- DNS-based: CDN nameserver
- answers with a nearby PoP
- (by resolver location, load,
- health); short TTLs ~30-60 s
- anycast: every PoP announces
- the same IP; BGP routing
- delivers you to the nearest
- many CDNs mix both
Inside a PoP
Inside a point of presence, a layer four balancer spreads users across cache servers. TLS terminates right there, so the handshake round trips are twenty milliseconds instead of two hundred. Hot objects sit in memory and the long tail on SSD. Servers within the PoP hash the URL so each object is stored once per PoP, not once per server, which multiplies the effective cache size.
- L4 balancer spreads users
- over cache servers
- TLS terminated at the edge
- (handshake RTTs are short)
- cache: hot objects in RAM,
- long tail on SSD
- servers in a PoP hash the
- URL so each object lives once
Cache keys
The cache key decides your hit ratio. By default it's the host, path and query string, so a tracking parameter like utm source creates a new copy for every campaign. Strip or sort query params, vary only on what really changes the response, like encoding, and never vary on cookies. Control lifetime with Cache-Control: a year with immutable for fingerprinted files, or s-maxage to cache at the CDN but not in the browser.
key = host + path + query? Cache-Control: public, max-age=31536000, immutable Cache-Control: s-maxage=60 (CDN only), max-age=0 (browser) Vary: Accept-Encoding kill hit ratio: random query params, Vary: Cookie, Set-Cookie
Origin shield
Here's the part people forget. With three hundred points of presence, a new object causes three hundred misses, each hitting the origin. Tiered caching puts an origin shield in between: every edge misses into one regional tier, and only the shield talks to the origin. Add request collapsing, where a thousand simultaneous misses for the same URL become one upstream fetch, and the origin sees roughly one request per object.
- 300 PoPs × a miss each =
- 300 origin fetches per object
- shield: one regional tier
- all edges miss into
- → origin sees ~1 fetch
- request collapsing: 1,000
- concurrent misses for the
- same URL → 1 upstream fetch
Pull vs push
Two ways to fill the edge. Pull is the default: the edge fetches from the origin on the first miss and keeps the object for its TTL. There's nothing to manage, but the first user at each location waits. Push means uploading content to the CDN ahead of time. It makes sense for things you know will be hammered at a set moment, like a game patch or a launch video, at the cost of managing that storage yourself.
- pull (most common): edge
- fetches from origin on first
- miss, keeps it for its TTL
- + zero ops, only hot content
- - first user per PoP waits
- push: you upload to the CDN
- ahead of time
- + no cold misses (launches,
- big game patches, video)
- - you manage storage + expiry
Updating files
How do you update a file cached in three hundred places? The best answer is to never update it: put a content hash in the filename, cache it for a year, and a deploy simply references the new name. For things with fixed URLs, like HTML, use short TTLs with revalidation, where the edge sends an ETag and gets a tiny 304 if nothing changed. Purges by URL or tag exist, but they take seconds to minutes to reach every edge.
- versioned URLs (best):
- /app.3f9c1a.js, cache 1 year
- deploy = new name, instant
- purge by URL or tag:
- seconds to minutes globally
- old copy may live a bit
- short TTL + revalidate:
- ETag / If-None-Match → 304
- for HTML that must stay fresh
Dynamic content
CDNs help with uncacheable traffic too. The user's TLS handshake ends at the nearby edge, and the edge forwards over warm, pooled connections across the CDN's own backbone, saving several long round trips. Popular API responses can be micro-cached for a second or two, which absorbs spikes. Edge functions can handle redirects, A/B splits, auth checks and image resizing. Video streaming works because each few-second segment is just a cacheable file.
- dynamic acceleration: TLS at
- edge + pooled warm connections
- over the CDN backbone to origin
- micro-caching: cache API
- responses 1-5 s at the edge
- edge compute: auth checks,
- redirects, A/B, image resize
- video: cache HLS segments
Failure handling
Failure handling. If the origin returns errors, a stale-if-error directive lets edges keep serving the last good copy for hours. If a point of presence fails, DNS stops handing it out or anycast withdraws its route, and users land on the next nearest one. Entire CDNs have had global outages, so large sites use two CDNs with DNS steering. And keep cache keys strict, so an attacker can't get a poisoned response cached for everyone.
- origin down: stale-if-error=
- 86400 keeps serving old copy
- PoP down: DNS stops handing
- it out; anycast withdraws the
- route → next nearest PoP
- whole CDN down (it happens):
- multi-CDN via DNS steering
- cache poisoning: strict keys
Security
The edge is also your shield. Floods get absorbed across hundreds of locations before they reach you, and firewall rules run there. For private files, like paid videos, the app issues signed URLs with an expiry, and the edge checks the signature itself without calling your servers. Finally, lock the origin down so it only accepts traffic from the CDN, or attackers just go around it.
- DDoS: hundreds of PoPs absorb
- floods far from your origin
- WAF rules at the edge
- signed URLs / cookies:
- HMAC(path + expiry) checked
- at the edge, no origin call
- lock origin: accept traffic
- only from the CDN
Sizing
Sizing a CDN is about bandwidth. Five million daily users at twenty megabytes each is a hundred terabytes a day. Divided by eighty-six thousand four hundred seconds and tripled for peak, that's about three and a half gigabytes a second, around twenty-eight gigabits. With a ninety-five percent hit ratio the origin only serves five terabytes a day; at ninety-nine percent, one. Byte hit ratio is the number to watch.
5M daily users × 20 MB/day = 100 TB/day egress peak ≈ 3x average: 100 TB / 86,400 s × 3 ≈ 3.5 GB/s ≈ 28 Gbps hit ratio 95% → origin ~5 TB/day, ~1.4 Gbps peak hit ratio 99% → 1 TB/day cost: CDN egress < cloud egress
Say it like this
In the interview, draw users, the CDN edge, an origin shield and the origin or object store. Then give a policy per content type: fingerprinted static files cached for a year, HTML short-lived with ETags, private media behind signed URLs. Name stale-if-error for origin outages, multi-CDN if downtime is very costly, and give the egress estimate and the hit ratio you're aiming for.
- draw: users → CDN edge →
- shield → origin / object store
- cache: static + media, keys
- normalised, immutable
- versioned filenames
- HTML: short TTL + ETag
- private: signed URLs
- failure: stale-if-error,
- multi-CDN if critical
- number: egress and hit ratio