Skip to the chapter
←System DesignGGroundworkSDSystem Design
System Design/Intermediate/I2
9 / 36 written chapters
/

The cover
  1. B1The mental model
  2. B2Client-server basics
  3. B3Vertical vs horizontal
  4. B4Databases in design
  5. B5Caching fundamentals
  6. B6APIs & communication
  7. B7Walkthrough: URL shortener
  1. I1Load balancing in depth
  2. I2CDNs
  3. I3Message queues & async
  4. I4Consistency models
  5. I5Replication
  6. I6Rate limiting & throttling
  7. I7Designing for availability
  8. I8Storage systems
  9. I9Search systems
  10. I10Microservices
  11. I11Security basics
  12. I12Walkthrough: feed/chat
  13. I13Walkthrough: notifications
  1. A1CAP theorem in depth
  2. A2Transactions and isolation
  3. A3Storage engines
  4. A4Sharding at scale
  5. A5Time, ordering and IDs
  6. A6Distributed consensus
  7. A7Batch & stream processing
  8. A8Fault tolerance
  9. A9Observability at scale
  10. A10Release safety
  11. A11Capacity estimation
  12. A12Case studies
  13. A13Walkthrough: key-value store
  14. A14Walkthrough: web crawler
  15. A15Walkthrough: payments
  16. A16Tradeoff thinking

SDI2 · Part 2 · Intermediate

CDNs

Move the bytes closer to the user — the cheapest 10x in system design, right up to the moment you cache something personal.

23 min read2 diagrams11 sections0 exercises
9 of 36

In this chapter

  • A CDN is a cache you don't operate, sitting where the latency is
  • How the request finds the nearest edge: anycast and DNS steering
  • Push vs pull: who decides what lives at the edge
  • Cache-Control: the directives and what they actually do
  • ETags and revalidation: paying for freshness in bytes, not seconds

and 6 more

about 23 min
11On this page
  1. A CDN is a cache you don't operate, sitting where the latency is
  2. How the request finds the nearest edge: anycast and DNS steering
  3. Push vs pull: who decides what lives at the edge
  4. Cache-Control: the directives and what they actually do
  5. ETags and revalidation: paying for freshness in bytes, not seconds
  6. Choosing a TTL: the question is "how stale is tolerable", not "how fresh can I be"
  7. Cache key design, and the bug that ends careers
  8. Invalidation: purge is the fallback, versioned URLs are the design
  9. Edge compute: the CDN stops being read-only
  10. When a CDN is a 10x win, and when it changes nothing
  11. Recognizing it in an unseen problem

A CDN is a cache you don't operate, sitting where the latency is

You already know caching: put a copy of an expensive result somewhere faster than recomputing it. A CDN applies that idea to the one cost you cannot optimise away in your own datacentre — the speed of light. A round trip from Singapore to us-east-1 is ~200-230 ms on fibre and no amount of Redis fixes it. A CDN is a network of PoPs (points of presence — racks of caching proxies in ~100-600 metros depending on the vendor) that terminate the user's TCP and TLS locally and serve the response from a cache 10 ms away.

Interviewers ask about CDNs because it is the fastest read on whether you think about where a system runs, not just what it does — and because the follow-up ("what happens when a logged-in user's page gets cached?") separates people who have configured a CDN from people who have heard of one.

client edge PoP Singapore origin us-east-1 8 ms RTT only on MISS +~210 ms RTT HIT: ~12 ms to first byte — TLS terminated locally, warm connection MISS: ~230 ms — and ~3 round trips (~630 ms) if the origin link is cold at 95% hit rate the weighted average is ~23 ms (0.95 × 12 + 0.05 × 230), and origin sees 1/20th the traffic
The interesting number is not the hit latency, it's the hit rate — everything in this chapter is a lever on that one percentage. The latencies are illustrative planning figures: Singapore to us-east is roughly 200-230 ms round trip, a nearby edge 5-30 ms.

How the request finds the nearest edge: anycast and DNS steering

There are two mechanisms and good candidates name both.

  • Anycast. The CDN announces the same IP prefix via BGP from every PoP. Internet routers each pick the topologically shortest path to that prefix, so a packet addressed to 104.16.0.1 lands in Frankfurt from Berlin and in São Paulo from Rio, with no application logic involved. Failover is a BGP withdrawal: pull the announcement from a PoP and traffic re-converges elsewhere in seconds. The catch is that anycast is stateless routing — a mid-connection path change can land packets at a different PoP, which is why anycast TCP needs careful ECMP consistency (in practice all major CDNs solve this).
  • DNS-based steering (unicast). The authoritative DNS server returns a different A record per resolver location, using EDNS Client Subnet to see the real user prefix rather than the resolver's. More control (you can weight by PoP load or cost), but it inherits DNS TTL latency: draining a PoP takes as long as the longest cached TTL, so these deployments run 20-60 s TTLs and pay the extra lookups.

Vendors differ: Cloudflare and Fastly lean on anycast, while AWS documents CloudFront as using DNS to route each viewer to the best edge location. Either way there is layer-4 load balancing inside the PoP. Say "anycast for the coarse routing, DNS or GeoIP steering when I need per-PoP control" and you have covered both families without claiming that any one vendor does everything.

Say it like this → "The edge terminates TLS ~8 ms from the user instead of ~210 ms away. Even for a full miss that's a win, because the handshake round trips happen locally and the edge keeps a warm, tuned connection pool to origin — I'm paying one long RTT instead of three."

The handshake arithmetic behind that claim: a brand-new TCP connection plus a TLS 1.3 handshake costs two round trips before the first request byte goes out, and the request itself is a third. To an origin 210 ms away that is about 630 ms before the first byte arrives; to an edge 8 ms away it is about 24 ms. HTTP/3 over QUIC folds the transport and TLS handshakes into one round trip, and resumed sessions can send data at once (0-RTT), which shrinks the gap for returning users but not the physics of an uncached miss.

Push vs pull: who decides what lives at the edge

 Pull (origin-pull)Push
Who populatesFirst user to request an object; edge fetches and stores itYou upload objects to the CDN ahead of time, usually in CI
Cost of a cold objectOne user eats the full origin RTT; N PoPs means up to N cold fetches unless there's a shield tierZero — it's already there
Storage costOnly what's actually requestedEverything, in every region, whether requested or not
Operational loadNear zero — set headers and point DNS at itA deploy step that can fail, plus lifecycle management
Reach for this whenAlmost always. It's self-tuning: popular objects are cached, the long tail isn'tLarge, predictable, launch-critical objects — a game patch, a video catalogue, a Super Bowl ad you cannot afford to miss on

The honest answer in an interview is "pull, with an origin shield." A shield is a designated mid-tier PoP that all other PoPs miss through, so a cold object costs one origin fetch globally instead of one per PoP. Vendors name it differently (Cloudflare's tiered cache, CloudFront's regional edge caches, which AWS puts between the PoPs and your origin). Edges also collapse concurrent misses for the same object into a single origin fetch (request collapsing). On a network with hundreds of PoPs that is the difference between a launch and an origin outage.

Cache-Control: the directives and what they actually do

DirectiveWhat it actually doesThe part people get wrong
max-age=NFresh for N seconds in any cache, including the browserOnce sent, you cannot recall it from a browser. Deploying a bad 1-year max-age is unfixable without changing the URL
s-maxage=NSame, but only for shared caches (CDN, proxy). Overrides max-age thereLooks like the lever you want (max-age=0, s-maxage=600: browsers revalidate, CDN absorbs the load), but RFC 9111 says it also carries the semantics of proxy-revalidate, so a shared cache may not serve it stale. Cloudflare documents that it disables stale-while-revalidate and stale-if-error. Prefer a CDN-targeted header (below)
publicCacheable by shared caches even when the request had an Authorization headerSetting this on an authenticated endpoint is exactly how personalised pages leak
privateBrowser may cache; shared caches must notIt is not a security control — it's a hint. Don't rely on it for secrets
no-cacheStore it, but revalidate with the origin before every reuseDoes not mean "don't cache". That's no-store. This is the single most common misreading in the whole spec
no-storeCaches must not store the response or reuse it for other requestsCorrect for account pages and API responses with PII; wasteful everywhere else. RFC 9111 itself says it is not a reliable or sufficient privacy mechanism, so it never replaces authentication and per-user keys
must-revalidateOnce stale, you may not serve it — even if the origin is downTurns an origin outage into a user-visible outage. Usually you want the opposite
stale-while-revalidate=NFor N seconds past expiry, serve the stale copy immediately and refresh in the backgroundThe highest-value directive in the table and the most under-used — it decouples freshness from latency. It needs a request to arrive to trigger the refresh
stale-if-error=NIf revalidation hits an error (RFC 5861: a 500, 502, 503 or 504; vendors also treat an unreachable origin that way), keep serving the stale copy for N seconds past expiryFree availability. Your CDN becomes a static failover for the whole site. Support and error definitions vary by vendor, so test it
immutableDon't even conditionally revalidate on a user reloadOnly safe with content-hashed filenames. Pair with max-age=31536000
Vary: HAdds request header H to the cache keyVary: User-Agent shatters your hit rate into thousands of fragments. Vary: Accept-Encoding is fine (3 values)
The three header sets worth memorising
  • Hashed static asset (/app.9f2c1a.js): Cache-Control: public, max-age=31536000, immutable
  • Shared HTML or a hot read API: Cache-Control: public, max-age=60, stale-while-revalidate=600, stale-if-error=86400 (CloudFront documents exactly this max-age plus SWR plus SIE form). If you want browsers to revalidate while only the CDN holds the copy, send the CDN its own directives with the RFC 9213 CDN-Cache-Control header (or a vendor equivalent such as Surrogate-Control) and keep Cache-Control: max-age=0 for browsers. Do not reach for s-maxage here, and check each vendor's stale-handling docs: they differ.
  • Anything user-specific: Cache-Control: private, no-store — and check that no CDN rule overrides it

Dry run: one URL's cache state through a stale-while-revalidate and stale-if-error cycle

/api/products/42 is served with Cache-Control: public, max-age=60, stale-while-revalidate=600, stale-if-error=86400, and the edge applies these directives with RFC 5861 semantics: both stale windows are counted from the moment the entry expires, not from the last fetch.

TimeEventEdge cache stateResponse servedWhy
t = 0sFirst requestMISSFetch origin, store, return 200 — origin RTT paid onceNothing cached yet
t = 30sSecond requestFresh (age 30s ≤ 60s)200 straight from the edge, no origin callInside the max-age window
t = 65sThird requestStale (expired 5s ago; the SWR window runs 60s to 660s)200 from the edge immediately, background revalidation firedInside the stale-while-revalidate window — the user never waits on origin
t = 66sBackground revalidation completesFresh again (age resets to 0, so the entry now expires at t = 126s)No client-visible response — this refresh happened off the request pathThe SWR fetch from t=65s just landed
t = 8,000sRequest arrives; origin is down for maintenanceStale (expired at t = 126s, so 7,874s past expiry); SWR window long over; the revalidation fails200, serving the stale copy anyway7,874s < 86,400s — inside the stale-if-error window (counted from expiry), so a failed origin still gets a stale-but-valid response
t = 90,000sRequest arrives; origin still downStale, 89,874s past expiryThe origin error (for example a 503) is passed to the client89,874s > 86,400s — the stale-if-error window has expired, so there is nothing left to fall back to

Three directives, three different jobs: max-age bounds normal staleness, stale-while-revalidate hides a slow refresh behind a stale response instead of making the user wait, and stale-if-error turns a dead origin into a non-event for up to a full day past expiry before it finally becomes the client's problem. (CloudFront also caps all of this at the distribution's maximum TTL, so a vendor setting can end the window sooner than the header.)

ETags and revalidation: paying for freshness in bytes, not seconds

When an object goes stale the edge does not have to re-download it. It sends a conditional request — If-None-Match: "9f2c1a" — and the origin replies 304 Not Modified with an empty body if the ETag still matches. You save the payload (often 99% of the bytes) but you still pay the full origin round trip, so a 304 costs the same latency as a 200. That is precisely the gap stale-while-revalidate closes.

  • Strong ETag — byte-for-byte identity, required for range requests and resumable downloads. Usually a content hash.
  • Weak ETag (W/"abc") — semantically equivalent. Right choice when gzip levels or a timestamp comment make bytes differ while meaning doesn't.
  • Last-Modified / If-Modified-Since — 1-second granularity, and it lies whenever a deploy rewrites files without changing content. Prefer ETags; keep Last-Modified as a fallback.
⚠ ETags computed per-server break behind a load balancer If your ETag is derived from the file's inode or mtime (nginx's default is mtime-size), two app servers holding identical content emit different ETags. Every revalidation then misses, the CDN re-downloads, and you have quietly built a cache that never hits. Derive the ETag from a hash of the content or from the build ID — something all replicas agree on.

Choosing a TTL: the question is "how stale is tolerable", not "how fresh can I be"

ContentTTLReasoning
Hashed JS/CSS/fonts1 year, immutableThe URL changes when the bytes change, so staleness is impossible by construction
Un-hashed images, PDFs1-7 days at the CDN, minutes in the browserYou can purge the CDN in seconds; you cannot purge browsers
Marketing / docs HTMLCDN TTL 300 s + stale-while-revalidate=86400Editors expect changes within minutes, not instantly; SWR means nobody ever waits for the refresh
Product listing / search resultsCDN TTL 10-60 s + stale-while-revalidate60 s of staleness on a catalogue is invisible to users and removes 99% of origin reads on a hot query
Price, inventory countCDN TTL 0-5 s, or don't cacheWrong price is a business incident. Cache the page shell, fetch the number client-side
Anything per-userno-store at the CDNCache key cardinality equals user count — the hit rate is ~0 even if it were safe

A useful reframing under pressure: TTL is a budget for how wrong you are willing to be, multiplied by how often the data changes. A 60-second TTL on data that changes hourly is nearly pointless conservatism; a 60-second TTL on data that changes every 200 ms is a deliberate, correct decision to serve approximate data fast.

Cache key design, and the bug that ends careers

The cache key is what the edge hashes to decide "have I seen this request before?" By default it is roughly scheme + host + path + full query string, plus whatever Vary adds. Two failure modes sit on either side of getting it right.

shop.example /products/list ?page=2&sort=price Vary: Accept- Encoding cache key = safe: nothing here identifies a person, so every visitor shares one entry GET /account/orders Cookie: session=alice… Set-Cookie in response cookie NOT in the key, response marked public Bob requests the same path, hits Alice's cached entry, and reads her order history. One request poisons the entry for every user in that PoP until the TTL expires.
The green row is a good key: coarse enough to be shared, precise enough to be correct. The red row is the class of bug behind Valve's Steam incident in December 2015, when an aggressive caching change served some users' store pages (account details included) to other users.
⚠ The classic: caching a personalised response It needs three things to line up, and they line up depressingly often: a response that varies by user, a cache key that ignores the thing identifying that user (cookie, Authorization header), and a Cache-Control that permits shared caching (or an origin that omits the header entirely and lets the CDN apply a default TTL). The defence is layered: default to private, no-store on every authenticated route; put the CDN in "cache nothing unless the origin explicitly opts in" mode; and add a synthetic test that logs in as user A, then requests the same URL as user B and asserts the response does not contain A's data.

Going the other way, an over-precise key destroys your hit rate. Keys should be normalised before hashing:

  • Sort and allow-list query params. ?sort=price&page=2 and ?page=2&sort=price must hash the same, and tracking params like utm_source, fbclid, gclid must be stripped — otherwise every ad click creates a unique, permanently-cold cache entry.
  • Never key on the whole Cookie header. Key on a derived value instead: a boolean logged-in, or a plan tier. Two variants, not two million.
  • Collapse device classes. Bucket User-Agent into mobile|desktop|bot at the edge and vary on that, never on the raw string.
  • Lowercase the host, drop the default port, canonicalise trailing slashes — free hit-rate.

Invalidation: purge is the fallback, versioned URLs are the design

The strongest thing you can say here is that you avoid invalidation rather than optimise it. Content-addressed URLs (/static/app.9f2c1a.js, emitted by the bundler) make the problem disappear: new bytes get a new URL, the old URL stays valid forever, and rollback is just re-pointing the HTML. The HTML itself is the only short-TTL object in the system, and it is small.

MechanismPropagationReach for this when
Versioned / hashed URLInstant, by constructionDefault for every build artefact — JS, CSS, images, fonts
Purge single URLSub-second to seconds globally (Cloudflare reports single-file purge p50 around 234 ms); budget tens of seconds for slow or tiered setupsOne asset was published wrong; a legal takedown
Surrogate-key / cache-tag purgeSub-second to a few seconds on modern CDNs (Cloudflare reports a global p50 under 150 ms; AWS reports CloudFront tag invalidation under 5 s at p95)The real tool for dynamic sites: tag every response with the entity IDs it contains (Surrogate-Key: product-42 category-9) and purge by tag when the entity changes. One product edit invalidates exactly the pages that mention it
Purge everythingSeconds, then a stampedeAlmost never. Every PoP simultaneously misses, origin takes roughly 1 ÷ (1 − hit rate) times its normal read load (20x at 95%, 100x at 99%), and you find out whether your database was ever really sized for it
Say it like this → "I'd tag responses with surrogate keys for the entities they render, and purge by tag on write. That gives me long TTLs — minutes, not seconds — with event-driven freshness, so the hit rate stays above 95% and edits still show up immediately. Full purges I'd treat as an incident tool, because they turn the CDN into an origin stampede."

Edge compute: the CDN stops being read-only

Every major CDN now runs your code in the PoP, but the runtimes are not interchangeable. CloudFront Functions are tiny JavaScript (ES 5.1) handlers with sub-millisecond duration, 2 MB of memory and no network access. Lambda@Edge is a full Node.js or Python function: up to 30 seconds, network access, 128 MB (viewer events) or up to 10 GB (origin events), and a container start that can add real latency. Cloudflare Workers run in V8 isolates with very fast starts; CPU time is capped at 10 ms on the free plan and defaults to 30 seconds (max 5 minutes) on the paid plan, with 128 MB per isolate. Fastly Compute runs WebAssembly. Pick by what the code must do, then read that vendor's current limits. Used well, edge code converts several origin round trips into edge-local work:

  • Cache key rewriting. Normalise the query string, bucket the A/B variant, downgrade a cookie to a boolean — the normalisation rules above, implemented rather than configured.
  • Auth at the edge. Verify a JWT signature locally and reject unauthenticated traffic 200+ ms (for a distant user) before it would have reached your origin. Revocation still needs an origin check, so keep tokens short-lived.
  • Personalisation without breaking the cache. Cache one shared, anonymous HTML shell aggressively, then have the Worker stitch in the per-user fragment (name, cart count) from a KV lookup. You get a cacheable page and a personalised one.
  • Geo / consent routing, redirects, signed URLs, bot scoring — all decisions that only need the request itself.

Be honest about the limits: edge runtimes have tight memory and CPU budgets, and eventually-consistent edge KV. They can open outbound TCP (Workers have connect(), and Cloudflare's Hyperdrive pools Postgres connections), but every query still pays the RTT from the PoP back to the database, and a chatty request that makes four of them at 200 ms each eats the entire win. Edge compute is for decisions about the request, not for business logic that needs your transactional store.

When a CDN is a 10x win, and when it changes nothing

WorkloadEffectWhy
Static assets, images, video, downloadsEnormous95-99% offload; origin egress drops 20-100x. CDN per-GB prices are often close to cloud egress list prices, so the saving is mostly offload (plus free or discounted origin-to-CDN transfer; AWS waives it for AWS origins)
Mostly-anonymous read pages (news, docs, catalogue, marketing)LargeOne cached copy serves millions; the long tail is where hit rate goes, so watch p50 not just totals
Read-heavy public API (prices, timetables, feature flags)Real, with 5-60 s TTLsThe traffic is spiky and repetitive — exactly what a cache is for
Highly personalised API (/me/feed)~Nothing on cacheabilityKey cardinality equals user count. You still get TLS termination and a warm origin connection — worth maybe 30-50% off TTFB, not 10x
Write-heavy endpoints (checkout, messaging, uploads)Nothing, by definitionIn practice CDNs cache only GET and HEAD responses. A CDN can still shed abusive traffic and terminate TLS, but it is not a scaling story
Real-time, sub-second freshness (live trading, presence)Nothing, and it can hurtAny TTL is too long; caching here converts a latency problem into a correctness problem

The load-bearing metric in all six rows is the same: hit rate. Origin load scales with the miss rate: a 50% hit rate halves origin load (real, but not a 10x story); 90% removes 10x, 95% removes 20x and 99% removes 100x. Track both the request hit ratio and the byte hit ratio, because a few huge uncached video segments can dominate egress while the request ratio looks healthy, and use the standard Cache-Status response header (RFC 9211) to see which cache in the chain served a response. When an interviewer asks "how would you know it's working", the answer is hit rate by content type and origin egress per user — not "latency looks better."

Recognizing it in an unseen problem

  • Signals: "global users", "millions of images", "video streaming", "our site is slow in India", or a read:write ratio quoted above about 20:1. Any of those and the CDN belongs in your first drawing, before you touch the database.
  • The naive design puts one app tier in one region behind a load balancer and tries to fix the 200 ms of physics with more replicas. Replicas cut queue time; they cannot cut propagation delay.
  • Distinguishing it from application caching: a CDN caches HTTP responses keyed by URL, near the user; Redis caches arbitrary values keyed by anything, near the service. If the thing you want to reuse isn't addressable by URL, or is per-user, it belongs in Redis — this is a layered-cache question, not an either/or.
  • Estimate before you commit. 10 M daily users × 2 MB of assets ≈ 20 TB/day. If every byte left your origin at a list price near $0.09/GB, that's roughly $1,800/day. Browser caching removes some of those requests first; at 95% CDN offload only about 1 TB/day is fetched from your origin, and that origin-to-CDN leg is often discounted or free. Cost is a legitimate reason to reach for a CDN and it scores well, but credit the offload, not a cheaper per-GB rate.
  • The pitfall to name unprompted: personalised content in a shared cache. Say the words "I'd default authenticated routes to private, no-store and make the CDN opt-in rather than opt-out" and you have pre-empted the follow-up question.
  • Two named attacks to know. Web cache deception tricks a cache that decides by file extension into storing a victim's private page under a static-looking path (/account/orders/x.css); cache poisoning abuses request inputs that affect the response but are not in the key. Defences: honour the origin's Cache-Control, mark dynamic responses private/no-store, and do not cache by extension alone. Also worth a sentence: signed URLs or cookies for paid or private media, negative caching (a short TTL on 404s), range or segment caching for video, and multi-CDN for resilience.
  • The second pitfall: full purges and cold-start stampedes. If the design has a global purge in it, pair it with a shield tier or staggered TTLs, or say out loud that the origin must be sized for the miss storm.
Before you move on
  • Explain the difference between no-cache and no-store from memory, without looking it up.
  • Trace how stale-while-revalidate and stale-if-error handle a slow or down origin for one URL, the way the dry run above does.
  • Explain why an over-precise cache key (keyed on the raw Cookie header, or an unstripped query string) destroys hit rate, and how you'd fix it.
  • Describe the cache-key bug that leaks one user's personalised response to another, and the two-layer defence against it.
  • Explain why versioned or hashed URLs are the default invalidation strategy, with purge as the fallback rather than the plan.
  • Explain why s-maxage and stale-while-revalidate do not combine well, and how both stale windows are counted from expiry.
Sources and further reading
  • RFC 9111: HTTP Caching — s-maxage implying proxy-revalidate, the no-store caveat, and no-cache semantics
  • RFC 5861: stale-while-revalidate and stale-if-error — stale windows counted from expiry, and the error definition
  • RFC 9213: Targeted HTTP Cache Control — the CDN-Cache-Control header for CDN-only directives
  • BleepingComputer: Steam caching error — the December 2015 incident behind the personalised-cache warning
  • Cloudflare: Cache-Control behaviour — how s-maxage interacts with stale-while-revalidate and stale-if-error
  • AWS: CloudFront expiration and serving stale content — max-age with SWR and SIE, and the maximum TTL cap
  • AWS: how CloudFront delivers content — DNS-based routing and regional edge caches
  • Cloudflare Workers limits — CPU time, memory and subrequest limits
  • AWS: CloudFront Functions versus Lambda@Edge — duration, memory, language and network differences
  • Cloudflare: instant purge — reported purge latency, including tag purge
  • AWS: CloudFront cache-tag invalidation — tag-based invalidation and its reported propagation time
  • PortSwigger: web cache deception — how private pages end up in shared caches

Claims in this chapter were checked against these sources in October 2026. Figures marked "planning figure" are rules of thumb, not guarantees.

✓Practise

5 questions drawn from 8, scored out of 5. This is practice: it runs in your browser, saves nothing and does not change what you have marked read.

I2

Finished reading?

0 of 36 chapters read in this series

→Next stop

  1. I1Previous · I1Load balancing in depth
  2. I2you are here
  3. Next · I3Message queues & async processingThe request path should only do the work the user is actually waiting for — everything else goes on a queue and gets retried.20 min read

01On this page

11 sections · 0%

  1. 01A CDN is a cache you don't operate, sitting where the latency is
  2. 02How the request finds the nearest edge: anycast and DNS steering
  3. 03Push vs pull: who decides what lives at the edge
  4. 04Cache-Control: the directives and what they actually do
  5. 05ETags and revalidation: paying for freshness in bytes, not seconds
  6. 06Choosing a TTL: the question is "how stale is tolerable", not "how fresh can I be"
  7. 07Cache key design, and the bug that ends careers
  8. 08Invalidation: purge is the fallback, versioned URLs are the design
  9. 09Edge compute: the CDN stops being read-only
  10. 10When a CDN is a 10x win, and when it changes nothing
  11. 11Recognizing it in an unseen problem

[] chapters · t top