Infrastructure
Design CDN
01
Requirements
Requirements
- Customers point a hostname at the CDN and we serve their static assets, images, downloads and video segments from cache.
- Customers set caching rules per path: TTLs, which query parameters and headers form the cache key, and whether cookies bypass the cache.
- Customers purge content by URL, by prefix or by tag, and see it gone everywhere within seconds.
- TLS for every customer hostname, with automatic certificate issuance and renewal.
- Built-in protection: rate limits, a web application firewall and absorption of volumetric attacks.
- Near real-time logs and dashboards: requests, bytes, hit ratio, errors and latency per hostname.
- Most users reach a site within 30 ms round trip in well-connected regions.
- Cache hits answered in under 10 ms of server time; first byte at p95 under 100 ms for hits worldwide.
- Edge request hit ratio of 95% or better for typical static workloads.
- A full site going dark costs users a reconnect, not an outage; the network as a whole targets 99.99% or better.
- A purge reaches every site in under 2 seconds at p99.
- Origins see only a small fraction of traffic, even right after a purge.
Static assets, video and API acceleration behave very differently. Ask what share of bytes is video, how big the largest objects are, and whether dynamic content is in scope. Video pushes you to chunked caching and large disks; APIs push you to connection reuse and edge compute instead of caching.
02
Sizing the network
Sizing the network
Assume a CDN with 60,000 customer zones, 180 points of presence (PoPs) and a mixed workload: many small web assets plus video segments that dominate the bytes. Average response size across all requests is about 60 KB.
| Quantity | Working | Result |
|---|---|---|
| Peak requests | evening peak across the busiest regions | about 30M / s |
| Peak egress | 30M / s x 60 KB | about 1.8 TB / s, roughly 14 Tbps |
| Busiest PoP share | about 4% of global traffic | 1.2M req / s, about 580 Gbps |
| Edge servers in that PoP | 580 Gbps / 25 Gbps usable each, plus N+2 | about 26 servers |
| Cache per server | 512 GB RAM for hot objects, 60 TB NVMe | about 1.5 PB disk per large PoP |
| Requests missing the edge | 5% of 30M / s | about 1.5M / s to the parent tier |
| Requests reaching origins | parent tier absorbs about 70% of edge misses | about 450K / s across all customers |
| Log lines | 30M / s x about 400 B | about 12 GB / s before sampling and compression |
| Purge requests | 60K zones, bursty deploys | about 5K / s at peak, each fanned to 180 PoPs |
| TLS certificates | one or more per customer hostname | about 2M managed certificates |
Egress dominates cost, and every point of hit ratio is worth real money: going from 95% to 96% removes a fifth of the remaining origin and backbone traffic. That is why most of the design is about getting more hits out of the same disks.
A popular PoP sees millions of distinct objects a day, and most are requested only once or twice. The cache must stop those one-hit objects from evicting the ones that will be requested again.
03
API design
API design
Customers interact with two surfaces: a control API for configuration and purges, and HTTP headers on their origin responses that tell the edge how to cache.
04
Architecture
Architecture
flowchart LR
U["Users"] --> AC["Anycast IP, BGP picks the nearest PoP"]
AC --> LB["L4 balancer in the PoP"]
LB --> ES["Edge server: TLS, WAF, routing"]
ES --> CH["Owner of the cache key in this PoP"]
CH -->|miss| PT["Parent tier in a regional hub"]
PT -->|miss| OR["Customer origin"]
CP["Control plane"] --> CFG[("Config and cert store")]
CFG -. "push" .-> ES
CP --> PB["Purge bus"]
PB -. "fan out" .-> CH
PB -. "fan out" .-> PT
ES -. "logs" .-> LP["Log pipeline and analytics"]Every customer hostname resolves to a small set of anycast addresses that all 180 PoPs announce through BGP. The internet's routing picks the path to the "closest" PoP in network terms, which is usually close in distance too. Inside the PoP, an L4 load balancer spreads connections across edge servers with consistent hashing on the connection tuple, so packets of one TCP or QUIC flow keep landing on the same server.
The edge server terminates TLS, applies the firewall and rate limits, matches the request against the zone's rules and builds the cache key. It then hashes that key to find which server in the PoP owns the object. Sharding cache by key inside the PoP means 26 servers act as one large cache instead of 26 copies of the same hot set. If the owner has the object, it streams it back through the edge server that took the request.
On a miss, the owner asks a parent tier: a handful of large regional hubs, each acting as shield for many edge PoPs. Only a parent miss reaches the customer's origin. The control plane is separate: it stores configurations and certificates, pushes versioned changes to every server, fans out purges, and collects logs. The data plane keeps serving with its last known config if the control plane is unreachable.
One IP everywhere. A site that fails withdraws its routes and traffic shifts to the next nearest PoP in seconds, without waiting for DNS TTLs.
Consistent hashing of the cache key across servers in the PoP. A very hot object is copied to every server's memory so one machine is not the bottleneck.
Regional shields turn many edge misses into one origin fetch. They have bigger disks and keep long-tail objects that individual PoPs evict.
Config, certificates and purges flow out; logs and metrics flow in. It is built so that its own outage never stops content delivery.
05
Deep dive: routing, the cache hierarchy and purge
Deep dive: routing, the cache hierarchy and purge
Three things separate a real CDN from a reverse proxy in many cities: how users find the right site, how sites share work so hit ratio stays high, and how a change at the origin becomes visible everywhere fast.
flowchart TD
R["Request arrives at edge"] --> K["Build cache key from host, path, chosen query and headers"]
K --> O["Hash key to owner server in PoP"]
O --> M{"Fresh copy on owner?"}
M -->|yes| H["Serve hit"]
M -->|stale| SWR["Serve stale, refresh in background"]
M -->|no| C{"Fetch already in flight?"}
C -->|yes| W["Wait on the same fetch"]
C -->|no| P["Ask parent tier"]
P --> PO{"Parent has it?"}
PO -->|yes| F["Store and serve"]
PO -->|no| OR["Fetch from origin, store at both tiers"]Getting users to the right site
There are two ways to steer users. DNS steering answers each lookup with the IP of a PoP chosen for that resolver. It allows fine control over load, but it sees the resolver, not the user, and resolvers can be far from their users. A change also takes as long as the DNS TTL plus whatever caching resolvers do on top.
Anycast announces the same prefix from every PoP and lets BGP choose. Failover is fast: when a PoP stops announcing, routers converge on the next path in seconds. It also spreads attack traffic naturally, because a botnet spread around the world hits many sites at once. The cost is less control. BGP does not know about load, and a routing change can move a live TCP connection to a different PoP, which then has no state for it. In practice, routes are stable enough that this is rare, and QUIC connection ids help.
Most large CDNs mix both. They use anycast for the front door, then tune it with BGP communities and prefix splits when one site is overloaded, and sometimes use DNS to steer very large customers or video traffic with precision.
Making the cache hit
Cache key design comes first. By default the key is scheme, host, path and the full query string, plus whatever the response lists in Vary. Most real gains come from removing noise: drop tracking parameters such as utm_source, sort the remaining ones, and normalise Accept-Encoding into a few buckets. A single unnormalised parameter can split one object into thousands of copies.
Inside a server, two layers work together: RAM for small, very hot objects and NVMe for everything else. Pure LRU lets a crawl of one-hit objects push out the useful set, so the admission policy matters as much as eviction. A common approach is to admit an object to disk only on its second request within a window, tracked in a small counting filter, and to evict with an LRU variant that protects frequently reused objects.
Large files need a different shape. A 4 GB game patch or an hour of video should not be one cache entry. Store objects in fixed slices of about 2 MB, keyed by object and slice number, and serve range requests by assembling slices. A user who watches the first two minutes then leaves only pulls the slices they watched, and different slices of one file can live on different servers.
Protecting the origin
Three mechanisms keep origins safe. The parent tier turns misses from many PoPs into a single fetch per region. Request coalescing makes sure that if 3,000 requests for the same uncached object arrive at once, only one goes upstream and the rest wait on it. And stale serving lets the edge return an expired copy while it revalidates in the background (stale-while-revalidate), or for hours if the origin is failing (stale-if-error).
Revalidation is cheap when origins send ETag or Last-Modified: the parent asks "has this changed?" and gets a 304 with no body if not. That keeps expired-but-unchanged content flowing at the cost of one small request.
Purge in two seconds
A purge request lands in the control plane, which assigns it a global sequence number and writes it to a durable log. A fan-out service publishes it to every PoP over persistent connections, and inside the PoP a local relay delivers it to every server. Each server acknowledges, and the control plane reports progress per PoP. Two seconds is achievable because purges are tiny messages pushed over warm connections, not pulls.
Purging by URL is easy; purging by tag is what customers actually need. When an origin response carries Surrogate-Key: product-8812, the server records that key in a local index from tag to cache entries. Purging the tag then removes every page, image and API response that mentioned the product. A soft purge only marks entries stale, so the next request revalidates instead of every PoP hitting the origin at once.
Servers that were offline during a purge must catch up before they serve. Each server remembers the last purge sequence it applied, and on reconnect it replays the missing range from the log. If the gap is too old to replay, it drops the affected zones' cache entirely.
If a customer deletes a leaked document and purges it, one forgotten server still serving it is an incident. That is why purges are acknowledged per server and why a lagging server is pulled out of rotation until it catches up.
06
Tradeoffs and design choices
Tradeoffs and design choices
Fast failover, attack traffic spread across sites, and no dependency on resolver location. You accept coarser load control and the rare connection that breaks when routes shift.
Default for edge trafficPrecise per-request placement and easy weighting. Slow to react to failures, and accuracy depends on where resolvers sit relative to users.
Useful for big video customersOrigins see a fraction of the misses, and long-tail content survives in big parent caches. Misses cost one extra hop of latency.
Higher combined hit ratioFewer moving parts and lower miss latency. Origin load scales with the number of PoPs, and every purge triggers 180 refetches.
Does not scale with PoPsContent enters the cache only when someone asks for it, so disks hold what is actually popular. The first viewer in each region pays a miss.
Fits most workloadsPre-warm a game patch or film launch everywhere so the first second is a hit. Great for planned spikes, wasteful for everything else.
Use for known launchesDisks are pooled, so busy customers use space quiet ones are not using. Keys include the zone so data never leaks between customers. A noisy customer can evict others, which per-zone quotas limit.
Efficient use of hardwarePredictable performance for large customers, at much lower utilisation. Usually sold as a premium option rather than the default.
Premium tier only07
Failure modes
Failure modes
Users in that city lose their nearest site, and any request in flight fails.
Health checks withdraw the PoP's anycast routes, and traffic moves to neighbours within seconds. Neighbours have spare capacity planned for it, and their caches warm quickly because the parent tier still holds the content.A botnet sends 8M requests a second at a single hostname, more than any one PoP could absorb.
Anycast spreads the flood across all sites. Drop malformed packets in the kernel or NIC, rate limit per source and fingerprint at the edge, and challenge suspicious clients before they reach caches or origins.An attacker sends a request with a crafted header that the origin reflects into the page. The response is cached under the normal key and served to everyone.
Strip or normalise headers that are not part of the key before forwarding, refuse to cache responses that vary on unexpected inputs, and let customers declare which headers the origin may use.A network partition isolates several servers during a purge, and they keep serving the old version after the customer is told it is done.
Report completion only after every server acknowledges. Servers that lag behind the purge log stop serving the affected zones until they catch up or flush.As TTLs expire, more requests fall through to a dead origin, and the site slowly turns into error pages.
Serve stale copies withstale-if-error, keep negative results short, and use coalescing so a recovering origin is not hit by every waiting request at once.
An automation bug stops renewals. Thousands of hostnames will fail TLS on the same day.
Renew 30 days before expiry, alert on any certificate inside 14 days, and keep a second issuing authority ready.08
Interview tips
Interview tips
Open with how users find a PoP. Compare anycast and DNS steering in a couple of sentences, then pick one with a reason. It sets up failover and attack absorption.
Draw the tiers. Edge, sharded PoP cache, parent tier, origin. Explain what each one saves, using your hit-ratio numbers.
Talk about cache keys. Normalising query strings and headers is where real hit-ratio gains come from, and most candidates skip it.
Cover stampedes. Request coalescing and stale serving together answer "what happens when a hot object expires?"
Treat purge as a distributed system. Sequence numbers, per-server acks, tags and soft purge. Saying "we invalidate the cache" is not enough.
Separate control plane and data plane. Say that serving continues on the last good config if the control plane dies.
09
Evolution
Evolution
Reverse proxies in three or four regions, chosen by resolver location. A real improvement over one origin for global users.
One IP announced everywhere, consistent hashing across servers in each site, and stale serving to protect origins.
Edge misses go to shields first, purges become tag-based with acknowledgements, and logs stream to customers.
The same edge servers inspect traffic, rate limit and challenge, turning the CDN into the front door of the application.
Customers run code at every PoP and keep small data close to users, so some requests never leave the edge at all.
Watch and read
Try next
Free to read · better with landed
Whiteboard this with landed
landed runs it as a live system design round on the whiteboard and grades your trade-offs.

