#Why cache, and what to cache
A cache is a copy of a result kept somewhere faster than where it came from, so that the next request for the same thing can skip the expensive part. The expensive part is usually I/O you're waiting on: a database query, a call to another service, an external API with per-call pricing — or CPU work like rendering or a heavy aggregation. The cache trades freshness and memory for latency and load.
My notes start with "Read about API caching in nodejs". The core idea fits in one sentence: caching pays off when the same result is read much more often than it changes, and when serving a slightly old copy is acceptable. A product page read 2,000 times a second and edited twice a day is a perfect candidate; a bank balance shown just before a transfer is not.
The numbers show why a good hit ratio matters so much:
const requestsPerSec = 2_000;
const dbMs = 20; // a database read
const cacheMs = 1; // a Redis GET on the same network
for (const hitRatio of [0, 0.5, 0.9, 0.99]) {
const dbReadsPerSec = Math.round(requestsPerSec * (1 - hitRatio));
// a miss pays for the cache lookup AND the database read
const avgMs = hitRatio * cacheMs + (1 - hitRatio) * (cacheMs + dbMs);
console.log(`hit ratio ${String(hitRatio * 100).padStart(2)}% db reads/s ${String(dbReadsPerSec).padStart(4)} avg latency ${avgMs.toFixed(1)} ms`);
}
// hit ratio 0% db reads/s 2000 avg latency 21.0 ms
// hit ratio 50% db reads/s 1000 avg latency 11.0 ms
// hit ratio 90% db reads/s 200 avg latency 3.0 ms
// hit ratio 99% db reads/s 20 avg latency 1.2 ms- Every request either hits (served from the cache in 1 ms) or misses (pays the 1 ms lookup, then the 20 ms database read). Only misses reach the database.
- At a 0% hit ratio the cache is pure overhead: 21 ms instead of 20, and the full 2,000 reads per second still hit the database.
- At 90% the database sees 200 reads per second — a 10× reduction — and average latency drops to 3 ms. At 99% it's 20 reads per second, a 100× reduction.
- The jump from 90% to 99% cuts database load by another 10×, which is why cache keys, TTLs and warm-up get tuned so carefully. And notice what the average hides: the 1% of misses still take 21 ms, so p99 latency barely improves (see What to measure).
Caches exist at every layer, and the ones closest to the user save the most:
| Layer | Example | What it saves |
|---|---|---|
| Browser | Cache-Control: max-age=…, ETag | The whole request, or the body bytes (HTTP caching headers) |
| CDN / reverse proxy | Cloudflare, CloudFront, nginx | Requests ever reaching your servers |
| In-process memory | a Map, lru-cache | A network hop to Redis and the DB |
| Shared cache | Redis, Memcached | Database and downstream-service load |
| Database | its own buffer pool and query plans | Disk reads (you don't manage this one) |
What to cache, and what not to:
- Good candidates: read-heavy, slow-changing data (product catalogues, configuration, feature flags, exchange rates refreshed every minute), expensive computed results (aggregations, rendered fragments), and responses from slow or rate-limited third-party APIs.
- Be careful with: anything per-user or per-tenant (the key must include that scope — see Cache invalidation and cache keys), and data whose staleness has a cost you can name.
- Don't cache: values used for security or money decisions at the moment of the decision (permissions during an authorisation check, stock during checkout, balances during a transfer), one-off results nobody reads twice, and errors — a cached failure turns a 1-second blip into a 30-second outage.