Backend
Advanced 18 min read Backend

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.

RedisCache-ControlETagTTLcache-asidecache stampedeinv
Introduction

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.

Motivation

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.
Think about it

Mental model

Imagine

A chef pre-prepping popular dishes

Think of

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).

Mechanics

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.

GET /products/42
Cache hit?
Serve cached value
Cache miss
Write back with TTL
Return fresh JSON
Reference

Key commands

docker run -d -p 6379:6379 redis:7

Run a Redis container on the default port.

<container id>
redis-cli SET products:42 '{"price":9.99}' EX 60

Write a key with a 60-second TTL.

OK
redis-cli GET products:42

Read a key's value.

{"price":9.99}
redis-cli TTL products:42

Show remaining seconds before the key expires.

52
redis-cli DEL products:42

Invalidate a key immediately after a write.

(integer) 1
Hands-on

Examples

Cache-aside with node-redis

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: db

Every 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.

Real world

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.
Guidelines

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.
Watch out

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.
Root cause analysis

Common issues

Error
Cannot connect to Redis at localhost:6379
Root cause

Redis is not running or the port is wrong.

Fix

Start the container (docker start redis) and verify with redis-cli ping.

Error
OOM command not allowed when used memory > 'maxmemory'
Root cause

Redis hit its memory cap and eviction policy forbids writes.

Fix

Add TTLs, raise maxmemory, or set a sane maxmemory-policy like allkeys-lru.

Error
Cache serves stale data after an update
Root cause

The write path never invalidated the key and the TTL is long.

Fix

Call DEL on the affected keys inside the update transaction, or shorten the TTL.

When things break

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.
Interviews

Interview checkpoint

Practice

Hands-on lab

Cache-aside with Redis and HTTP headers
40 min · advanced
In progress
Objectives
  • 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

0/4 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

In the cache-aside pattern, what should a write operation do?

2

An HTTP response with Cache-Control: no-store should not be stored by any cache.

3

Redis keys stored without a TTL can cause which production problem?

Quick reference

Cheat sheet

Redis commands
SET key value EX 60Write with a TTL
GET keyRead a value
DEL keyInvalidate immediately
TTL keySeconds until expiry
INCR keyAtomic counter (rate limits)
HTTP caching headers
Cache-Control: max-age=60Fresh for 60s
Cache-Control: no-storeNever cache
ETag: "abc"Revalidation tag
If-None-Match: "abc"Ask for 304 if unchanged
Go deeper

Official documentation