Caching Checklist
The checklist
HTTP caching
- Public endpoints have
Cache-Control: public, max-age=N - User-specific endpoints use
privateorno-store - ETags enable conditional requests (304 Not Modified)
-
stale-while-revalidateused for public read-heavy endpoints - Sensitive data (auth tokens, personal info) uses
no-store
Server-side caching
- In-memory cache with TTL for expensive queries
- LRU eviction to bound memory usage
- Cache-aside pattern with
cacheThroughwrapper - Stampede protection for popular cache entries
- Cache keys include all query parameters that affect the result
What to cache
- Expensive queries: JOINs, aggregations, subqueries
- Computed results: averages, leaderboards, statistics
- External API responses: with extra-long TTL for fallback
- Not cached: user-specific rapidly-changing data, auth sessions, real-time data
Invalidation
- TTL set appropriately per data type (seconds to hours)
- Event-based invalidation for data where staleness is noticeable
- Tags used for complex applications with many interrelated caches
- Write-through or write-behind for frequently read, rarely written data
Monitoring
- Cache hit rate tracked (target: 80%+)
- Cache size tracked (memory usage)
- Miss rate spikes investigated (cold cache, stampede, bad TTL)
Common mistakes
Caching everything. Not all data benefits from caching. Fast queries (under 1ms), user-specific data with many variations, and frequently changing data add complexity without meaningful improvement.
TTL too long. A 24-hour TTL on book ratings means a review posted at 9 AM is not reflected until 9 AM the next day. Match TTL to the data’s change rate and staleness tolerance.
TTL too short. A 1-second TTL adds cache overhead (check, miss, query, store) on nearly every request. If the query is fast, skip caching.
Forgetting to invalidate. Adding a cache without adding invalidation. The data changes but the cache serves stale data until TTL expires (if you are lucky) or forever (if TTL is very long).
Caching “not found” results. Caching undefined for a non-existent book means the book is “not found” even after it is created — until the TTL expires. Either do not cache misses, or use a very short TTL.
No cache key for parameters. Caching the book list under the key "books" regardless of genre or page. The fiction list is returned when you ask for fantasy.
No stampede protection. A popular cache entry expires. 100 concurrent requests all miss and all query the database. Use pending promise deduplication.
No eviction. The cache grows without bound. Eventually the Node.js process runs out of memory and crashes. Use LRU eviction or set a maximum cache size.
When NOT to cache
Authentication state. A user’s session, JWT validity, or permissions must always be checked against the source. Stale auth data is a security vulnerability — a revoked session that is still cached allows unauthorized access.
Financial data. Account balances, transaction history, stock prices. Even brief staleness can cause real-world harm (overdrafts, wrong trades).
Inventory for purchase decisions. “3 left in stock” must be accurate. Stale inventory means selling items you do not have.
Data that changes on every request. If every request produces different data (UUIDs, timestamps, random content), caching has a 0% hit rate and adds overhead.
Data that is already fast. A primary key lookup on an indexed table takes under 1ms. Adding a cache check, TTL management, and invalidation logic for a 1ms query is over-engineering.
Exercises
Exercise 1: Review your cached endpoints against this checklist. Which items pass?
Exercise 2: Identify an endpoint you cached that should NOT be cached. Why?
Exercise 3: Calculate your cache hit rate. Is it above 80%? If not, why? (TTL too short? Cache too small? Too many unique keys?)
What is the most common caching mistake?