AlgoInsight

Free lesson · System Design Fundamentals

Content Delivery Network (CDN)

Medium7 min readsystem-designhlddistributed-systems

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:

Six Takeaways from This Lesson

  1. 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.

  2. 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).

  3. Origin shielding collapses fan-in - 200 edge misses → 1 origin request. Protects the origin from thundering herds on TTL expiry.

  4. Cache-Control headers govern CDN behavior. s-maxage for CDN TTL, stale-while-revalidate for latency-free cache refreshes, immutable for content-hashed static assets.

  5. Cache what's shared across users (static assets, public API responses). Don't cache personalized content unless the cache key includes the user identifier.

  6. 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.

Back to System Design Fundamentals