Menu
Medium #system-design·August 13, 2026

Mitigating the Thundering Herd Problem in Caching Systems

This article discusses the 'Thundering Herd Problem', a critical issue in distributed caching where many clients concurrently request the same uncached data, overwhelming the backend database. It explores the causes and proposes various system design solutions to prevent database Distributed Denial of Service (DDoS) and ensure cache effectiveness.

Read original on Medium #system-design

Understanding the Thundering Herd Problem

The Thundering Herd Problem occurs when a cache miss for a popular item causes a large number of concurrent requests to hit the backend data store (e.g., a database). This sudden surge of requests can overwhelm the database, leading to performance degradation, timeouts, and potentially a cascading failure. It's often triggered during cache warm-up, cache invalidation, or when a popular item expires simultaneously across multiple cache nodes. This scenario effectively turns a cache, intended to protect the database, into a mechanism that inadvertently DDoS's it.

⚠️

The Cache's Paradox

While caching is vital for scalability, incorrect handling of cache misses, especially for popular items, can create more problems than it solves. A poorly designed cache invalidation strategy or lack of protection against simultaneous cache misses can lead to a 'database DDoS'.

Architectural Solutions and Mitigation Strategies

Several system design patterns and techniques can be employed to mitigate the Thundering Herd Problem. These strategies primarily focus on preventing multiple concurrent requests from reaching the backend data store for the same missing cache entry.

  • Request Coalescing (Single Flight/Mutex Lock): When the first request for a missing item reaches the cache, it acquires a lock. Subsequent requests for the same item wait for the lock to be released. Once the data is fetched and cached by the first request, all waiting requests retrieve it from the now-populated cache. This significantly reduces the load on the backend.
  • Probabilistic Caching (Cache Stampede Prevention): Instead of immediately fetching expired items, introduce a small, random expiry window. When an item expires, a small percentage of requests might re-fetch it, while others continue serving the stale data for a short period. This spreads out the load over time.
  • Cache Pre-fetching/Warming: Proactively load popular data into the cache before it's requested or before it expires. This can be done based on access patterns or scheduled jobs, ensuring critical data is always available in the cache.
  • Lease/Revalidate Mechanism: When a cache entry is stale, instead of immediately purging it, mark it as 'stale' and serve it while a single background process (or the first request) attempts to revalidate/refresh it from the backend. If revalidation fails, the stale data can still be served, preventing a hard cache miss.

Implementation Considerations

Implementing these solutions requires careful consideration of consistency requirements, cache eviction policies, and the potential for increased latency for the initial request that populates the cache. Distributed locks are often necessary for request coalescing in a distributed caching environment to ensure atomicity across multiple cache nodes.

go
type SingleFlightGroup struct {
    mu sync.Mutex
    m  map[string]*call
}

type call struct {
    wg  sync.WaitGroup
    val interface{}
    err error
}

func (g *SingleFlightGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
    g.mu.Lock()
    if g.m == nil {
        g.m = make(map[string]*call)
    }
    if c, ok := g.m[key]; ok {
        g.mu.Unlock()
        c.wg.Wait() // Wait for existing call to complete
        return c.val, c.err
    }
    c := new(call)
    c.wg.Add(1)
    g.m[key] = c
    g.mu.Unlock()

    c.val, c.err = fn() // Execute the actual data fetch
    c.wg.Done()

    g.mu.Lock()
    delete(g.m, key) // Clean up after call completes
    g.mu.Unlock()

    return c.val, c.err
}
cachingthundering herdcache stampededistributed systemsscalabilitydatabase protectionrequest coalescingsystem design patterns

Comments

Loading comments...