~learn.
?

Part 0233 minIntro+60 XP

Part 2: Infrastructure Building Blocks.

The Complete Guide to System Components

5 stages16 code

watch it first · newThe whole lesson in a 19-minute animated lecture5 quiz checkpoints · ask the AI about any moment · captions, chapters and speed

Intro · 1 min

Introduction#

Every system you design will use some combination of infrastructure building blocks. Understanding what each component does, how it works internally, when to use it, trade-offs, and how it fits with other components is essential for system design interviews and real-world architecture.

This document provides deep coverage of each component — not just definitions, but the practical knowledge you need to make informed design decisions.

DiagramThe system design building blocks
%% title: The system design building blocks
flowchart TD
  E@{ icon: "sd:globe", label: "Edge layer<br/>CDN · WAF · DNS<br/>Load Balancer · API Gateway" }
  A["Application layer<br/>Web Servers · Application Servers<br/>Microservices"]:::core
  D@{ icon: "sd:database", label: "Data layer<br/>Databases · Caches<br/>Object Storage · Search Engines" }
  M@{ icon: "sd:queue", label: "Messaging layer<br/>Message Queues · Event Streams<br/>Pub/Sub" }
  E --> A --> D --> M
  class E edge
  class D store
  class M queue
ExplainerThe whole lesson: infrastructure building blocks

Layer by layer: the edge (a CDN cuts a 200ms trip from Tokyo to about 20ms, DNS resolves names, a load balancer spreads 50,000 requests a second and drops servers that fail health checks), the application layer (stateless servers with sessions in Redis, monolith first), the data layer (PostgreSQL by default, cache-aside at a 5.9ms average, S3 for files) and messaging (an event bus, work queues and pub-sub instead of a synchronous chain).

Chapter 1 · Stage 1 of 5 · 22 min

Chapter 1: Edge Layer Components#

The edge layer is the first thing users interact with. It sits between users and your application servers, handling traffic before it reaches your backend. A well-designed edge layer dramatically improves performance, security, and reliability.

1.1 Content Delivery Network (CDN)#

What Is a CDN?#

A Content Delivery Network is a geographically distributed network of proxy servers (called edge servers or Points of Presence - PoPs). The goal is to serve content from locations physically closer to users, reducing latency dramatically.

When a user requests content, instead of traveling thousands of miles to your origin server, they connect to the nearest CDN edge location.

Your turn · checkpoint 1not answered

Estimate it: a Tokyo user with and without the edge

A user in Tokyo loads images from a US origin 200 ms away. A Tokyo CDN edge answers a cached image in ~20 ms, and a miss costs ~220 ms (edge plus the trip to the origin). If 95% of requests hit the edge cache, what is the average latency, and what share of requests still reaches the origin?

Think it through, or jot it down. Then compare.

One strong answer

  • Average: 0.95 × 20 ms + 0.05 × 220 ms = 19 + 11 = ~30 ms, against 200 ms+ for every request without a CDN.
  • Origin: only the 5% of misses, so the origin handles a twentieth of the traffic.
  • The misses still cost more than going direct, which is why the hit ratio is the number to watch.

How CDN Works — Step by Step#

DiagramCDN request flow
%% title: CDN request flow
sequenceDiagram
  participant U as User (Tokyo)
  participant E as CDN Edge (Tokyo)
  participant O as Origin (US-East)
  Note over U,O: SCENARIO · User in Tokyo requests image from US-based origin
  Note over U,O: WITHOUT CDN
  U->>O: Request · 200ms RTT
  O-->>U: Response
  Note over U,O: Every single request = 200ms+ latency<br/>Origin server handles ALL traffic worldwide<br/>High bandwidth costs for origin
  Note over U,O: WITH CDN · first request (cache miss)
  U->>E: 1. Request image.jpg from nearest CDN edge (Tokyo)
  E->>E: 2. Check local cache → MISS (not cached yet)
  E->>O: 3. Fetch from origin server (200ms to US)
  O-->>E: 4. Origin returns image to CDN edge
  E-->>U: 5. Cache the image AND return to user
  Note over U,O: First request total time ~220ms (still slow, but now cached!)
  Note over U,E: WITH CDN · subsequent requests (cache hit)
  U->>E: 1. Request image.jpg
  E-->>U: 2. Return from local cache immediately
  Note over U,E: No origin request needed!<br/>Subsequent requests ~20ms (10x faster!)<br/>Origin load reduced by 90%+ for popular content

CDN Points of Presence (PoPs)#

Major CDN providers have hundreds of edge locations worldwide:

CDN networks (examples)
Cloudflare network (Example)
  • North America: 55+ cities (LA, NYC, Chicago, Dallas, Seattle...)
  • Europe: 55+ cities (London, Frankfurt, Paris, Amsterdam...)
  • Asia Pacific: 50+ cities (Tokyo, Singapore, Sydney, Mumbai...)
  • South America: 20+ cities (São Paulo, Buenos Aires...)
  • Africa: 15+ cities (Johannesburg, Cape Town, Lagos...)
  • Middle East: 10+ cities (Dubai, Tel Aviv...)

Total: 330+ cities across 100+ countries (as of 2025-26)

AWS CloudFront
  • 750+ Points of Presence
  • 15 Regional Edge Caches
  • Integrated with AWS services

The closer the PoP to the user, the lower the latency.

  • A user in Mumbai connecting to a Mumbai PoP = ~5-10ms
  • Same user connecting to US origin = ~150-200ms

What to Cache vs What NOT to Cache#

This is critical for CDN strategy:

CDN caching decisions

Always cache (Static Content)

  • Images
    • Product photos, thumbnails, hero images
    • User avatars (public ones)
    • Icons, logos, favicons
    • Formats: jpg, png, gif, webp, svg, ico
    • TTL: 1 year (use versioned URLs for updates)
  • CSS & JavaScript
    • Stylesheets (app.css)
    • JavaScript bundles (app.js, vendor.js)
    • Use content hash in filename: app.a1b2c3d4.js
    • TTL: 1 year (hash changes = new URL = cache miss = fresh file)
  • Fonts
    • Web fonts: woff, woff2, ttf, eot
    • TTL: 1 year (fonts rarely change)
  • Video & audio
    • Video files: mp4, webm
    • Audio files: mp3, wav
    • HLS/DASH segments for streaming
    • TTL: 1 year or based on content type
  • Documents
    • PDFs, downloadable files
    • Software installers
    • TTL: Based on update frequency

Cache with care (Semi-Dynamic Content)

  • API responses (only if SAME for all users)
    • ok: Product catalog (same for everyone)
    • ok: Store locations
    • ok: Public configuration
    • no: User profile (different per user!)
    • TTL: 1-5 minutes (short, to stay fresh)
  • Search results
    • Only if you expect high cache hit rate
    • Popular searches might benefit
    • TTL: 1-5 minutes
  • User-agnostic pages
    • Homepage (logged-out version only)
    • Category/listing pages
    • Marketing/landing pages
    • TTL: 5-60 minutes

Never cache

  • User-specific data
    • User profiles, account pages
    • Shopping carts, wishlists
    • Order history
    • Personalized recommendations
    • Dashboard data
  • Authenticated API responses
    • Anything requiring login
    • User-specific search results
    • Private data
  • Write operations
    • POST requests
    • PUT/PATCH requests
    • DELETE requests
  • Real-time data
    • Stock prices, cryptocurrency rates
    • Live sports scores
    • Chat messages
    • Live analytics dashboards
  • Webhooks & callbacks
    • Payment confirmations
    • Third-party notifications
    • OAuth callbacks
  • Sensitive data
    • Financial information
    • Health records (HIPAA)
    • PII (Personally Identifiable Information)
Your turn · checkpoint 2not answered

Which cache should keep it longer: the CDN or the browser?

A page is the same for every user and changes a few times a day. You can give the CDN and the browser different cache times. Which one gets the longer TTL, and why?

Think it through, or jot it down. Then compare.

One strong answer

  • The CDN. You can purge a CDN when the content changes, but you can't reach into users' browsers to clear their copies.
  • So the browser keeps a short TTL and checks back often, and the CDN keeps a long one and is purged on change.
  • That's the max-age=3600, s-maxage=86400 pattern below: 1 hour in the browser, 24 hours at the CDN.

Cache-Control Headers Deep Dive#

HTTP headers control how CDNs (and browsers) cache content:

Cache-Control headers
Long-term caching (Static assets with content hash)

Cache-Control: public, max-age=31536000, immutable

  • public — Can be cached by CDN and browser
  • max-age=31536000 — Cache for 1 year (365 * 24 * 60 * 60)
  • immutable — Don't even check for updates (save revalidation)

Use for: app.a1b2c3.js, style.x7y8z9.css, image.abc123.png (Files with content hash in filename - changing content = new file)

Short-term caching (Semi-dynamic content)

Cache-Control: public, max-age=300, must-revalidate

  • max-age=300 — Cache for 5 minutes
  • must-revalidate — After TTL, MUST check with origin before serving

Use for: API responses, product listings, semi-dynamic pages

Revalidation caching (Always fresh, but allow caching)

Cache-Control: public, no-cache · ETag: "abc123"

  • no-cache — DOESN'T mean "don't cache"!
  • It means: Cache it, but revalidate with origin before using
  • ETag allows conditional requests (If-None-Match: "abc123")
  • If unchanged, origin returns 304 Not Modified (no body = fast)

Use for: Frequently updated content where staleness is unacceptable

No caching (Private/sensitive content)

Cache-Control: private, no-store

  • private — Only browser can cache (not CDN)
  • no-store — Don't cache at all, not even temporarily

Use for: User-specific data, authenticated responses, sensitive data

Different TTL for CDN vs browser

Cache-Control: public, max-age=3600, s-maxage=86400

  • max-age=3600 — Browser caches for 1 hour
  • s-maxage=86400 — CDN (shared cache) caches for 24 hours

Why? CDN can invalidate quickly. Browsers can't. So CDN can cache longer, browser should check more often.

Stale-while-revalidate (Performance optimization)

Cache-Control: public, max-age=300, stale-while-revalidate=86400

  • For 5 minutes: Serve from cache
  • For up to 24 hours after that: Serve stale content IMMEDIATELY, but fetch fresh content in background
  • After that window: Must wait for fresh content

Benefit: User never waits for revalidation!

Cache Invalidation Strategies#

Cache invalidation strategies
1. Versioned URLs (Recommended for static assets)
  • Instead of: /static/app.js
  • Use: /static/app.a1b2c3d4.js (content hash in filename)

How it works:

  1. Build tool (Webpack, Vite) generates hash from file content
  2. When code changes, hash changes, URL changes
  3. New URL = automatic cache miss = users get fresh file
  4. Old version stays cached (harmless, eventually expires)

Pros

  • Instant updates for users
  • Zero cache purge latency
  • Safe rollbacks (old URLs still work)
  • No CDN API calls needed
2. Purge API (For non-versioned content)

AWS CloudFront:

bash3 lines
aws cloudfront create-invalidation \
  --distribution-id EDFDVBD632 \
  --paths "/images/*" "/api/products/*"

Cloudflare:

bash3 lines
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
  -H "Authorization: Bearer {token}" \
  -d '{"files":["https://example.com/image.jpg"]}'

Propagation time:

CDN Propagation
CloudFront Typically under ~2 minutes; can take 10-15 minutes (must propagate to all 750+ PoPs)
Fastly ~150ms (instant purge is their specialty)
Cloudflare <150ms on average (Instant Purge, rebuilt 2022-2024)

Pros

  • Works for any URL pattern

Cons

  • Propagation delay
  • May have cost (CloudFront: 1000 free/month, then $0.005 each)
3. TTL-based expiry (Simple but has staleness window)

Cache-Control: max-age=300 (5 minutes)

  • Content cached for 5 minutes
  • After TTL, CDN checks with origin (If-Modified-Since)
  • Origin returns 304 (unchanged) or 200 (new content)

Trade-off:

  • Short TTL (60s) = More origin requests, always fresh
  • Long TTL (1hr) = Fewer origin requests, may be stale
4. Cache tags / surrogate keys (Advanced)

Supported by: Fastly, Varnish, some CDNs

Response header: Surrogate-Key: product-123 category-shoes brand-nike

When product 123 updates: PURGE /tags/product-123

  • → Invalidates ALL URLs tagged with "product-123"
  • → Product page, category pages, search results, recommendations...

Pros

  • Granular invalidation without knowing all URLs
  • One purge affects all related content

Cons

  • Requires CDN support
Best practice: Combine strategies
Content Strategy
Static assets (JS, CSS, images) Versioned URLs + long TTL
API responses Short TTL + Purge on update
HTML pages stale-while-revalidate + Purge on publish

CDN Provider Comparison#

CDN provider comparison
CloudFront (AWS)

Network: 750+ edge locations, 15 regional edge caches

Strengths

  • Deep AWS integration (S3, ALB, API Gateway, Lambda@Edge)
  • Lambda@Edge for compute at edge (auth, redirects, A/B testing)
  • Origin Shield (extra caching layer to protect origin)
  • Real-time logs to Kinesis
  • Field-level encryption
  • WebSocket support

Weaknesses

  • Slower invalidation (usually minutes, not milliseconds)
  • Complex pricing model
  • Configuration can be verbose

Best for: AWS-native applications, serverless architectures

Pricing: ~$0.085/GB (varies by region), $0.0075/10K HTTP requests ($0.01/10K HTTPS)

Cloudflare

Network: 330+ cities worldwide

Strengths

  • Generous FREE tier (yes, really free)
  • Built-in DDoS protection (industry-leading)
  • Workers (JavaScript/WASM at edge - very powerful)
  • Workers KV (distributed key-value at edge)
  • Instant cache purge (<150ms global average)
  • Easy setup (just change DNS nameservers)
  • Fast public DNS resolver (1.1.1.1, often top-ranked in benchmarks)
  • Argo Smart Routing (optimized paths)

Weaknesses

  • Less control over caching behavior than some competitors
  • Advanced features require paid plans
  • Must use Cloudflare DNS on Free/Pro plans (CNAME setup needs Business+; can be a dealbreaker)

Best for: Startups, security-focused apps, edge computing

Pricing: Free tier, Pro $20/mo, Business $200/mo (billed annually; $25 and $250 billed monthly), Enterprise custom

Fastly

Network: 100+ PoPs (fewer but very high capacity)

Strengths

  • INSTANT PURGE (<150ms global) - their killer feature
  • VCL (Varnish Configuration Language) for custom caching logic
  • Real-time analytics and logging
  • Fastly Compute (formerly Compute@Edge; WebAssembly at edge)
  • Image optimization built-in
  • Surrogate keys for granular invalidation

Weaknesses

  • Fewer PoPs than competitors (100+ vs 330+)
  • Steeper learning curve (VCL is powerful but complex)
  • No free tier (only a $50 developer trial credit)
  • Higher cost for small sites

Best for: Media streaming, real-time content, advanced caching needs

Pricing: $0.12/GB + $0.0075/10K requests (minimum ~$50/mo)

Akamai

Network: 4,000+ locations (largest network by far)

Strengths

  • Largest, most distributed network in the world
  • Enterprise-grade SLAs (99.99%+)
  • Advanced security (WAF, Bot Management, DDoS)
  • Media delivery specialization (used by major streaming services )
  • API acceleration

Weaknesses

  • Expensive (enterprise pricing)
  • Complex configuration and management
  • Sales-driven (hard to get started without talking to sales)

Best for: Large enterprises, media companies, global reach needs

Pricing: Enterprise contracts (typically $$$$$)

Quick selection guide
Situation Pick
AWS shop / serverless CloudFront
Startup / budget-conscious Cloudflare (free tier)
Need instant cache purge Fastly or Cloudflare
Edge computing / Workers Cloudflare
Enterprise / media streaming Akamai or Fastly
Simple setup Cloudflare
▲ Interview tip

CDN Interview Tips#

When discussing CDN in system design interviews:

CDN in the interview
What to mention
  1. What you're caching — "Static assets like images, CSS, JS, and fonts are served through CDN." "For API responses that are identical for all users, like product catalog, we can also cache at the CDN."
  2. Cache strategy — "We use content-hashed filenames (app.a1b2c3.js) which allows us to set an aggressive 1-year TTL. When code changes, the hash changes, so users automatically get the new version."
  3. Cache hit ratio — "We target 95%+ cache hit ratio for static assets. This reduces origin load significantly."
  4. Geographic distribution — "With users in Asia, Europe, and Americas, CDN ensures everyone gets sub-50ms latency for static content instead of 200ms+ to our US origin."
  5. Cache invalidation — "For product data that changes, we use short TTL (5 min) combined with cache purge on updates for immediate consistency when needed."
Sample interview answer

"For static content, we'll use CloudFront CDN. All JavaScript and CSS files use content-based hashing in filenames, like app.a1b2c3.js, which lets us set a 1-year cache TTL. When we deploy new code, the hash changes, so users automatically get fresh files.

For images, we serve them from S3 through CloudFront with the same long TTL. This gives us 95%+ cache hit ratio and reduces our origin bandwidth by about 90%.

For semi-dynamic content like product listings that are the same for all users, we cache at the CDN with a 5-minute TTL. When a product is updated, we purge that specific path.

With this setup, users globally get sub-50ms response times for static content, and our origin servers only handle cache misses and authenticated requests."

Further Reading: CDN#

1.2 Load Balancer#

What Is a Load Balancer?#

A load balancer distributes incoming network traffic across multiple servers. It's one of the most fundamental components for building scalable systems.

Why do we need it?

  1. Scalability: One server can only handle so much. Distribute load across many.
  2. Availability: If one server dies, others continue serving traffic.
  3. Performance: Route to the least-loaded or fastest-responding server.
Your turn · checkpoint 3not answered

What breaks if one of three servers dies?

A load balancer spreads 50,000 requests a second over three identical servers. Each server can handle at most 20,000 requests a second. One server crashes and the health checks remove it. What happens next?

Think it through, or jot it down. Then compare.

One strong answer

  • Before: ~17,000 a second each, inside the 20,000 limit.
  • After: 50,000 ÷ 2 = 25,000 a second each, more than either survivor can take, so they overload too.
  • Removing a dead server only helps if the rest have room for its share. Size the pool so N − 1 servers can carry the whole load.

How Load Balancing Works#

DiagramLoad balancer architecture
%% title: Load balancer architecture
flowchart TD
  T@{ icon: "sd:users", label: "Incoming traffic<br/>(50,000 req/sec)" } --> LB@{ icon: "sd:load-balancer", label: "LOAD BALANCER<br/>Listener (Port 443) · Routing Rules · Health Checks<br/>SSL Termination · Session Persistence · Algorithm Selection" }
  LB --> S1@{ icon: "sd:server", label: "Server 1<br/>~17K req/s · OK" }
  LB --> S2@{ icon: "sd:server", label: "Server 2<br/>~17K req/s · OK" }
  LB --> S3@{ icon: "sd:server", label: "Server 3<br/>~17K req/s · DOWN" }
  HC["Health check probes<br/>(every 10-30 seconds)"]:::note -.-> S1 & S2 & S3
  class T client
  class LB edge
  class S1,S2 ok
  class S3 bad
  • Server 3 is marked unhealthy → traffic redistributed to 1 & 2
  • Now: Server 1 and 2 each handle ~25K req/s

Load Balancing Algorithms — Detailed#

Load balancing algorithms
1. Round robin

How it works:

  • Request 1 → Server A
  • Request 2 → Server B
  • Request 3 → Server C
  • Request 4 → Server A (cycle repeats)
  • Request 5 → Server B
  • ...

Implementation: next = servers[counter % len(servers)]; counter++

Pros

  • Simple to implement and understand
  • Even distribution over time
  • No state to maintain
  • Works well with homogeneous servers

Cons

  • Ignores server capacity (all treated equal)
  • Ignores current load
  • Some requests may take longer than others

Best for: Identical servers, stateless requests, similar workloads

2. Weighted round robin
Server Configuration Distribution (out of 10 requests)
Server A weight = 5 (more powerful, 8 CPU cores) 5 requests (50%)
Server B weight = 3 (medium, 4 CPU cores) 3 requests (30%)
Server C weight = 2 (smaller, 2 CPU cores) 2 requests (20%)

Pros

  • Accounts for different server capacities
  • Useful during migrations (old=1, new=5)
  • Canary deployments (stable=99, canary=1)

Cons

  • Weights are static, don't adapt to actual load
  • Requires manual tuning

Best for: Heterogeneous servers, gradual rollouts, canary deploys

3. Least connections

Current state:

  • Server A: 150 active connections
  • Server B: 75 active connections ← Next request goes here
  • Server C: 120 active connections

How it works:

  1. LB tracks active connections per server
  2. New request → server with fewest connections
  3. Connection count incremented when request starts
  4. Connection count decremented when response completes

Pros

  • Automatically adapts to actual load
  • Handles varying request durations well
  • Self-balancing

Cons

  • Requires connection tracking (slight overhead)
  • Doesn't account for connection "weight" (some heavier than others)

Best for: Long-running connections, WebSockets, variable workloads

4. Least response time

Current metrics:

  • Server A: avg response = 45ms, 50 connections
  • Server B: avg response = 28ms, 60 connections ← Fastest!
  • Server C: avg response = 52ms, 40 connections

Calculation: Route to server with lowest (response_time * connections)

Pros

  • Optimizes for actual user-perceived latency
  • Automatically adapts to server performance
  • Accounts for both load and speed

Cons

  • More complex to implement
  • Can be "noisy" with variable latencies
  • May starve consistently slow servers

Best for: Latency-sensitive applications, heterogeneous performance

5. IP hash (Source IP Affinity)

How it works: server_index = hash(client_ip) % number_of_servers

Client hash(ip) % 3 Server
192.168.1.1 12345 12345 % 3 = 0 Server A
192.168.1.2 67890 67890 % 3 = 0 Server A
192.168.1.3 11111 11111 % 3 = 2 Server C

Same client always goes to same server (until server pool changes)

Pros

  • Sticky sessions without cookies
  • Good for local server-side caching
  • Stateless LB (no session table needed)

Cons

  • Can cause uneven distribution
  • Adding/removing server reshuffles many clients
  • Mobile users may change IP (WiFi → cellular)
  • Users behind NAT share IP

Best for: Caching layers, when cookies aren't available

6. Consistent hashing (Advanced)

How it works:

  • Servers placed on a virtual ring (hash space 0 to 2^32 - 1)
  • Request key hashed to point on ring
  • Route to first server clockwise from that point
  • On the ring: Server A → Server B → Server C (clockwise)

Key benefit: Adding/removing server only affects 1/N of keys! (Unlike IP hash where many clients get reshuffled)

Best for: Distributed caches, databases, when servers change often

Algorithm selection guide
Scenario Recommended Algorithm
Simple stateless API Round Robin
Mixed server sizes Weighted Round Robin
Long-running connections Least Connections
Latency-sensitive app Least Response Time
Need session stickiness IP Hash or Cookie-based
Distributed cache Consistent Hashing
Canary deployment Weighted Round Robin

Default choice: Least Connections (adapts well to most scenarios)

ExplainerLoad balancing: round robin, weighted, least connections

Round robin sends requests to A, B, C in turn, ignoring load. Weighted round robin with weights 5, 3 and 2 gives A 5, B 3 and C 2 of every 10 requests. Least connections sends each new request to the server with the fewest active connections (B, with 75 against 150 and 120).

Your turn · checkpoint 4not answered

Balancing PostgreSQL connections: Layer 4 or Layer 7?

Your app servers open connections to a pool of PostgreSQL read replicas, and you want a load balancer in front of them. Should it work at Layer 4 or Layer 7?

Think it through, or jot it down. Then compare.

One strong answer

  • Layer 4. The database protocol isn't HTTP, so a Layer 7 balancer has no paths, headers or cookies to route on.
  • Layer 4 only needs the IP address and port, works for any TCP protocol and adds very little latency.
  • Layer 7 is worth its extra cost only when you need content-based routing, such as sending /api/v2 somewhere else.

Layer 4 vs Layer 7 Load Balancing#

Layer 4 vs Layer 7 load balancing

OSI model reference: Layer 4 = Transport Layer (TCP/UDP) · Layer 7 = Application Layer (HTTP/HTTPS)

Layer 4 (L4)

What it sees — TCP/IP Packet Headers:

  • Source IP: 192.168.1.100
  • Destination IP: 10.0.0.1
  • Source Port: 52431
  • Destination Port: 443
  • Protocol: TCP
  • [Payload is encrypted/opaque - LB cannot read it]

What it can do

  • Route based on IP address and port
  • Very fast (no packet inspection, just forward)
  • Protocol agnostic (TCP, UDP, any port)
  • Preserve client IP (DSR/Direct Server Return mode)
  • Handle millions of connections

What it cannot do

  • Route based on URL path (/api vs /static)
  • Read HTTP headers or cookies
  • Modify request/response content
  • Smart routing based on content
  • SSL termination (usually passed through; AWS NLB can terminate TLS)

Use cases: Database connections (MySQL, PostgreSQL) · Redis, Memcached · Gaming servers (UDP) · Any non-HTTP TCP/UDP service · When you need maximum performance

AWS: Network Load Balancer (NLB) · GCP: TCP/UDP Load Balancer

Performance: ~1M+ connections/sec, <100μs added latency

Layer 7 (L7)

What it sees (after SSL termination) — Full HTTP Request:

  • Method: GET
  • Path: /api/v2/users/123
  • Host: api.example.com
  • Headers:
    • Authorization: Bearer eyJhbGc...
    • Content-Type: application/json
    • X-Request-ID: abc123
    • X-Feature-Flag: new-checkout
  • Cookies:
    • session_id=xyz789
  • Body: {"name": "John"}

What it can do

  • Route based on URL path (/api/* → API servers)
  • Route based on hostname (api.* vs www.*)
  • Route based on HTTP headers
  • Route based on cookies (session affinity)
  • Route based on query parameters
  • SSL/TLS termination
  • Add/modify/remove headers
  • URL rewriting (/old-path → /new-path)
  • Response caching
  • Compression (gzip)
  • Rate limiting
  • Authentication
  • WebSocket support

Example L7 routing rules:

Rule Condition Route to
Rule 1 IF path starts with /api/v1/* api-v1-target-group
Rule 2 IF path starts with /api/v2/* api-v2-target-group
Rule 3 IF path starts with /static/* static-servers
Rule 4 IF header X-Canary equals "true" canary-target-group
Rule 5 IF host equals admin.example.com admin-servers
Default — web-servers

Use cases: Web applications · REST APIs · Microservices routing · A/B testing (route 10% to new version) · Canary deployments · Multi-tenant applications (route by subdomain) · Blue-green deployments

AWS: Application Load Balancer (ALB) · GCP: HTTP(S) Load Balancer

Performance: ~100K connections/sec, 1-5ms added latency

Comparison summary
Aspect Layer 4 (NLB) Layer 7 (ALB)
Operates at TCP/UDP level HTTP/HTTPS level
Sees IP + Port Full HTTP request
Speed Very fast Fast
Latency added <100μs 1-5ms
SSL termination Usually pass-through (NLB TLS listeners can terminate) Yes
Content routing No Yes
Header manipulation No Yes
WebSocket Yes (pass-through) Yes (native)
Health checks TCP/HTTP HTTP with path
Cost (AWS) Lower Higher

When to use which:

  • HTTP/HTTPS traffic → L7 (ALB)
  • Database connections → L4 (NLB)
  • Need content-based routing → L7
  • Need maximum throughput → L4
  • Non-HTTP protocols → L4

Health Checks Deep Dive#

Health checks

Purpose: Detect unhealthy servers and stop routing traffic to them

Configuration example (AWS ALB)
Setting Value
Protocol HTTP
Port 8080
Path /health
Interval 30 seconds
Timeout 5 seconds
Healthy threshold 2 consecutive successes
Unhealthy threshold 3 consecutive failures
Success codes 200-299
Health check timeline example
Time LB Sends Server Response Server Status
0s GET /health 200 OK Starting (1/2)
30s GET /health 200 OK HEALTHY (2/2) ← Active!
60s GET /health 200 OK Healthy
90s GET /health 500 Error Warning (1/3 failures)
120s GET /health Connection timeout Warning (2/3 failures)
150s GET /health 500 Error UNHEALTHY ← Removed!
180s GET /health 200 OK Recovering (1/2)
210s GET /health 200 OK HEALTHY ← Back in pool!
Types of health checks

1. Shallow / liveness check — Just verifies the process is running and can respond

GET /health Response:

json3 lines
{
  "status": "ok"
}

Pros

  • Fast, low overhead
  • Always succeeds if process is up
  • Good for load balancer health checks

Cons

  • Doesn't verify dependencies work

2. Deep / readiness check — Verifies the app AND all its dependencies are healthy

GET /health/ready Response (healthy):

json19 lines
{
  "status": "healthy",
  "version": "1.2.3",
  "uptime_seconds": 86400,
  "checks": {
    "database": {
      "status": "up",
      "latency_ms": 5
    },
    "redis": {
      "status": "up",
      "latency_ms": 1
    },
    "external_api": {
      "status": "up",
      "latency_ms": 45
    }
  }
}

Response (unhealthy - HTTP 503):

json11 lines
{
  "status": "unhealthy",
  "checks": {
    "database": {
      "status": "down",
      "error": "Connection refused"
    },
    "redis": { "status": "up" },
    "external_api": { "status": "up" }
  }
}

Pros

  • Comprehensive status
  • Catches dependency failures

Cons

  • Slower (queries dependencies)
  • Risk: If DB is down, ALL servers marked unhealthy!
Best practice

Use SHALLOW check for load balancer (keep traffic flowing). Use DEEP check for monitoring/alerting (know what's broken).

Endpoint Checked by
/health LB checks this (fast, always works if process up)
/health/ready Kubernetes readiness probe
/health/detailed Monitoring systems (Datadog, etc.)

Load Balancer Providers#

Load balancer options
Cloud load balancers

AWS

  • ALB (Application Load Balancer) - Layer 7
    • HTTP/HTTPS traffic
    • Path-based and host-based routing
    • WebSocket and HTTP/2 support
    • Integrated with WAF, Cognito
    • ~$0.0225/hour + $0.008/LCU
  • NLB (Network Load Balancer) - Layer 4
    • TCP/UDP/TLS traffic
    • Ultra-low latency (<100μs)
    • Millions of requests per second
    • Static IP / Elastic IP support
    • ~$0.0225/hour + $0.006/NLCU

GCP

  • HTTP(S) Load Balancer - Global L7
    • Single anycast IP worldwide
    • Automatic multi-region failover
    • Cloud CDN integration
  • Network Load Balancer - Regional L4
    • High-performance TCP/UDP
    • Regional scope
Software load balancers

NGINX

  • Most popular web server / reverse proxy
  • HTTP, TCP, UDP load balancing
  • Open source (free) + NGINX Plus (commercial)
  • Event-driven, high performance
  • Great documentation and community

Config example:

nginx12 lines
upstream backend {
    least_conn;
    server 10.0.0.1:8080 weight=3;
    server 10.0.0.2:8080;
    server 10.0.0.3:8080 backup;
}
server {
    listen 80;
    location / {
        proxy_pass http://backend;
    }
}

HAProxy

  • High-performance TCP/HTTP load balancer
  • Battle-tested (GitHub, Reddit, Stack Overflow, Twitter)
  • Very detailed metrics and logging
  • Advanced health checking

Envoy

  • Modern, cloud-native proxy
  • Originally from Lyft, now CNCF
  • Foundation of service meshes (Istio, AWS App Mesh)
  • Native gRPC support
  • Dynamic configuration via xDS API
  • Best for: Kubernetes, service mesh, microservices

Traefik

  • Kubernetes-native
  • Auto-discovery of services
  • Automatic Let's Encrypt certificates
  • Best for: Kubernetes, Docker Swarm

Further Reading: Load Balancing#

1.3 Domain Name System (DNS)#

What Is DNS?#

DNS (Domain Name System) is the internet's phone book. It translates human-readable domain names (like www.example.com) into IP addresses (like 93.184.216.34) that computers use to identify each other.

Without DNS, you'd need to memorize IP addresses for every website you visit. DNS makes the internet usable by humans.

Why DNS Matters for System Design#

DNS is much more than name resolution. In modern systems, DNS is used for:

  • Load balancing: Distributing traffic across multiple servers
  • Geographic routing: Directing users to the nearest datacenter
  • Failover: Automatically switching to backup servers when primary fails
  • Service discovery: Letting microservices find each other
  • Traffic management: Canary deployments, blue-green deployments

Understanding DNS deeply is essential for designing globally distributed, highly available systems.

How DNS Works — Step by Step#

When you type www.example.com in your browser, here's what happens:

DiagramDNS resolution flow
%% title: DNS resolution flow
sequenceDiagram
  participant B as Browser
  participant OS as Operating system
  participant R as DNS resolver
  participant RT as Root server
  participant T as .com TLD server
  participant A as ns1.example.com
  Note over B: STEP 1 · Browser cache check
  B->>B: "Have I looked up this domain recently?"
  Note over B: If YES → use cached IP<br/>(typically cached for TTL duration)<br/>If NO → ask Operating System
  Note over B,OS: STEP 2 · Operating system cache
  B->>OS: Resolve www.example.com
  OS->>OS: "Is this domain in my local DNS cache?"
  Note over OS: If YES → return cached IP to browser<br/>If NO → ask configured DNS resolver
  Note over OS,R: STEP 3 · DNS resolver (recursive resolver)
  OS->>R: Ask configured DNS resolver<br/>(ISP or 8.8.8.8 or 1.1.1.1)
  R->>R: "Is it in my cache?"
  Note over R: If YES → return cached IP<br/>If NO → start recursive resolution (steps 4-6)
  Note over R,RT: STEP 4 · Root DNS server
  R->>RT: "Where is www.example.com?"
  Note over RT: 13 root server identities<br/>(a.root-servers.net through m.root-servers.net)<br/>~2,000+ instances worldwide via Anycast
  RT-->>R: "I don't know, but .com TLD is handled by:<br/>a.gtld-servers.net, b.gtld-servers.net, ..."
  Note over R,T: STEP 5 · TLD (Top Level Domain) server
  R->>T: "Where is www.example.com?"
  T-->>R: "example.com is handled by nameservers:<br/>ns1.example.com (IP 93.184.216.34)<br/>ns2.example.com (IP 93.184.216.35)"
  Note over R,A: STEP 6 · Authoritative DNS server
  R->>A: "What is IP of www.example.com?"
  A-->>R: "www.example.com = 93.184.216.34, TTL=300"
  Note over B,R: STEP 7 · Response returns
  R->>R: Cache answer (respects TTL)
  R-->>OS: Return IP
  OS->>OS: Cache
  OS-->>B: Return IP
  B->>B: Cache and connect to 93.184.216.34

Timing:

  • Full resolution (no cache): 50-200ms
  • Cached at resolver: 1-10ms
  • Cached locally: <1ms

DNS Record Types#

DNS record types
A record (Address)

Maps domain to IPv4 address

dns2 lines
example.com.      300   IN   A   93.184.216.34
api.example.com.  60    IN   A   10.0.1.100

Multiple A records = DNS round-robin load balancing

dns3 lines
api.example.com.  60   IN   A   10.0.1.100
api.example.com.  60   IN   A   10.0.1.101
api.example.com.  60   IN   A   10.0.1.102
AAAA record (IPv6)

Maps domain to IPv6 address

dns1 line
example.com.  300  IN  AAAA  2606:2800:220:1:248:1893:25c8:1946
CNAME record (Canonical Name / Alias)

Creates an alias pointing to another domain

dns3 lines
www.example.com.   300  IN  CNAME  example.com.
cdn.example.com.   300  IN  CNAME  d123456.cloudfront.net.
blog.example.com.  300  IN  CNAME  mycompany.ghost.io.

Important: Cannot use CNAME at zone apex (example.com). Workarounds: ALIAS (Route 53), ANAME, CNAME flattening (Cloudflare)

MX record (Mail Exchange)

Specifies mail servers for the domain

dns2 lines
example.com.  300  IN  MX  10  mail1.example.com.
example.com.  300  IN  MX  20  mail2.example.com.  (backup)

The number after MX is the Priority (lower = higher priority).

TXT record (Text)

Stores text, used for verification and email security

dns6 lines
; Domain verification:
example.com.  IN  TXT  "google-site-verification=abc123..."
; SPF (email authentication):
example.com.  IN  TXT  "v=spf1 include:_spf.google.com ~all"
; DMARC (email policy):
_dmarc.example.com.  IN  TXT  "v=DMARC1; p=reject; rua=..."
NS record (Name Server)

Delegates DNS authority to nameservers

dns2 lines
example.com.  86400  IN  NS  ns1.example.com.
example.com.  86400  IN  NS  ns2.example.com.

DNS in System Design#

DNS use cases in system design
1. Geographic routing (GeoDNS)

Route users to nearest datacenter:

User Query Resolves to
User in Tokyo api.example.com 10.1.0.1 (Asia server)
User in NYC api.example.com 10.2.0.1 (US East server)
User in London api.example.com 10.3.0.1 (EU server)

Benefits: Lower latency, data residency compliance

2. Failover

Automatic switch to backup when primary fails:

  • Primary healthy: api.example.com → primary-dc.example.com
  • Primary fails: api.example.com → backup-dc.example.com

TTL affects failover speed! Lower TTL = faster failover

3. Weighted routing (Canary Deployments)

Split traffic between versions — api.example.com:

  • Weight 95: stable.internal (v1.0)
  • Weight 5: canary.internal (v1.1)
4. Service discovery (Internal DNS)

Services find each other by DNS name:

  • user-service.internal → 10.0.1.1, 10.0.1.2, 10.0.1.3
  • order-service.internal → 10.0.2.1, 10.0.2.2

No hardcoded IPs! Works with auto-scaling.

DNS Providers#

DNS providers and resolvers

Managed DNS

  • Route 53 (AWS) — Health checks, latency/geo routing, $0.50/zone/mo
  • Cloudflare DNS — Free, fastest, DDoS protection, instant propagation
  • Cloud DNS (GCP) — Anycast, private zones
  • Azure DNS — Azure integration

Public resolvers

  • 8.8.8.8 / 8.8.4.4 — Google Public DNS
  • 1.1.1.1 / 1.0.0.1 — Cloudflare (fastest)
  • 9.9.9.9 — Quad9 (security-focused)
  • 208.67.222.222 — OpenDNS

Further Reading: DNS#

Quick check4 questions · +5 XP each

Edge layer: CDN, load balancer, DNS

  1. Q1/4 You ship a fix to app.js, which the CDN caches with a 1-year TTL, and users keep getting the old file. What is the recommended fix for static assets?

    Answer & explanation

    Answer: C

    With versioned URLs, new content means a new URL and an automatic cache miss: instant, no purge delay, safe rollbacks. A 60-second TTL works, but it throws away most of your hit ratio and sends far more requests to the origin.

  2. Q2/4 A response has Cache-Control: public, no-cache plus an ETag. What does the CDN do with it?

    Answer & explanation

    Answer: A

    no-cache means "cache, but revalidate first"; if the ETag still matches, the origin answers 304 with no body. "Never store" is no-store. Mixing the two up is a classic mistake.

  3. Q3/4 You need /api/v2/* routed to a new target group and TLS terminated at the load balancer. Which load balancer do you need?

    Answer & explanation

    Answer: B

    Only L7 sees the decrypted HTTP request (path, host, headers), so only L7 can route on /api/v2. L4 is faster, but it sees only IP and port; the payload is opaque to it.

  4. Q4/4 Your LB health check queries the database, Redis and an external API. The database has a 30-second blip. What happens?

    Answer & explanation

    Answer: B

    A deep check fails on every server together when a shared dependency is down, so the LB marks the whole pool unhealthy. That is why the lesson uses a shallow /health check for the LB and keeps deep checks for monitoring.

Chapter 2 · Stage 2 of 5 · 2 min

Chapter 2: Application Layer#

The application layer is where your business logic runs.

2.1 Stateless vs Stateful Applications#

This is critical for scalability:

Stateful vs stateless servers

Stateful server (Problematic)

Server stores session data in memory:

  1. Request 1: User logs in → Server A stores session in memory
  2. Request 2: User loads profile → Must go to Server A (has session!)
  3. Request 3: Server A crashes → Session LOST! User logged out!

Problems

  • User MUST return to same server (sticky sessions required)
  • Server crash = data loss
  • Can't add servers easily (new servers don't have sessions)
  • Memory grows with users
  • Horizontal scaling is painful

Stateless server (Preferred)

Server stores NO session data. All state is external.

  1. Request 1: User logs in → Server A creates session in Redis. Returns JWT token to client
  2. Request 2: User loads profile → Goes to Server B (any server works!). Server B validates JWT, gets session from Redis
  3. Request 3: Server A crashes → No problem! User continues on B, C

Benefits

  • Any server can handle any request
  • Easy horizontal scaling (just add servers)
  • Server failure = no data loss
  • Simple load balancing (round-robin works)
  • Easy deployments (no session migration)

Where to store state

  • Sessions → Redis (fast, TTL support)
  • User data → PostgreSQL (persistent)
  • Files → S3 (durable)
  • Cache → Redis/Memcached

2.2 Monolith vs Microservices#

Monolith vs microservices

Monolith

Single application containing a User Module and an Order Module, over a shared database.

Pros

  • Simple to develop
  • Easy to test
  • Simple deployment
  • No network latency
  • ACID transactions easy

Cons

  • Scale everything together
  • Single failure affects all
  • Technology lock-in
  • Slow builds as code grows
  • Team coordination hard

Start with monolith when:

  • Small team (< 10 developers)
  • New product (unclear requirements)
  • Need to move fast (MVP)
  • Limited DevOps expertise

Microservices

User Service, Order Service, Payment Service — each with its own DB. Independent databases & deploys.

Pros

  • Scale services independently
  • Technology diversity
  • Isolated failures
  • Team autonomy
  • Faster CI/CD per service

Cons

  • Complex operations
  • Network latency
  • Distributed transactions hard
  • Testing complexity
  • Need observability tooling

Consider microservices when:

  • Large team (can dedicate team per service)
  • Clear service boundaries exist
  • Different scaling requirements
  • Strong DevOps culture
  • Have observability infrastructure

"Start with a monolith, extract services when needed" (paraphrasing Martin Fowler, "MonolithFirst")

Further Reading: Architecture#

Quick check2 questions · +5 XP each

Stateless apps and monolith vs microservices

  1. Q1/2 Sessions live in each app server's memory. Server A crashes during a traffic peak. What do its users see, and what is the fix?

    Answer & explanation

    Answer: A

    In-memory state dies with the process. Externalizing it (Redis for sessions) lets any server handle any request. Sticky sessions are the symptom, not the cure: they still lose the session when that one server dies.

  2. Q2/2 A 6-person team is building a new product whose requirements are still unclear. Which architecture does the lesson recommend?

    Answer & explanation

    Answer: C

    Small team, unclear requirements, need to move fast: all the "start with monolith" signals. Microservices pay off with large teams and clear boundaries; before that you pay the ops and distributed-transaction cost for nothing.

Chapter 3 · Stage 3 of 5 · 4 min

Chapter 3: Data Layer#

Your turn · checkpoint 5not answered

Moving money between two accounts

A transfer takes 100 from account 1 and adds 100 to account 2. The server could crash between the two writes. Which kind of database do you pick for the balances, and what feature are you relying on?

Think it through, or jot it down. Then compare.

One strong answer

  • A relational (SQL) database with an ACID transaction. Both updates go between BEGIN and COMMIT, so both happen or neither does.
  • Without that, a crash between the writes makes 100 disappear (or appear from nowhere).
  • The selection guide sends "Need ACID transactions?" to PostgreSQL or MySQL. The NoSQL stores are picked for flexible schemas, speed or write volume instead.

3.1 SQL vs NoSQL Databases#

SQL vs NoSQL databases
SQL (Relational) databases

Examples: PostgreSQL, MySQL, SQL Server

Structure: Fixed schema, tables with rows and columns

users orders
id (PK) INT id (PK) INT
email TEXT user_id (FK) → users (1:N) INT
name TEXT total DECIMAL
created_at TIMESTAMP status TEXT

ACID transactions:

sql4 lines
BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
  UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;  -- Both happen or neither happens

Best for

  • Complex queries with JOINs
  • Transactions (banking, orders)
  • Data integrity critical
  • Structured, predictable data
  • Reporting and analytics

Default choice: PostgreSQL (handles most use cases well)

NoSQL database types

1. Document store (MongoDB, CouchDB)

jsonc7 lines
{
  "_id": "user_123",
  "name": "John",
  "orders": [                    // Embedded documents
    {"id": "ord1", "total": 99.99}
  ]
}
  • ok: Flexible schema, natural for hierarchical data
  • no: No JOINs (denormalize instead)
  • Best for: Content management, catalogs, user profiles

2. Key-value store (Redis, DynamoDB)

  • user:123 → {"name": "John", "email": "j@ex.com"}

  • session:abc → {"user_id": 123, "expires": ...}

  • ok: Extremely fast O(1) lookups

  • no: No complex queries

  • Best for: Sessions, caching, real-time data, leaderboards

3. Wide-column store (Cassandra, HBase)

Row Key Column1 Column2 Column3
user:123 name:John email:j@ex.com age:30
user:456 name:Jane city:NYC (no age!)
  • ok: Massive write throughput, linear scaling
  • no: Limited query patterns
  • Best for: Time-series, IoT, logging, high write volume

4. Graph database (Neo4j, Neptune)

  • (Alice) —FOLLOWS→ (Bob) —WORKS_AT→ (Acme)

  • ok: Natural for connected data, fast traversals

  • Best for: Social networks, recommendations, fraud detection

Database selection quick guide
Need Use
Need ACID transactions? PostgreSQL, MySQL
Flexible schema? MongoDB, DynamoDB
Simple key-value + caching? Redis
Massive writes? Cassandra
Full-text search? Elasticsearch
Graph relationships? Neo4j
Default choice? PostgreSQL

3.2 Caching#

Caching essentials
Why caching matters

Without cache: Every request → Database (50ms). 1000 requests = 1000 DB queries = DB overload

With cache (90% hit rate):

  • 900 requests → Cache (1ms)
  • 100 requests → Database (50ms)
  • Average latency: (0.9 × 1) + (0.1 × 50) = 5.9ms
  • vs 50ms without cache = 8.5x faster!
  • Database load reduced by 90%!
Cache-aside pattern (Most Common)
pythonget_user · 18 lines
def get_user(user_id):
    # 1. Check cache first
    cached = redis.get(f"user:{user_id}")
    if cached:
        return json.loads(cached)  # Cache HIT

    # 2. Cache MISS - query database
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)

    # 3. Store in cache for next time
    redis.setex(f"user:{user_id}", 3600, json.dumps(user))  # 1hr TTL

    return user

# INVALIDATION (on update):
def update_user(user_id, data):
    db.update(user_id, data)
    redis.delete(f"user:{user_id}")  # Invalidate cache
Cache eviction policies
Policy Behaviour
LRU (Least Recently Used) Remove least recently accessed (most common choice; note Redis defaults to noeviction unless configured)
LFU (Least Frequently Used) Remove least often accessed
FIFO (First In First Out) Remove oldest
TTL (Time To Live) Remove after expiration time
Redis vs Memcached
  • Redis: Rich data structures, persistence, pub/sub, Lua scripts
  • Memcached: Simpler, multi-threaded, more memory efficient

Default choice: Redis (more versatile)

WorksheetThe 50,000 requests a second example, end to end (assumptions labelled)
Value
Incoming requests (the load balancer example) 50,000/s over 3 app servers → ~17,000/s each
After one server fails 50,000 ÷ 2 = 25,000/s each
User reads per request (assumed) 1 → 50,000 reads/s
Cache hit rate (assumed, as in the caching example) 90% → 45,000/s from Redis, 5,000/s to the database
Average read latency (1 ms hit, 50 ms miss) 0.9 × 1 + 0.1 × 50 = 5.9 ms
If the hit rate drops to 80% 10,000/s to the database, 10.8 ms average

Conclusion: size each app server for 25,000 requests a second, not 17,000, so the pool survives losing one. Size the database for well above 5,000 queries a second, because a small drop in the cache hit rate doubles its load.

Further Reading: Databases & Caching#

3.3 Object Storage#

Object storage (S3)
Structure

Bucket: my-app-uploads

  • images/profiles/user123.jpg (Object)
  • videos/uploads/intro.mp4 (Object)
  • documents/reports/q3.pdf (Object)
Characteristics

Pros

  • Virtually unlimited scale
  • 99.999999999% durability (11 nines!)
  • HTTP access (easy CDN integration)
  • Cheap (compared to block storage)
  • Versioning, lifecycle policies

Cons

  • Not a native filesystem (FUSE tools like Mountpoint/s3fs exist, with limits)
  • No partial updates (replace whole object)
  • Higher latency than local disk
Use cases
  • User uploads (images, videos, documents)
  • Static website assets
  • Backups and archives
  • Data lake storage
Providers
  • AWS S3 (the original, most popular)
  • Google Cloud Storage
  • Azure Blob Storage
  • Cloudflare R2 (S3-compatible, no egress fees!)
Quick check3 questions · +5 XP each

Databases, caching and object storage

  1. Q1/3 Cache hits take 1ms and database reads 50ms. At a 90% hit rate the average is about 5.9ms. What is the average if the hit rate falls to 80%?

    Answer & explanation

    Answer: A

    0.8 x 1ms + 0.2 x 50ms = 0.8 + 10 = 10.8ms. Misses dominate, so losing 10 points of hit rate nearly doubles latency. It also doubles the share of traffic hitting the database (10% to 20%), which is often the bigger problem.

  2. Q2/3 With cache-aside, update_user writes to the database and then deletes the cache key. What goes wrong if you skip the delete?

    Answer & explanation

    Answer: B

    The cache does not know the DB changed, so the stale entry is served until it expires. Deleting it forces the next read to reload fresh data. Caches never roll back database writes; staleness is silent, which is what makes it dangerous.

  3. Q3/3 You must ingest millions of IoT sensor readings per second and query them by device and time. Which store fits best?

    Answer & explanation

    Answer: C

    Wide-column stores are built for massive write throughput and linear scaling, the lesson's pick for time-series and IoT. PostgreSQL is the default for transactions and joins, not for this write volume.

Chapter 4 · Stage 4 of 5 · 2 min

Chapter 4: Messaging Layer#

4.1 Message Queues#

DiagramMessage queue patterns
%% title: Message queue patterns
flowchart TD
  subgraph PS["Publish-subscribe (fan-out)"]
    direction TB
    PUB@{ icon: "sd:server", label: "Publisher" } --> T@{ icon: "sd:stream", label: "Topic" }
    T --> SA["Subscriber A"]:::core
    T --> SB["Subscriber B"]:::core
    T --> SC["Subscriber C"]:::core
  end
  subgraph P2P["Point-to-point (work queue)"]
    direction TB
    PA@{ icon: "sd:server", label: "Producer A" } --> Q@{ icon: "sd:queue", label: "Queue<br/>[3][2][1]" }
    PB@{ icon: "sd:server", label: "Producer B" } --> Q
    Q --> C1["Consumer 1"]:::core
    Q --> C2["Consumer 2"]:::core
  end
  class PUB,PA,PB service
  class T,Q queue
  • Point-to-point (work queue): each message consumed by ONE consumer only. Use for: background jobs, task distribution, work queues.
  • Publish-subscribe (fan-out): each message delivered to ALL subscribers. Use for: events, notifications, real-time updates.

Message queue comparison:

RabbitMQ Kafka SQS Redis Streams
Throughput Medium Very High High Medium
Replay No (classic queues; Streams: yes) Yes No Yes
Ordering Queue Partition FIFO opt Stream
Persistence Optional Yes Yes Optional
Best for Routing Events Tasks Lightweight

When to use queues:

  • ✓ Async processing (user doesn't wait for slow task)
  • ✓ Decoupling services (Order → Inventory, Email, Analytics)
  • ✓ Handling traffic spikes (queue buffers requests)
  • ✓ Reliable delivery (payment events)
  • ✓ Rate limiting external APIs
Your turn · checkpoint 6not answered

What breaks if email is down in a synchronous chain?

Order Service calls Inventory, then Email, then Analytics, waiting for each reply in turn. The Email service goes down for ten minutes. What happens to new orders? What changes if Order Service publishes an OrderCreated event instead?

Think it through, or jot it down. Then compare.

One strong answer

  • Synchronous chain: every order waits on Email and then fails. One broken service stops checkout, even though inventory and analytics are fine.
  • Event bus: Order Service creates the order, emits one event and moves on. Inventory and Analytics consume it as usual. Email catches up from the bus when it's back.
  • The price: the flow is harder to trace, and the confirmation email arrives late (eventual consistency).

4.2 Event-Driven Architecture#

Instead of: Order Service → calls → Inventory → calls → Email → calls → Analytics (synchronous chain - slow, coupled, one failure breaks all)

We do:

DiagramEvent-driven architecture
%% title: Event-driven architecture
flowchart TD
  OS["Order Service<br/>1. Create order<br/>2. Emit event: OrderCreated"]:::core
  OS --> BUS@{ icon: "tech:kafka", label: "Event bus (Kafka)<br/>{ #quot;type#quot;: #quot;order.created#quot;, #quot;order_id#quot;: #quot;123#quot;, #quot;total#quot;: 99.99 }" }
  BUS -.-> INV@{ icon: "sd:warehouse", label: "Inventory Service<br/>Reserve stock" }
  BUS -.-> EM@{ icon: "sd:mail", label: "Email Service<br/>Send confirm" }
  BUS -.-> AN@{ icon: "sd:chart", label: "Analytics Service<br/>Track metrics" }
  class BUS queue
  class INV,EM,AN service

Benefits:

  • ✓ Services don't know about each other (loose coupling)
  • ✓ Easy to add new consumers
  • ✓ Failure isolation
  • ✓ Event replay capability

Challenges:

  • ✗ Harder to trace request flow
  • ✗ Eventual consistency
  • ✗ Need good observability

Further Reading: Messaging#

Quick check2 questions · +5 XP each

Messaging and event-driven design

  1. Q1/2 Each uploaded image needs exactly one worker to resize it. Which messaging pattern fits?

    Answer & explanation

    Answer: C

    In a work queue each message is consumed by one consumer only, which is exactly job distribution. Pub-sub delivers to all subscribers, so every worker would resize the same image.

  2. Q2/2 A new analytics service must consume OrderCreated events and also reprocess last week's orders. Which option from the comparison supports that directly?

    Answer & explanation

    Answer: A

    Kafka keeps the event log, so a new consumer can replay old events. It also lets you add consumers without touching the producer. SQS and classic RabbitMQ queues remove a message once it is consumed, so last week's events are gone.

Chapter 5 · Stage 5 of 5 · 2 min

Chapter 5: Quick Reference#

Component Selection Cheatsheet#

Component selection cheatsheet
"I need to..." Use...
Cache static content globally CDN (CloudFront, Cloudflare)
Distribute traffic to servers Load Balancer (ALB, Nginx)
Handle auth, rate limiting for APIs API Gateway (Kong, AWS APIGW)
Route domain to servers DNS (Route 53, Cloudflare)
Store structured data + transactions PostgreSQL, MySQL
Store documents, flexible schema MongoDB, DynamoDB
Fast key-value lookups, caching Redis
Store files (images, videos) S3, GCS
Full-text search Elasticsearch, Algolia
Async task processing SQS, RabbitMQ
High-throughput event streaming Kafka
Real-time pub/sub Redis Pub/Sub

Default choices:

  • Database: PostgreSQL
  • Cache: Redis
  • Queue: SQS (AWS) or Kafka (high throughput)
  • CDN: CloudFront (AWS) or Cloudflare
  • Object Storage: S3

Typical Architecture Patterns#

DiagramTypical architecture patterns
%% title: Typical architecture patterns
flowchart TD
  subgraph RT["Real-time app"]
    direction TB
    c1["CDN"]:::edge --> c2["Load Balancer"]:::edge --> c3["WebSocket Servers"]:::core --> c4@{ icon: "tech:redis", label: "Redis Pub/Sub" } --> c5@{ icon: "tech:kafka", label: "Kafka" }
  end
  subgraph MS["Microservices"]
    direction TB
    b1["CDN"]:::edge --> b2["API Gateway"]:::edge --> b3["Load Balancers"]:::edge --> b4["Services"]:::core
    b4 --> b5@{ icon: "sd:database", label: "DBs" }
    b4 --> b6@{ icon: "tech:kafka", label: "Kafka" }
  end
  subgraph SW["Simple web app"]
    direction TB
    a1["CDN"]:::edge --> a2["Load Balancer"]:::edge --> a3["App Servers"]:::core
    a3 --> a4@{ icon: "tech:postgresql", label: "PostgreSQL" }
    a3 --> a5@{ icon: "tech:redis", label: "Redis" }
  end
  class c4,a5 cache
  class c5,b6 queue
  class b5,a4 store

Summary#

Part 2 key takeaways
Edge layer
  • CDN: Cache static content at edge, 95%+ hit ratio target
  • Load Balancer: Distribute traffic, L4 vs L7, health checks
  • API Gateway: Auth, rate limiting, routing, transformation
  • DNS: Domain resolution, geo routing, failover
Application layer
  • Keep servers STATELESS (externalize state to Redis/DB)
  • Start MONOLITH, extract microservices when needed
Data layer
  • SQL (PostgreSQL): Transactions, relationships, complex queries
  • NoSQL: Document (MongoDB), Key-Value (Redis), Column (Cassandra)
  • Caching: Cache-aside pattern, LRU eviction
  • Object Storage: S3 for files
Messaging layer
  • Queues: Async processing, decoupling, traffic buffering
  • Kafka: High-throughput event streaming
  • Event-driven: Loose coupling, failure isolation

Comprehensive Resource List#

Books#

  • Designing Data-Intensive Applications - Martin Kleppmann (Essential!)
  • Building Microservices - Sam Newman
  • System Design Interview - Alex Xu
  • Web Scalability for Startup Engineers - Artur Ejsmont

Online Resources#

Documentation#

this lesson in shorts · 2

play them all →

One idea from this lesson in about a minute each, made for your phone.

End of Part 02 · Part 2: Infrastructure Building Blocks
Finish Part 02Stages clear as you read them (0/5 so far). Skipping ahead doesn't count: marking complete unlocks after ~6 more min of reading.

Go deeper · reading trail

all reading →

Real write-ups from the teams that built these systems. Each opens on the publisher's site in a new tab.

GoogleDocs · ~13 min

Load Balancing at the Frontend

Piotr Lewandowski, Sarah Chavis · 2016
What
A chapter of Google's Site Reliability Engineering book on how user traffic reaches the right datacenter.
Key idea
DNS load balancing picks a region but caches and resolvers blur it, so a virtual IP and network load balancer spread traffic inside the datacenter.
Why here
It shows DNS and the load balancer, two building blocks from this part, working as one layered traffic-routing system.
taught here: Load balancer
ShopifyBlog post · ~12 min

Deconstructing the Monolith: Designing Software that Maximizes Developer Productivity

Kirsten Westeinde · 2019
What
Shopify explains why it kept its Rails monolith and restructured it instead of splitting into microservices.
Key idea
A modular monolith with enforced component boundaries keeps one deployable while getting much of the isolation teams want from services.
Why here
A real counterweight to 'microservices by default' in the monolith vs microservices section: the boundary matters more than the network hop.
taught here: Monolith vs microservices
SegmentBlog post · ~15 min

Goodbye Microservices: From 100s of problem children to 1 superstar

Alexandra Noonan · 2018
What
Segment's story of splitting its event-delivery pipeline into one service per destination and then merging them back.
Key idea
Per-destination services multiplied queues, shared-library drift and on-call load, and one well-tested service turned out simpler and faster.
Why here
Shows the operational cost side of the microservices trade-off taught here, with the numbers that pushed a team back to a monolith.
taught here: Monolith vs microservices

New lessons by email

New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.

Discussion · …

Public: everyone reading this lesson sees comments. Be kind and keep it on the material.

Sign in to ask a question or answer one. Reading stays open to everyone.
loading the discussion…