← Back to list

How to Avoid Cache Stampede on Prod?

This is one of those situations where the problem isn’t the tool.

Samia Mahmood · 2026-08-13 15:54 · 0 claps · 6.3 min read
#production-bug #cache #cache-stampede #backend
Open on Medium ↗
Wiki topics: 🌐 · Web Development

How to Avoid Cache Stampede on Prod?

This is one of those situations where the problem isn’t the tool.

The problem is how we use the tool.

What Is a Cache Stampede?

A cache stampede occurs when many requests simultaneously encounter a cache miss for the same popular key and all try to regenerate that value from the underlying database or service.

Consider a popular endpoint:

GET /products/123

The application normally works like this:

Request
   ↓
Check Cache
   ↓
Cache Hit
   ↓
Return Data

This protects the database because most requests never reach it.

But what happens when the cache expires?

Request 1 ──┐
Request 2 ──┤
Request 3 ──┤
Request 4 ──┤
Request 5 ──┤
            ↓
        Cache Miss
            ↓
       Database

If hundreds or thousands of requests arrive at approximately the same time, they may all query the database to regenerate the same value.

Instead of:

1000 requests
      ↓
1000 cache hits
      ↓
       DB

you suddenly get:

1000 requests
      ↓
1000 cache misses
      ↓
1000 database queries
      ↓
Database overload

The problem becomes particularly severe when the key represents a hot resource that receives a large percentage of your traffic.

Why Is Cache Stampede Dangerous?

The database usually isn’t prepared to perform the same expensive operation hundreds or thousands of times simultaneously.

For example, imagine an endpoint that takes 500 ms to generate its response because it performs:

  • multiple database queries
  • joins
  • aggregation
  • expensive calculations

Normally, the result is cached, so subsequent requests are cheap.

But when that cache expires:

Cache expires
     ↓
500 requests arrive
     ↓
500 expensive DB operations
     ↓
DB connection pool fills
     ↓
Queries become slower
     ↓
More requests pile up
     ↓
Application latency increases

This can create a feedback loop where a single cache expiration eventually affects the entire application.

Cache Stampede vs Cache Avalanche

These two concepts are related but shouldn’t be treated as the same problem.

Cache Stampede

A stampede usually revolves around a hot cache key.

For example:

product:123

expires.

Thousands of users request product:123 at roughly the same time.

product:123
                  ↓
               EXPIRED
                  ↓
       ┌──────────┼──────────┐
       ↓          ↓          ↓
    Request 1  Request 2  Request 3
       ↓          ↓          ↓
      DB         DB         DB

The key problem is simultaneous regeneration of the same cached value.

Cache Avalanche

A cache avalanche happens when many cache entries expire or become unavailable around the same time, producing a large number of cache misses across the application.

For example:

user:123       → expires
product:456    → expires
dashboard:789  → expires
settings:111   → expires
report:222     → expires
       ↓
Large number of cache misses
       ↓
Large increase in DB traffic
       ↓
Database overload

This can happen when many keys are created around the same time with identical TTLs.

For example:

Key A → TTL 3600 seconds
Key B → TTL 3600 seconds
Key C → TTL 3600 seconds
Key D → TTL 3600 seconds

If these keys are populated together, they can also expire together.

Cache Penetration: Another Problem to Know

There is another cache-related problem worth understanding: cache penetration.

Cache penetration happens when requests repeatedly ask for data that doesn’t exist.

For example:

GET /users/999999
        ↓
Cache miss
        ↓
Database
        ↓
User doesn't exist

If the application doesn’t cache that negative result, the next request does the same thing.

An attacker or buggy client could repeatedly request nonexistent IDs:

/users/999999
/users/999998
/users/999997
/users/999996
...

The cache cannot help because there is no value to cache.

Common mitigations include:

  • negative caching
  • Bloom filters
  • request validation
  • rate limiting

So, at a high level:

ProblemWhat happensCache StampedeMany requests regenerate the same expired keyCache AvalancheMany cache keys expire around the same timeCache PenetrationRequests repeatedly query data that doesn’t exist

How to Prevent Cache Stampede

There isn’t one universal solution. The right approach depends on how expensive the underlying operation is and how frequently the data changes.

1. Use a Lock for Cache Regeneration

One of the most common approaches is to allow only one request to regenerate the missing value.

Without a lock:

Request A → Cache Miss → DB
Request B → Cache Miss → DB
Request C → Cache Miss → DB
Request D → Cache Miss → DB

With a lock:

Request A → Cache Miss → Acquire Lock → DB
Request B → Cache Miss → Wait
Request C → Cache Miss → Wait
Request D → Cache Miss → Wait
Request A → Store result in cache
B/C/D → Read newly populated cache

This is sometimes implemented using a distributed lock in Redis.

The important idea is:

Only one request should perform the expensive regeneration while other requests wait or use another fallback strategy.

This is particularly useful for expensive operations and highly popular keys.

2. Refresh the Cache Before It Expires

Instead of waiting for a popular cache key to expire, refresh it proactively.

For example, suppose:

TTL = 60 minutes

Rather than waiting until the 60th minute:

60 min → cache expires → requests hit DB

you can refresh the value before expiration:

55 min
   ↓
Background refresh
   ↓
New value stored
   ↓
TTL reset

This is often called cache warming or proactive cache refresh.

It works particularly well for data that:

  • is frequently accessed
  • is expensive to generate
  • changes relatively infrequently

Examples include:

  • application configuration
  • feature flags
  • popular products
  • frequently accessed reports
  • expensive dashboard data

3. Use Stale-While-Revalidate

Sometimes the best solution isn’t to force users to wait for fresh data.

Instead, you can temporarily serve a slightly stale value while refreshing the cache in the background.

Request
   ↓
Cached value exists but is slightly stale
   ↓
Return cached value immediately
   ↓
Background refresh
   ↓
Fresh value stored in cache

This approach can significantly reduce latency and prevent a large group of requests from simultaneously hitting the database.

The trade-off is that users may temporarily receive slightly stale data.

Therefore, this strategy is more suitable for data where a small amount of staleness is acceptable.

4. Update the Cache Along With the Write

Another strategy is to update the cache when the underlying data changes instead of simply deleting the cache entry.

For example:

UPDATE database
       ↓
Update cache
       ↓
Future reads → Cache

Compare that with cache invalidation:

UPDATE database
       ↓
Delete cache
       ↓
Next 100 requests
       ↓
Cache misses
       ↓
Database

Updating the cache can prevent this sudden wave of cache misses.

However, this introduces a consistency consideration.

What happens if:

Database update succeeds
        ↓
Cache update fails

Now the cache may contain stale data.

Therefore, this approach should be designed with appropriate retry, consistency, and failure-handling mechanisms.

How to Prevent Cache Avalanche

1. Add Randomness to TTL

One of the simplest ways to reduce synchronized expiration is to add TTL jitter.

Instead of:

Key A → 3600 sec
Key B → 3600 sec
Key C → 3600 sec
Key D → 3600 sec

use something like:

Key A → 3540 sec
Key B → 3680 sec
Key C → 3615 sec
Key D → 3720 sec

The expiration times are now spread out.

In code, the idea could be as simple as:

ttl = 3600 + random.randint(0, 600)

Now instead of thousands of keys expiring at exactly the same moment, their expiration is distributed over a time window.

This doesn’t eliminate cache misses.

It reduces the probability of synchronized cache misses.

2. Avoid Synchronized Cache Warming

Cache warming itself can cause problems if thousands of keys are populated simultaneously.

For example:

Deploy
  ↓
Warm 100,000 keys
  ↓
100,000 DB queries
  ↓
Database spike

If you need to warm a large cache, consider:

  • batching
  • rate limiting
  • staggering requests
  • prioritizing hot keys

The goal is to avoid replacing a cache problem with a database spike during cache warming.

3. Protect the Database

Caching shouldn’t be your only line of defense.

Even with a well-designed cache, you should still protect the database using mechanisms such as:

  • connection pooling
  • query timeouts
  • rate limiting
  • request throttling
  • circuit breakers
  • load shedding

This is important because a cache can fail.

Your system should still degrade gracefully when it does.

A Simple Production Example

Imagine an application with:

1000 requests/second

A popular endpoint normally gets its response from Redis.

1000 requests
      ↓
Redis
      ↓
~995 cache hits
      ↓
Only a few DB requests

Now the popular key expires.

Without protection:

1000 requests
      ↓
1000 cache misses
      ↓
1000 DB queries
      ↓
Connection pool saturation
      ↓
High DB latency
      ↓
API latency increases

With a regeneration lock:

1000 requests
      ↓
Cache miss
      ↓
1 request acquires lock
      ↓
1 DB query
      ↓
Cache populated
      ↓
Remaining requests read cache

The difference is enormous.

The cache didn’t make the system fragile.

The lack of a strategy for cache misses did.

A Practical Strategy

For a production system, you don’t necessarily have to choose only one technique.

A robust design might combine several:

Request
                       ↓
                    Cache
                       ↓
                ┌──────┴──────┐
                │             │
             Hit             Miss
                │             │
             Return      Check Lock
                              ↓
                    ┌─────────┴─────────┐
                    │                   │
                 Lock free           Lock exists
                    │                   │
                 Acquire              Wait
                    │                   │
                   DB                   │
                    │                   │
              Update Cache              │
                    │                   │
                    └─────────┬─────────┘
                              ↓
                         Return Data

For hot keys, you can combine this with:

  • proactive refresh
  • stale-while-revalidate
  • TTL jitter
  • database protection

What Should You Monitor?

Preventing cache stampede isn’t only about writing the right code.

You also need to know when it is happening.

Useful metrics include:

Cache metrics

  • cache hit rate
  • cache miss rate
  • eviction rate
  • key expiration rate
  • cache latency

Application metrics

  • request latency
  • error rate
  • requests per second
  • concurrent requests

Database metrics

  • CPU utilization
  • active connections
  • connection pool saturation
  • query latency
  • slow queries

A sudden combination like:

Cache hit rate ↓
Cache misses ↑
DB connections ↑
DB latency ↑
API latency ↑

should immediately make you think:

Could this be a cache-related load spike?

Final Takeaway

But a cache doesn’t remove the underlying work.

It simply changes when and where that work happens.

When the cache is available:

Request → Cache → Response

When the cache fails:

Request → Database → Expensive Work

At production scale, hundreds or thousands of requests can experience that failure at the same time.


메타데이터
post_id
290fe1b596d2
slug
how-to-avoid-cache-stampede-on-prod-290fe1b596d2
url
https://medium.com/@samiamahmood14/how-to-avoid-cache-stampede-on-prod-290fe1b596d2
canonical_url
https://medium.com/@samiamahmood14/how-to-avoid-cache-stampede-on-prod-290fe1b596d2
author_url
https://medium.com/@samiamahmood14
status
ok
fetched_at
2026-09-09 12:50:57