Caching: Redis & HTTP
Caching trades a little stale data for a lot of latency. HTTP caching happens at the edge with Cache-Control and ETags, while Redis caches hot database results and sessions in memory. This concept covers invalidation, TTLs, stampedes and cache-aside patterns.
What is this?
Caching stores the result of an expensive computation or query so later requests skip the work. HTTP caching uses Cache-Control, ETag and Last-Modified headers so browsers and CDNs serve responses without hitting your origin. Redis is an in-memory data store commonly used for cache-aside reads, sessions, rate-limit counters and queues, with keys that expire via TTL.
Why do you need it?
- Reading from Redis is sub-millisecond, versus 1-10ms for a database query — and zero if the CDN or browser serves the response.
- Caching absorbs read spikes: thousands of identical requests hit one warm cache instead of the database.
- Correct TTLs bound staleness, so cached data is always fresh enough while never being forever wrong.
- ETags and If-None-Match let you return 304 Not Modified with an empty body, saving bandwidth for unchanged content.
Mental model
A chef pre-prepping popular dishes
A cache
Rather than cooking every plate from scratch (database query), the kitchen keeps the most-ordered dishes ready (Redis cache) and marks them with a time they go stale (TTL). If a dish sits too long it is thrown out and cooked fresh on the next order. Eating well (hot data) is fast; the menu still lists everything (source of truth).
How it works
- Cache-aside: the app checks the cache key; on a hit it returns the value, on a miss it queries the database, stores the result with a TTL, and returns it.
- TTLs expire keys automatically after a set period, so the cache self-heals as data goes stale.
- HTTP caches (browsers, CDNs, proxies) honor Cache-Control: max-age to hold responses, and revalidate with ETags when freshness expires.
- Invalidation removes or bumps keys when writes happen, so the cache does not serve deleted or updated data.
- Cache stampedes are prevented with locks or short randomized TTLs so one cold key is not recomputed by every request at once.
Cache-aside read path with Redis, plus HTTP caching at the edge.
Key commands
docker run -d -p 6379:6379 redis:7Run a Redis container on the default port.
<container id>
redis-cli SET products:42 '{"price":9.99}' EX 60Write a key with a 60-second TTL.
OK
redis-cli GET products:42Read a key's value.
{"price":9.99}redis-cli TTL products:42Show remaining seconds before the key expires.
52
redis-cli DEL products:42Invalidate a key immediately after a write.
(integer) 1
Examples
Cache-aside with node-redis
const { createClient } = require("redis") const client = createClient({ url: process.env.REDIS_URL })client.on("error", (err) => console.error("redis error", err))client.connect() async function getProduct(id) { const key = "products:" + id // 1. Try the cache const cached = await client.get(key) if (cached) { return { source: "cache", data: JSON.parse(cached) } } // 2. Miss — load from the database (here a stand-in) const row = { id, name: "Widget", price: 9.99 } // 3. Store with a TTL so it expires and self-heals await client.set(key, JSON.stringify(row), { EX: 60 }) return { source: "db", data: row }} getProduct(42).then((r) => console.log(r.source)) // first call: dbEvery read checks Redis first. On a hit the parsed value is returned with source 'cache'. On a miss the row is loaded, stored with a 60-second EX (expiry), and returned with source 'db'. The TTL means stale entries disappear on their own; writes should call client.del(key) to invalidate immediately instead of waiting for expiry.
In production
- Hot product, profile and config reads are cache-aside in Redis behind the API layer.
- Sessions and rate-limit counters live in Redis so every replica sees the same state.
- CDNs (Cloudflare, Fastly) serve static assets and even API responses using Cache-Control at the edge.
- Leaderboards, queues and pub/sub use Redis's native data structures beyond plain key-value caching.
Best practices
- Prefer cache-aside with explicit TTLs; let the database stay the source of truth.
- Invalidate on write (DEL or versioned keys) rather than relying only on TTLs.
- Cache only what is safe to be slightly stale and what is read often but written rarely.
- Guard against stampedes with locking or jittered TTLs for expensive recomputations.
- Set Cache-Control on reads and treat ETags as a cheap revalidation path for changed content.
Common mistakes
- Caching for too long with no invalidation, serving deleted or stale data to users.
- Caching user-specific data under a shared key and leaking one user's data to another.
- Storing unbounded keys with no TTL, filling Redis memory until evictions destroy hit rates.
- Using the cache as the source of truth, then losing data on a Redis restart.
Common issues
Cannot connect to Redis at localhost:6379Redis is not running or the port is wrong.
Start the container (docker start redis) and verify with redis-cli ping.
OOM command not allowed when used memory > 'maxmemory'Redis hit its memory cap and eviction policy forbids writes.
Add TTLs, raise maxmemory, or set a sane maxmemory-policy like allkeys-lru.
Cache serves stale data after an updateThe write path never invalidated the key and the TTL is long.
Call DEL on the affected keys inside the update transaction, or shorten the TTL.
Debugging tips
- redis-cli MONITOR shows every command the app issues, revealing unexpected cache traffic.
- Check INFO stats for keyspace_hits and keyspace_misses to compute the hit ratio.
- Inspect maxmemory-policy — with 'allkeys-lru' you may be evicting important keys.
- Verify Cache-Control on responses with curl -I; missing headers mean no HTTP caching.
Interview checkpoint
Hands-on lab
- Run Redis and exercise basic commands
- Implement cache-aside reads with TTL
- Watch the cache hit vs miss path
- Add Cache-Control and ETag to an Express endpoint
Cache-aside with Redis and HTTP headers
40 min · advanced
Quick quiz
In the cache-aside pattern, what should a write operation do?
An HTTP response with Cache-Control: no-store should not be stored by any cache.
Redis keys stored without a TTL can cause which production problem?
Cheat sheet
SET key value EX 60Write with a TTLGET keyRead a valueDEL keyInvalidate immediatelyTTL keySeconds until expiryINCR keyAtomic counter (rate limits)Cache-Control: max-age=60Fresh for 60sCache-Control: no-storeNever cacheETag: "abc"Revalidation tagIf-None-Match: "abc"Ask for 304 if unchanged