When a 100ms Redis Cache Fetch Suddenly Took 137 Seconds: A Production Postmortem
Introduction
Redis is often considered one of the fastest components in a modern microservice architecture. Since it’s an in-memory data store, developers generally expect cache retrieval times to be measured in milliseconds, not seconds.
Recently, we encountered a production incident where Redis cache latency increased dramatically, with a cache fetch that normally completed in 100-800 milliseconds suddenly taking 137 seconds.
The result?
- Increased API response times
- Thread pool exhaustion
- Kafka consumer lag
- Cascading delays across more than one microservices
- Multiple production alerts and SLA breaches
The System Architecture
The application consists of multiple microservices communicating synchronously through REST APIs and asynchronously through Kafka.
Client
↓
API Gateway
↓
Service A
↓
Redis
↓
Service B
↓
Service C
↓
Service D
↓
Kafka Consumers
Several downstream services depended directly or indirectly on the response from Service A.
The Incident
Under normal conditions:
Redis cache fetch latency: 100ms – 800ms
During the incident:
Redis cache fetch latency: 137 seconds
Immediately after this:
- API latency increased dramatically.
- Request queues started growing.
- Thread pools became saturated.
- Kafka consumers started lagging.
- Dependent services began timing out.
- Alerts were triggered across multiple services.
At first glance, it appeared to be a Redis issue.
However, the investigation revealed that the problem was much larger than Redis itself.
Why Even 500ms Is High for Redis
Redis is an in-memory database.
Typical response times are:
| Environment | Expected Latency |
| Same Server | <1ms |
| Same VPC | 1-5ms |
| Different Availability Zone | 5-20ms |
| Cross Region | 20-50ms |
Understanding the Cascading Failure
The biggest problem wasn’t the slow Redis call itself.
The real problem was that every downstream service waited for the response.
Service A
↓
Service B waits
↓
Service C waits
↓
Service D waits
Eventually:
Blocked Threads
↓
Connection Pool Exhaustion
↓
Request Queue Build-up
↓
Increased Latency
↓
Timeouts
↓
System-wide Alarms
This phenomenon is called Cascading Failure
One slow dependency slowly degrades the entire distributed system.
Possible Root Causes
1. Redis Connection Pool Exhaustion
One of the most common reasons for long Redis response times.
Suppose:
spring:
data:
redis:
lettuce:
pool:
max-active: 8
If hundreds of requests simultaneously attempt to access Redis, they start waiting for available connections.
The wait time can quickly increase from milliseconds to several minutes.
Symptoms
- Increased request latency
- High thread count
- Low Redis CPU usage
- High connection wait time
2. Cache Stampede
A cache stampede occurs when thousands of requests attempt to load the same key simultaneously.
10,000 Requests
↓
Same Cache Key
↓
Heavy Contention
The Redis server becomes overloaded, and response times increase significantly.
Prevention
- Cache warming
- Request coalescing
- Distributed locks
- Stale cache strategy
3. Huge Cached Objects
Sometimes applications cache very large objects.
Example:
UserProfile
ProductCatalog
MediaMetadata
Large payloads increase:
- Serialization time
- Deserialization time
- Network transfer time
- Memory consumption
4. Network Problems
Sometimes Redis is perfectly healthy.
The delay comes from:
- DNS resolution issues
- Packet loss
- Cross-region communication
- Kubernetes networking problems
- Load balancer issues
Production Investigation Checklist
During incidents like this, investigate the following metrics immediately:
Redis Metrics
- CPU utilization
- Memory utilization
- Connection count
- Slow queries
- Network throughput
- Cache hit ratio
Application Metrics
- Thread pool utilization
- Connection pool utilization
- GC pauses
- Heap usage
- Request queue size
Kafka Metrics
- Consumer lag
- Processing time
- Message backlog
Immediate Mitigation
1. Fail Fast
Never allow Redis requests to wait for minutes.
spring:
data:
redis:
timeout: 2s
2. Use Circuit Breakers
@CircuitBreaker(name = “redis”)
If Redis becomes slow:
- Stop sending requests.
- Return fallback responses.
- Protect downstream services.
3. Bulkhead Isolation
@Bulkhead(name = “redis”)
Redis failures should not consume all application threads.
4. Fallback Cache
return staleCachedData;
Returning slightly old data is often better than bringing down the platform.
5. Monitor Everything
Important alerts:
- Redis latency > 50ms
- Connection pool usage > 80%
- Thread pool utilization > 80%
- Kafka consumer lag
- Slow query count
Conclusion
The biggest lesson from this incident is that in distributed systems, a dependency becoming slow can be more dangerous than a dependency completely failing.
A Redis cache fetch that normally took 100-800 milliseconds suddenly taking 137 seconds was not just a Redis problem, it became a platform-wide problem. The delay propagated across more than one microservices, leading to thread pool exhaustion, increased Kafka consumer lag, service timeouts, and multiple production alerts.
The incident highlighted an important engineering principle: every external dependency, no matter how reliable, can eventually become slow or unavailable. Systems must therefore be designed with resilience in mind.
Implementing proper timeouts, circuit breakers, bulkhead isolation, fallback mechanisms, and comprehensive monitoring ensures that a single slow component cannot bring down an entire ecosystem.
The goal of a resilient microservice architecture is not to prevent failures completely, failures are inevitable. The goal is to contain them, isolate them, and ensure that one service’s problem never becomes everyone’s problem.
Related articles