Free lesson · System Design Fundamentals
Content Delivery Network (CDN)
Lesson 11.1 - Content Delivery Network (CDN)
The first lesson of Module 11.
When a user in Tokyo requests an image from your server in Virginia, the round trip takes ~150ms just for the network - before the server even starts processing. For a web page with 50 assets (images, CSS, JS, fonts), that's 50 × 150ms of network latency if loaded sequentially, or multiple parallel connections each adding 150ms. The page feels sluggish. Users in Australia, India, and Brazil experience the same problem - your server is far away.
A CDN (Content Delivery Network) solves this by caching content at edge servers distributed globally - hundreds or thousands of locations worldwide. When the Tokyo user requests an image, the CDN serves it from a server in Tokyo (5ms), not Virginia (150ms). The content is cached at the edge, close to the user. For static content (images, videos, CSS, JS, fonts), this is transformative: 30-40x latency reduction for geographically distant users, massive bandwidth savings for your origin server, and built-in DDoS protection because the edge absorbs attack traffic.
How a CDN Works
flowchart TD
User["User in Tokyo"]
DNS["DNS resolves cdn.example.com\n→ nearest CDN edge IP\n(anycast - M4.4)"]
Edge["CDN Edge Server (Tokyo)\n\nCheck local cache:\nIs /images/logo.png cached?"]
Hit{"Cache hit?"}
Serve["✅ Serve from edge\nLatency: 5ms\nOrigin never contacted"]
Shield["Origin Shield\n(intermediate cache layer)\n\nCheck shield cache:\nIs it cached here?"]
ShieldHit{"Shield hit?"}
Origin["Origin Server (Virginia)\n\nGenerate/fetch the content\nReturn to shield + edge"]
ServeShield["Serve from shield\nCache at edge for next request"]
User --> DNS --> Edge --> Hit
Hit -->|"Yes"| Serve
Hit -->|"No (cache miss)"| Shield --> ShieldHit
ShieldHit -->|"Yes"| ServeShield
ShieldHit -->|"No"| Origin
Origin --> Shield --> Edge --> User
style Serve fill:#34d399,stroke:#10b981,color:#000
style Edge fill:#fbbf24,stroke:#f59e0b,color:#000
style Shield fill:#a78bfa,stroke:#8b5cf6,color:#000
A diagram showing the CDN request flow with three layers. The user in Tokyo makes a request for cdn.example.com/images/logo.png. DNS resolves cdn.example.com to the nearest CDN edge server using anycast routing (M4.4) - the user hits the Tokyo edge. The Tokyo edge server checks its local cache. If the image is cached (cache hit), it's served immediately - 5ms latency, the origin server is never contacted. This is the fast path and handles the vast majority of requests for popular content. If the image is NOT cached at the Tokyo edge (cache miss), the request goes to the origin shield - an intermediate cache layer typically in one or two locations. The shield serves as a shared cache for all edge servers in a region. If the shield has the image, it serves it to the Tokyo edge, which caches it locally for future requests. If the shield also doesn't have it (double miss - rare for popular content), the request goes to the origin server in Virginia. The origin generates or fetches the content, returns it to the shield (which caches it), and the shield returns it to the edge (which also caches it). The next request from any user in the Tokyo region hits the edge cache directly. The three-layer architecture - edge, shield, origin - means the origin server handles very little traffic. For popular content, the origin might be hit once per cache TTL across all edge locations. The CDN absorbs 90-99% of traffic that would otherwise hit the origin.
Cache Hierarchy: Edge, Shield, Origin
Interactive animation: Edge, shield, origin: traffic thins at every layer, so the origin is rarely contacted for popular content.
Edge servers (200-300+ locations globally): closest to the user. Cache hit rate for popular content: 80-95%. Serve the vast majority of requests. Each edge location independently caches content based on what users in that region request.
Origin shield (1-5 locations): intermediate cache between edge and origin. When an edge has a miss, it asks the shield before going to the origin. The shield aggregates cache misses from many edge locations - if 50 edge servers all miss on the same asset simultaneously (after TTL expiry), the shield absorbs 49 of those requests and sends only 1 to the origin. This is origin shielding - protecting the origin from thundering herds of cache misses.
Origin server (your infrastructure): the source of truth. Only contacted when both edge and shield miss - which for popular content is once per TTL cycle across the entire CDN. Origin traffic is a tiny fraction of total traffic.
Why origin shielding matters: without it, a TTL expiry on a popular asset triggers simultaneous cache misses at every edge location. If you have 200 edge locations and the asset expires, 200 requests hit the origin simultaneously. With origin shielding, 200 edge misses → 1 shield miss → 1 origin request. The shield collapses the fan-in from 200 to 1.
Interactive animation: The fan-in collapse: 200 edge misses on one expired asset become a single origin request.
What CDNs Cache
| Content type | CDN caching | TTL | Example |
|---|---|---|---|
| Static assets (images, CSS, JS, fonts) | Always | Hours to days | /static/logo.png, /bundle.js |
| Video/audio | Always (chunked) | Hours to weeks | Netflix streams, podcast episodes |
| API responses (read-heavy, public) | Often | Seconds to minutes | Product catalog, public user profiles |
| HTML pages (SSR/SSG) | Often | Seconds to minutes | Blog posts, landing pages |
| Personalized content | Rarely (not cacheable) | N/A | User dashboard, authenticated API responses |
| Dynamic API responses (write-heavy) | No | N/A | POST /orders, PUT /users/42 |
The rule: cache what's the same for many users (static assets, public data). Don't cache what's unique per user (personalized dashboards, authenticated responses) - unless you use a cache key that includes the user identifier (which fragments the cache and reduces hit rate).
CDN Configuration: Cache-Control Headers
The origin server tells the CDN what to cache and for how long using HTTP Cache-Control headers:
# Cache at CDN edge for 1 hour, browser for 5 minutes
Cache-Control: public, max-age=300, s-maxage=3600
# Don't cache at all (personalized content)
Cache-Control: private, no-store
# Cache forever (immutable assets with hash in filename)
Cache-Control: public, max-age=31536000, immutable
| Directive | Meaning |
|---|---|
public |
CDN (and any proxy) can cache this |
private |
Only the browser can cache - NOT the CDN |
max-age=N |
Browser cache TTL (seconds) |
s-maxage=N |
CDN/proxy cache TTL (overrides max-age for CDN) |
no-store |
Do not cache at all |
no-cache |
Cache it, but revalidate with origin before serving (confusing name!) |
immutable |
Content never changes - don't revalidate even after max-age |
stale-while-revalidate=N |
Serve stale content while fetching fresh version in background |
The stale-while-revalidate pattern: the CDN serves the cached (possibly stale) content immediately to the user (fast!) while simultaneously fetching a fresh version from the origin in the background. The next request gets the fresh version. This eliminates the latency penalty of cache misses for non-critical freshness - the user always gets a fast response, and the content is at most one request behind.
CDN Providers
| Provider | Edge locations | Strengths | Best for |
|---|---|---|---|
| Cloudflare | 300+ | Free tier, DDoS protection, edge compute (Workers), DNS | Most web applications. Best free tier. |
| AWS CloudFront | 450+ | Native AWS integration (S3, ALB, Lambda@Edge) | AWS-native architectures |
| Akamai | 4,200+ | Largest network, enterprise features, video delivery | Enterprise, media/video, highest performance |
| Fastly | 80+ | Instant purge (~150ms global), edge compute (Compute@Edge), Varnish-based | Dynamic content caching, real-time purge requirements |
| Google Cloud CDN | 100+ | Native GCP integration, Anycast | GCP-native architectures |
| Vercel/Netlify Edge | 100+ | Framework-specific (Next.js, static sites), developer experience | Frontend/Jamstack applications |
A Brandon Story: The Origin That Nearly Died
Interactive animation: Thirty minutes from launch to meltdown: 100 to 50,000 requests a minute, and no CDN in front.
A team I'd advised launched a viral marketing campaign. Their product page - a static HTML page with images and CSS - was shared on social media. Traffic went from 100 requests/minute to 50,000 requests/minute in 30 minutes. The origin server (a single EC2 instance behind an ALB) was handling every request - no CDN. CPU hit 100%. Response times climbed from 200ms to 8 seconds. The server started returning 503s. The viral moment became a viral outage.
Emergency fix (20 minutes): put Cloudflare in front. Point DNS to Cloudflare. Configure Cache-Control: public, s-maxage=300 for the product page and all static assets. Cloudflare's edge servers cached everything. Origin traffic dropped from 50,000 req/min to ~200 req/min (one request per edge location per 5-minute TTL). The origin server recovered immediately. Response times: 8 seconds → 15ms (served from Cloudflare's edge).
Cost of the CDN: $0 (Cloudflare's free tier handled the traffic). Cost of the outage: an estimated
2,000 in lost conversions during the 20 minutes of degraded performance, plus the reputational damage of a landing page that returned errors during a viral moment.Longer-term architecture: they configured origin shielding (Cloudflare's Argo Tiered Cache), set up stale-while-revalidate for the product page, and added a Cache-Control: public, max-age=31536000, immutable header for all static assets (with content-hash filenames for cache busting on updates).
Lessons:
- Every public-facing web application should have a CDN. It's not a performance optimization - it's infrastructure.
- Static content without a CDN means your origin handles every request from every user worldwide. A viral moment = origin death.
- Cloudflare's free tier handles enormous traffic. There's no cost excuse for not having a CDN.
- Origin shielding prevents thundering herds from cache TTL expiry.
stale-while-revalidateeliminates cache-miss latency for users - they always get a fast response.- Content-hash filenames (
bundle.a3f2c1.js) +immutableheader = cache forever, bust on deploy. The optimal static asset strategy.
Six Takeaways from This Lesson
-
CDNs cache content at edge servers globally - 5ms from a nearby edge vs 150ms from a distant origin. 30-40x latency reduction for geographically distributed users.
-
Three-layer cache hierarchy: edge (200+ locations, closest to user) → origin shield (1-5 locations, aggregates misses) → origin (your server, rarely hit for popular content).
-
Origin shielding collapses fan-in - 200 edge misses → 1 origin request. Protects the origin from thundering herds on TTL expiry.
-
Cache-Controlheaders govern CDN behavior.s-maxagefor CDN TTL,stale-while-revalidatefor latency-free cache refreshes,immutablefor content-hashed static assets. -
Cache what's shared across users (static assets, public API responses). Don't cache personalized content unless the cache key includes the user identifier.
-
Every public-facing application needs a CDN. The free tier of Cloudflare handles most workloads. No CDN = your origin handles every global request = vulnerability to any traffic spike.
What's Next
Lesson 11.2 covers distributed cache architecture - how to design a cache cluster with multiple nodes, consistent hashing for key distribution, and replication for high availability.