Cache Invalidation & TTL
Staleness is a trade, not a bug
TTLs decide how long stale-but-accepted content lives; invalidation is the emergency brake that forces fresh checks. Versioned assets make long TTLs safe; unversioned paths make invalidation a daily ritual.
What you will learn
- Model how TTL, staleness and revalidation interact
- Choose between purge/invalidation and cache-busting filenames
- Compose Cache-Control directives (max-age, s-maxage, must-revalidate, immutable)
The edge trusts Cache-Control. max-age sets how long a hit is served without asking; s-maxage overrides it for shared caches (CDNs); must-revalidate forces revalidation before reuse even on stale; immutable lets middle hops skip revalidation for hashed assets. Invalidation purges cached copies so the next fetch is a miss and refreshes from origin.
What is this?
Cache-Control directives: public, private, no-store (auth/personal), max-age=<s>, s-maxage=<s>, stale-while-revalidate, immutable, must-revalidate. Edge behavior: fresh (serve), stale + revalidate (ask origin w/ If-None-Match/If-Modified-Since → 304 → serve + refresh TTL), expired hard (miss). Invalidation: CloudFront create-invalidation, Front Door cache purge, Cloud CDN invalidateCache — costs and propagation vary. Cache busting: hash in filename (app-1a2b3c.js) → infinite TTL, never needs purge; no-store for dynamic routes.
Why do you need it?
- Wrong TTLs are the #1 'we changed it but users still see the old version' outage class.
- Purging entire distributions on every deploy is slow and expensive; versioning avoids it.
- Revalidation (304s) keeps freshness cheap instead of full origin re-downloads.
- Stale-while-revalidate hides origin latency behind freshly-aging caches.
Mental model
Bakery selling bread with a best-before date
TTL on cached responses
Happy customers take the loaf within the date (max-age); a few minutes past it, staff re-check the kitchen before selling (stale revalidation); truly expired bread is thrown out and re-baked (cache miss → origin).
How it works
- Origin sets Cache-Control on each response; the edge records freshness age.
- While age < max-age the edge serves without contacting origin.
- At expiry, edge revalidates: If-None-Match → 304 (fresh copy, keep bytes) or 200 (new bytes).
- Invalidation marks the object for immediate purge; the next request is a miss.
- Versioned assets let you set long TTLs and avoid blocking invalidations entirely.
Edges of staleness.
Examples
Production-grade cache headers
# hashed bundle → cache forever-ishCache-Control: public, max-age=31536000, immutable # index.html → short TTL so deploys propagate quicklyCache-Control: public, max-age=300, s-maxage=60, must-revalidate # API user profile → never cache at the edgeCache-Control: private, no-storeOne header per content class: immutable for versioned blobs, short revalidating TTL for the entry HTML, no-store for anything personalized.
In production
- Deploy pipeline writes hashed bundles with immutable headers; index.html gets a 5-minute TTL.
- E-commerce images use max-age=86400 + signed invalidation on price/render updates.
- News APIs use stale-while-revalidate so spikes read slightly-stale but never queue on origin.
Best practices
- Set explicit cache metadata at the source (S3 upload, build step) — don't patch it in the CDN.
- Prefer versioned URLs over mass invalidation; reserve purges for rollbacks and corrections.
- Use s-maxage to differ edge vs browser caching on the same object.
- Automate invalidation in the deploy pipeline, gated on the exact changed paths.
- Test with curl after each release: x-cache and Age reflect the intended freshness.
Common mistakes
- max-age=0 with nothing else → cacheable but revalidates constantly, hammering origin.
- Forgetting s-maxage/cache key variations so A/B variants serve each other's bytes.
- Invalidating 100k paths when five hashed filenames changed (slow + hot).
- Letting <meta http-equiv> or URL hacks (&nocache) do the job of real headers.
Debugging tips
- curl -sI: Age grows every second a hit is served; Age: 0 on every call ⇒ always missing.
- Look for a 304 not cache hit on repeat calls — origin is still being hit, just cheaply.
- Inspect the actual Cache-Control the origin returns with curl before blaming the edge.
- Vary / Content-Length mismatches between edge and origin points to corrupted cached bytes.
Interview checkpoint
Hands-on lab
- Serve the same object with different Cache-Control values
- Observe fresh, stale, and revalidated states in real-time
- Understand what a CDN can and cannot do without proper headers
Measure Cache-Control Without an Exploding Cache
30 min · beginner
Quick quiz
Which header is ignored by CDNs (they use s-maxage instead)?
A 304 revalidation response lets the edge reuse its cached copy and only refreshes validity.