Skip to content
WhatsApp
AI StudioPricing
Backend11 min read2026-09-04

Redis Caching Strategies That Prevent Thundering Herds and Cache Stampedes

Protect your primary database from catastrophic failure during traffic spikes. Learn how to implement Probabilistic Early Expiration (XFetch), mutex locking, and tiered in-memory caching.

Alamgir Kabir
Chief Architect & Founder
Redis Caching Strategies That Prevent Thundering Herds and Cache Stampedes

1. The Nightmare of the Cache Stampede

Imagine this scenario: your e-commerce storefront or SaaS homepage caches a complex, aggregated query (such as 'trending products' or 'top public templates') in Redis with a 60-minute Time-To-Live (TTL). The query takes 800ms to compute on your primary PostgreSQL database.

At exactly 12:00:00 PM, the cache key expires. Over the next 500 milliseconds, 3,000 concurrent user requests arrive. Finding the cache key empty, every single request simultaneously fires the heavy 800ms query against PostgreSQL.

This is the Thundering Herd (or Cache Stampede). PostgreSQL's CPU instantly hits 100%, query queues fill up, database connection pools exhaust, and the entire platform crashes. Adding more web servers only worsens the stampede.

2. The Solution: Probabilistic Early Expiration (XFetch Algorithm)

The most elegant mathematical solution to the cache stampede problem is the XFetch algorithm (developed by Vattani et al.).

Instead of waiting for the key to expire completely, as the remaining TTL diminishes, requests have an increasing probabilistic chance of recomputing the value in the background while still serving the current cached value to other users.

The formula calculates whether to recompute based on: remaining TTL, the time it took to compute the value previously (`delta`), and a tuning parameter (`beta`):

`-delta * beta * ln(random()) > remaining_ttl`

Pro Tip: With XFetch, high-traffic cache keys NEVER expire cold. They are continuously refreshed in the background by a single worker before expiration.

3. Implementation: Production XFetch in TypeScript

Here is a complete, production-ready implementation of the XFetch algorithm using Node.js and Redis:

xfetch-cache.ts
typescript
export interface CachedRecord<T> {
  value: T;
  delta: number; // Duration in ms it took to compute the value
  expiresAt: number; // Unix timestamp in ms
}

export class ResilientCache {
  constructor(private redis: any) {}

  async getOrCompute<T>(
    key: string,
    ttlSeconds: number,
    computeFn: () => Promise<T>,
    beta: number = 1.0
  ): Promise<T> {
    const raw = await this.redis.get(key);
    let record: CachedRecord<T> | null = raw ? JSON.parse(raw) : null;

    const now = Date.now();

    // Check XFetch probabilistic condition:
    // If record is missing, OR probabilistic threshold is met
    const shouldRecompute =
      !record ||
      now >= record.expiresAt ||
      -record.delta * beta * Math.log(Math.random()) > (record.expiresAt - now);

    if (shouldRecompute) {
      const startTime = Date.now();
      const freshValue = await computeFn();
      const delta = Date.now() - startTime;

      const newRecord: CachedRecord<T> = {
        value: freshValue,
        delta,
        expiresAt: now + ttlSeconds * 1000,
      };

      // Save with safety buffer TTL in Redis
      await this.redis.set(
        key,
        JSON.stringify(newRecord),
        "EX",
        ttlSeconds + 60
      );

      return freshValue;
    }

    return record.value;
  }
}

4. Distributed Mutex Locking (Redlock)

For expensive operations where you cannot tolerate even a probabilistic duplicate run (e.g. generating an expensive daily PDF financial report), use a distributed mutex lock in Redis.

When a cache miss occurs, the first process acquires a non-blocking lock with a short lease: `SET key:lock uuid NX EX 10`. Only the lock holder queries the database; other concurrent requests wait 50ms and retry fetching the newly cached value.

This ensures that exactly one database query executes regardless of how many thousands of requests arrive concurrently.

Frequently Asked Questions

What is the difference between Cache-Aside and Write-Through caching?

Cache-Aside checks the cache first and queries the database on miss. Write-Through updates both the database and the cache synchronously on every write.

How does Redis handle out-of-memory situations?

When memory limits are reached, Redis evicts keys according to its `maxmemory-policy`. For web caching, `allkeys-lru` or `volatile-lru` is recommended.

Can Redis replace a primary database?

While Redis supports persistence (AOF and RDB), it lacks relational integrity constraints, ACID multi-table transactions, and complex indexing. Use Redis as a high-speed cache alongside PostgreSQL.

Topics:#redis caching strategies#cache stampede prevention#thundering herd redis#xfetch probabilistic early expiration#distributed mutex caching#redis cluster high availability#database protection caching

Want this thinking applied to your product?

Spin up a prototype in minutes, or brief our team and get a scoped plan within 24 hours.