Caching Layers Demystified
The caching layers in a modern web app — CDN, HTTP, in-process, distributed — and how to pick correctly.
Caching is a stack, not a single knob. The right place to cache depends on what you are caching and who can invalidate it.
The layers, from client to database
- Browser cache — controlled by
Cache-ControlandETagheaders. - CDN — CloudFront, Cloudflare, Fastly. Global edge.
- Reverse proxy — nginx, Varnish, HAProxy in front of your origin.
- Application in-process — LRU inside the Node/Go/Python process.
- Distributed cache — Redis, Memcached, Dragonfly.
- Database — Postgres shared_buffers, MySQL query cache, materialized views.
What to cache where
| Data | Best layer | Reason |
|---|---|---|
| Static assets (JS, CSS, images) | CDN + long Cache-Control | Content-hashed URLs — cache forever. |
| Public HTML pages | CDN with short TTL + stale-while-revalidate | Fresh enough, blazing fast. |
| Per-user API responses | Redis + short TTL | Skip re-computing personalized data every request. |
| Expensive DB queries | In-process + Redis (two levels) | Hit in-process first; miss → Redis; miss → DB. |
| Session data | Redis with expiration | Fast, shared across app instances. |
The invalidation problem
There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton
Practical patterns that work:
- Short TTLs — for most data, 30–120 seconds is invisible to users and eliminates 90% of invalidation bugs.
- Explicit invalidate on write — for user-visible data, delete/purge the cache key in the same code path as the write.
- Cache tags — group related keys so you can purge a set with one call (Cloudflare supports this natively).
What NOT to cache
- Authentication decisions.
- Data with regulatory freshness rules (banking balances, medical records).
- Anything you cannot invalidate atomically when it changes.