Skip to main content

Command Palette

Search for a command to run...

Understanding the Thundering Herd Problem

A real world analogy

Updated
11 min read
Understanding the Thundering Herd Problem

Imagine a neighborhood store that opens at 9:00 AM. For days, people talk about a limited-time deal, a popular gadget at a steep discount. At 8:59 the queue is around the block. At the exact stroke of 9:00 the door unlocks and the entire crowd pushes forward. The checkout counters, staffed for regular traffic, are suddenly overwhelmed: people pile up, staff scramble, registers choke, and the manager watches the system which was perfectly adequate for normal days, collapse for a few chaotic minutes.

That exact physical phenomenon maps directly to a classic distributed-systems problem: a large set of clients or processes all act on the same signal (door opens, cache expires, service comes back up) and simultaneously consume a resource, overwhelming the downstream system. In systems engineering we call that the Thundering Herd Problem.


Problem definition

Thundering Herd Problem (short) :

A large number of processes, threads, or clients wake up or issue requests at the same time because they share a common trigger, producing a sudden spike in demand that overwhelms one or more parts of the system.

Key characteristics:

  • Synchronized trigger : a single event (cache TTL expiry, deployment finish, service restart, fixed CRON time) causes many actors to act simultaneously.

  • Resource contention : the simultaneous requests hit the same scarce resource (database, upstream API, file, lock).

  • Cascading failure potential : one overloaded component causes slowdowns or errors that propagate through the system.

Where it occurs

Common places the problem shows up ~

  • Cache systems: when a widely-used cache key expires at once (cache TTL aligned across instances), all application nodes detect a miss and fetch from the database.

  • Databases / connection pools: many requests try to open DB connections or execute heavy queries, exhausting connections and causing timeouts.

  • Load balancers / scaling events: traffic redistribution after a node failure or scaling event can route many requests to fewer instances.

  • Microservices & dependency availability: when a dependent service becomes available after being down, many downstream services may replay or reattempt requests at once.

  • Scheduled jobs / CRON: thousands of scheduled tasks align at the same minute/hour, leading to bursts.

Architecture example

What happens when cache TTL expires (sequence):

  1. Cache TTL for popular key K reaches 0.

  2. App Servers A, B, C all receive client requests needing K.

  3. Each server sees a cache miss and issues a DB query for K at nearly the same time.

  4. DB receives N duplicate queries; connection pool fills; CPU spikes.

  5. DB makes the queries slowly, responses backlog; some requests timeout → 5xx errors.

  6. Once DB responds, caches are updated, but the damage (errors, high latency) already happened.

This is the classic cache-driven thundering herd (also called “cache stampede” in some literature).


Real-world example

Picture a large e-commerce site running a midnight sale, or a streaming service releasing a new season at 00:00. If a popular resource (product detail, front-page list, content metadata) is cached with the same TTL across all app servers and the TTL hits precisely at release time, thousands (or millions) of servers/clients can simultaneously attempt to fetch fresh data from the origin database or metadata store. The database becomes the bottleneck: queries queue, CPU saturates, connections are exhausted, and the site becomes slow or returns errors during the very moment demand is highest.

Real-World Example: Traffic Spikes During Major Events

One of the most common real-world manifestations of the Thundering Herd Problem occurs during major digital events when millions of users attempt to access the same resource simultaneously.

1. Live Sports Streaming (IPL Match Start)

During events like the Indian Premier League (IPL), millions of users open their streaming apps just minutes before a match begins. Platforms streaming the match often experience extremely synchronized user behavior.

Typical pattern:

Millions of users open the app at 7:29 PM
            |
            v
    Authentication Requests
            |
            v
   Match Metadata API
            |
            v
      Cache Layer
            |
     (Cache expires)
            |
            v
        Database

If the match metadata cache expires exactly at match time, all application servers may simultaneously attempt to fetch the same data (team lineups, stream URLs, player stats).

Instead of 1 request, the database suddenly receives thousands of identical queries, causing:

  • DB connection pool exhaustion

  • High CPU usage

  • Increased latency

  • Temporary service degradation

This is a classic cache stampede scenario, a subtype of the Thundering Herd Problem.


2. New Show Releases on Streaming Platforms

Streaming platforms such as Netflix frequently release new episodes or entire seasons at a fixed time globally.

For example:

New season releases at 12:00 AM
        |
Millions of users refresh the app
        |
        v
   Home Feed API
        |
        v
   Recommendation Cache
        |
     (Cache Miss)
        |
        v
 Recommendation Database

If popular endpoints like:

  • homepage recommendations

  • new releases list

  • show metadata

expire simultaneously, backend services may experience a massive synchronized surge of cache misses, causing the underlying database or recommendation service to be overwhelmed.

To prevent this, streaming platforms typically use:

  • TTL jitter for cache entries

  • request coalescing

  • pre-warming caches before release

  • CDN edge caching


Why These Events Are Dangerous

Unlike normal traffic growth, these events have perfect synchronization:

  • Same trigger time

  • Same resource requested

  • Same backend dependency

This combination makes systems extremely vulnerable to the Thundering Herd effect, especially when caching layers or backend services are not designed to absorb synchronized demand spikes.


System impact ~ the cascade

When a thundering herd hits, expect to see:

  • CPU usage: spikes to near 100% on the overloaded component (DB, app servers) because of processing many heavy requests concurrently.

  • Database connections: connection pools exhaust; new requests block or fail with connection timeouts.

  • Cache hit ratio: plummets temporarily because multiple clients experienced a miss and are now hitting origin.

  • Response latency: increases from low milliseconds to multiple seconds or timeouts; user experience degrades.

  • Error rates: 5xx errors rise as services fail to handle the load; downstream clients see higher failure rates.

  • Autoscaling thrash: systems may spin up many containers/VMs simultaneously in response to latency, which can cause cold-start penalties or further pressure on shared resources (DB).

  • Operational noise: alerted pagers, mitigation scripts, human intervention — wasted resources while the spike resolves.


Normal spike vs Thundering Herd ~ what's different?

  • Normal (organic) spike: traffic increases gradually or proportionally across endpoints; load is distributed and usually predictable (e.g., a marketing campaign driving more users).

  • Thundering herd: the spike is synchronized, many identical requests happen at (nearly) the same instant because of a shared trigger (TTL expiry, scheduled job, cache flush). This synchronization makes it far more dangerous because the system can’t amortize or stagger demand.


Prevention techniques (practical, accessible)

Below are proven strategies used in practice. You don’t need heavy math, each technique is about changing timing or coordination so requests don’t all hit the same resource at once.

1. Request coalescing / single-flight

Idea: Ensure only one upstream request for a given key is in-flight; other requests wait for that single result.
How it works: When multiple requests for key K arrive and no cache entry exists, the first request performs the DB fetch; subsequent requests subscribe to the same in-progress operation and reuse its result when it completes.
Notes: Languages/frameworks have patterns (e.g., Go’s singleflight), and many caching libraries implement this.

2. Cache locking (distributed mutex)

Idea: Use a lock per cache key so only one process performs the heavy load operation.
How it works: On cache miss, process tries to acquire a short-lived lock (e.g., Redis SETNX + short TTL). If acquired, it fetches from DB and updates cache; otherwise, clients either wait, periodically check, or serve stale data.
Caution: Locks must have a TTL to avoid deadlock if a process crashes.

3. Staggered cache expiry / TTL jitter

Idea: Avoid aligning key expirations.
How it works: Instead of a fixed TTL of 3600s for everyone, add randomness: TTL = base + rand(-j, +j). This spreads refreshes over time and prevents synchronized misses.
Good for: large populations of identical keys where synchronized expiry is possible.

4. Refresh-ahead / Probabilistic early expiration

Idea: Proactively refresh popular cache entries before they expire and optionally serve slightly stale data while refreshing.
How it works: When serving key K, if remaining_TTL < threshold, kick off an asynchronous refresh (background thread) to renew the cache instead of waiting for an on-demand rebuild.
Probabilistic early expiration: on each hit, with small probability based on TTL, treat the key as expired and refresh—this probabilistically spreads load.

5. Exponential backoff with jitter (clients)

Idea: When clients encounter failures or miss the cache and see rate-limited upstreams, back off retries using exponential intervals plus randomness.
How it works: On retry, wait base * 2^n plus random jitter. Jitter avoids synchronized retry bursts when many clients fail simultaneously.

6. Rate limiting and throttling

Idea: Protect downstream resources by limiting request rates per client/key and providing graceful degradation.
How it works: Use token buckets or circuit breakers. Throttle new cache rebuilds per key to protect DB; reject or queue extra rebuilds.

7. Request batching

Idea: Batch multiple similar requests into a single upstream query when possible.
How it works: For example, instead of 100 separate DB queries for different keys, combine them into SELECT ... WHERE id IN (...). Useful when patterns allow grouping.


Architecture after mitigation

Result: cache misses are spread out, only one fetch per key happens at a time, and background refreshes keep the cache warm. The DB sees a controlled, limited flow rather than a flood.


Operational guidance ~ what to monitor

To detect and diagnose a thundering herd quickly monitor:

  • Cache hit ratio (key-level if possible)

  • DB connection pool usage and rejected connection counts

  • CPU and query latency percentiles for DB and app servers

  • Request latencies (p50/p95/p99)

  • 5xx error rates and time-aligned spikes

  • Rate of cache misses for hot keys

  • Queuing/backpressure indicators (request queues, thread pool exhaustion)

Graphing these metrics around an event (TTL expiry, deploy) often reveals the synchronized spike signature.


Q1. How would you design a cache system to prevent the thundering herd?
Answer outline:

  • Use read-through cache with single-flight to coalesce duplicate fetches.

  • Add cache locking with short TTL locks to limit rebuilders to one process.

  • Introduce staggered TTL/jitter when setting TTLs.

  • Implement refresh-ahead / probabilistic early expiration to refresh hot keys proactively.

  • Protect DB with rate-limiting/throttling and good connection pool settings.

  • Instrument metrics (cache hit ratio, DB connections) to validate behavior.

Q2. What happens when millions of users refresh at exactly midnight?
Answer outline:

  • If backend caches/keys expire at midnight, you'll see synchronized cache misses → DB overload.

  • Symptoms: spike in request rate, high DB CPU/latency, exhausted connections, increased 5xx.

  • Mitigations: stagger cache expiry, pre-warm/read-through caching, CDN usage, backpressure and rate-limits on API endpoints, and load test for midnight scenarios.

Q3. Explain single-flight vs cache locking — when to use each?
Answer outline:

  • Single-flight: In-process or library-level deduplication: best when many threads in the same process may duplicate work.

  • Cache locking (distributed mutex): Required when multiple processes across machines might rebuild the cache simultaneously. Uses a distributed coordination store (Redis) to ensure only one process rebuilds.

  • Often use both: single-flight within-process + distributed lock across nodes.


Practical tips & anti-patterns

Do this:

  • Add jitter to TTLs by default.

  • Protect origin databases with connection pool limits and circuit breakers.

  • Pre-warm caches for known traffic events (major campaign, release).

  • Run load tests that simulate synchronized events — not just steady load.

Avoid this:

  • Setting identical TTLs across many nodes for extremely hot keys.

  • Rebuilding caches without any concurrency limits.

  • Relying solely on autoscaling to solve DB saturation — autoscaling increases app capacity but not DB throughput.

The Thundering Herd Problem is painful because it turns a predictable resource (like a cache miss) into a catastrophic, synchronized storm. The fix is almost always about coordination and timing rather than raw scale: coalesce duplicate work, spread refreshes in time (jitter), refresh proactively, and add sensible throttles and protections on the origin.

Think of it this way: instead of unlocking the store and letting everyone run to the single checkout counter, give people staggered entry, multiple checkout lanes, and a store manager who coordinates who goes first. Small changes - randomizing expiry times, allowing one person to fetch and share the result, and refreshing popular items early, turn a panic into a handled increase in demand.