Caching with Redis: Patterns, TTLs and Stampedes

A
Author
08 Sep, 2026 2 min read 265 views

A cache in front of a slow database query or API can cut response times from hundreds of milliseconds to one. Getting freshness and failure behaviour right is the hard part.

Cache-aside (lazy loading)

async function getProduct(id) {
  const key = `cache:product:${id}`;
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const product = await db.products.find(id);       // the slow part
  if (product) await redis.set(key, JSON.stringify(product), { EX: 600 });
  return product;
}

The app checks the cache first, and on a miss loads from the database and fills the cache.

Invalidate on write

async function updateProduct(id, changes) {
  await db.products.update(id, changes);
  await redis.del(`cache:product:${id}`);           // next read reloads fresh data
}

Deleting is safer than writing the new value into the cache, which can race with concurrent updates.

Choosing TTLs

  • Always set one; it's your safety net if invalidation is missed.
  • Short (seconds to minutes) for data that changes often, longer for reference data.
  • Add random jitter (for example 600 ± 60 seconds) so many keys don't expire at the same moment.

Preventing a stampede

When a popular key expires, hundreds of requests can miss at once and all hit the database. Let only one rebuild it:

async function getWithLock(key, ttl, load) {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const gotLock = await redis.set(`${key}:lock`, '1', { NX: true, PX: 5000 });
  if (!gotLock) {
    await new Promise((r) => setTimeout(r, 100));     // someone else is rebuilding
    return getWithLock(key, ttl, load);
  }
  try {
    const value = await load();
    await redis.set(key, JSON.stringify(value), { EX: ttl });
    return value;
  } finally {
    await redis.del(`${key}:lock`);
  }
}

Fail open

If Redis is down, fall back to the database (with timeouts) rather than failing the request. A cache should make things faster, not become a single point of failure.

What not to cache

  • Data that must be exactly current (account balances, stock at checkout).
  • Per-user data with huge key counts and low reuse.
  • Anything cheap to compute; measure before adding a cache.
Was this article helpful?

Comments

No comments yet. Have a question or a tip? Be the first to share it.

Leave a comment

Comments are reviewed before they appear.