How and when do you cache in a Node application?
3 minintermediatenodejscachingredisperformanceinvalidation
Quick Answer
Cache expensive or frequently-read, infrequently-changing data to cut latency and load. Options range from in-process memory (fast but per-instance and volatile) to a shared store like Redis (consistent across instances). The hard part is invalidation — use TTLs and explicit invalidation on writes.
Detailed Answer
Answer:
Why cache: avoid repeating expensive work — DB queries, external API calls, heavy computation — for data that's read often and changes rarely.
Layers of caching:
- In-process memory (a
Maporlru-cache): fastest, zero network hop.- ❌ Not shared across instances; lost on restart; can bloat memory. Fine for small, hot, per-instance data.
- Distributed cache (Redis/Memcached): shared across all instances, survives restarts, supports TTLs and rich structures.
async function getUser(id) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.getUser(id);
await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 300); // 5 min TTL
return user;
}
- HTTP caching (Cache-Control/ETag) and a CDN for static or public responses — offload work entirely.
Invalidation — the hard part:
- TTL/expiry — simplest; accept slightly stale data for a bounded window.
- Write-through / explicit invalidation — delete or update the cache key when the underlying data changes.
async function updateUser(id, data) {
const user = await db.updateUser(id, data);
await redis.del(`user:${id}`); // invalidate on write
return user;
}
Watch out for:
- Stale data — pick TTLs that match tolerance for staleness.
- Cache stampede — many misses hitting the DB at once when a hot key expires (mitigate with locks/"single-flight" or jittered TTLs).
- Don't cache per-user sensitive data in shared/public caches.
Rule: cache read-heavy, change-rarely data; always have an invalidation strategy (at minimum a TTL).