Cache invalidation: code and snippet reading
Read real cache-aside snippets — a key-design bug, a purge that races a reader, and a fixed-TTL stampede — and pick the highest-leverage fix a senior makes first.
Invalidation bugs hide in the cache-key string and the order of two lines on the write path. Read each snippet, find the staleness gap it opens, and choose the fix a senior engineer would make first.
Practise the loop you run in every cache-consistency incident: read the key design and the write path, predict where stale data leaks in, and reach for the highest-leverage fix — not the biggest hammer.
Snippet 1 — the cache key
// GET /products?category=shoes&sort=price&page=2&utm_source=newsletter
function cacheKey(req) {
return "products:" + req.url; // whole URL, query string and all
}
async function handler(req, res) {
const k = cacheKey(req);
let body = await redis.get(k);
if (!body) {
body = await db.queryProducts(req.query);
await redis.set(k, body, "EX", 300);
}
res.send(body);
}
The product catalogue rarely changes, yet hit rate is near zero and the origin is busy. What is wrong with this key, and what is the fix?
Snippet 2 — the purge on write
async function updateUser(id, patch) {
const row = await db.users.update(id, patch); // DB is now fresh
await redis.del("user:" + id); // drop the cache entry
return row;
}
// elsewhere, the read path (cache-aside):
async function getUser(id) {
const k = "user:" + id;
let u = await redis.get(k);
if (!u) {
u = await db.users.find(id); // may read a pre-update row
await redis.set(k, u, "EX", 3600); // ...and write it back
}
return u;
}
This passes every local test but reverts user edits in production for up to an hour. What is the defect and the cheapest production patch?
Snippet 3 — the TTL
const TTL = 600; // 10 minutes, same for every entry
async function cacheConfig(tenantId) {
const k = "config:" + tenantId;
let cfg = await redis.get(k);
if (!cfg) {
cfg = await db.loadConfig(tenantId); // expensive: ~400ms, joins 5 tables
await redis.set(k, cfg, "EX", TTL);
}
return cfg;
}
After a fleet-wide deploy that warms many tenants' configs at once, latency p99 spikes hard every 10 minutes. What is happening and the first fix?
Three classic invalidation bugs, all read straight from the code: a cache key that folds in irrelevant params shatters hit rate, so build keys from only the response-affecting params, normalised; delete-on-write races a concurrent reader’s repopulating SET, patched cheapest with delayed double-delete (leases or write-through for the strong fix); and a fixed TTL warmed in one window stampedes the origin on every expiry, defused with jitter plus single-flight. Read the key and the write path first, name the gap, then pick the smallest fix that closes it.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.