Retries Are Almost Never the Cause. Measure the Amplification Factor Before You Turn Them Off.
TL;DR A retry storm occurs when service failures trigger automated retries that multiply traffic, overwhelming downstream dependencies and preventing recovery. To diagnose one, you must calculate the amplification factor: the ratio of physical downstream attempts to initial logical requests. If this ratio rises while inbound user traffic remains flat, your recovery logic has become an accidental DDoS attack.
The question you are actually asking at 3am
When a downstream dependency is red-lining and latency is spiking, your first instinct is to look at the traffic graph. It’s up 10x. The immediate question isn't "what is a retry storm?"—you can see the chaos. The real question is: If I cut retries right now, will the service recover or will I convert a degraded state into a total outage?
Systems in distress exist in one of three states regarding retries:
- Reacting: Retries are helping a small percentage of users overcome transient blips.
- Amplifying: Retries are making a bad situation worse, increasing the load on a struggling dependency.
- Sustaining: The original fault (e.g., a slow DB query) has cleared, but the sheer volume of queued retries is keeping the dependency underwater.
One number: the amplification factor
To distinguish these states, you need the amplification factor. This is the ratio of total physical downstream attempts divided by initial logical requests, measured per dependency.
Consider this "Before vs. During" signal table:
| Metric | Baseline | Incident (Retry Storm) | Incident (Traffic Spike) |
|---|---|---|---|
| User Requests (Logical) | 100 rps | 100 rps | 1,000 rps |
| Physical Dependency Calls | 105 rps | 1,000 rps | 1,050 rps |
| Amplification Factor | 1.05 | 10.0 | 1.05 |
| Success Rate | 99% | 10% | 95% |
| Dependency Queue Depth | Low | Critical | High |
The smoking gun is when user requests are flat but dependency traffic is climbing. If your amplification factor is significantly above 1.1, your recovery logic is likely the primary source of load.
How to count physical attempts when nobody instrumented a retry counter
Most teams don't have a service_retries_total counter ready when the fire starts. According to Microsoft Learn, identifying these patterns often requires deep diagnostic logging, but you can approximate the factor using existing telemetry in this order:
- Distributed Tracing: Group spans by a logical
request_id. Count how many child spans exist for a single parent span. This is the most accurate method for attributing which layer is retrying. - Service Mesh/Proxy Counters: If you run Envoy or Istio, look for
upstream_rq_retryor retry overflow metrics. These capture retries that happen outside your application code. - Gateway vs. Service Ratios: Compare the inbound request rate at your API Gateway to the outbound request rate of the calling service. The delta is your amplification.
- Connection Pool Acquisition: If the observability pipeline is also failing, look at your database or connection pool "checkout" rates. A surge in acquisitions without a corresponding surge in user sessions is a proxy for retry loops.
Multi-layer fan-out is a product, not a sum
In modern microservices, the retry storm is often a result of nested policies. If your mobile SDK retries 3 times, your API gateway retries 3 times, and your backend service retries 3 times, a single failed user request generates 27 physical attempts (3 x 3 x 3).
Key Takeaway: Multi-layer retry amplification is multiplicative, meaning even conservative local policies can create massive global surges when stacked across a call chain.
According to research from Agoda Engineering, a chain 10 services deep can model a surge from 100 rps to 51,200 rps—over 500 times the baseline load—simply through default retry behaviors. In reality, most "storms" are caused by layers you didn't know were retrying, such as default settings in an ORM, a service mesh, or a cloud SDK.
Differential diagnosis: three things that look identical on a traffic graph
Not every traffic surge is a retry storm. Use this table to perform a differential diagnosis:
| Feature | Retry Storm | Traffic Spike | Cache-Cold Stampede | Client Reconnect Loop |
|---|---|---|---|---|
| Inbound User Traffic | Static | Increasing | Static | Static |
| Attempt/Logical Ratio | High (>2.0) | Low (~1.0) | Low (~1.0) | N/A (TCP level) |
| Arrival Pattern | Clustered/Fixed | Stochastic | Sharp Step Function | Periodic/Fixed |
| Correlation | Error Rate Leads | External Event | Deploy/TTL Expiry | Deployment/Network |
A thundering herd problem (or cache stampede) often looks like a retry storm, but the mechanism is different: it's many unique requests hitting a backend simultaneously because a shared cache expired, not the same request being repeated.
The test that settles it, done safely
If you suspect retries are sustaining the outage, do not disable them globally. Manually editing production configs is dangerous. Instead, perform a scoped pressure relief test:
- Identify one non-critical caller or one specific route.
- Cap retries at 0 or 1 for that specific scope (via a feature flag or dynamic config).
- Observe:
- Success recovers and load drops: Retries were sustaining the outage.
- Load drops but errors persist: Retries were amplifying, but the dependency is still genuinely broken.
- Nothing changes: The load is coming from elsewhere (background workers, health checks, or a genuine traffic spike).
When retries are innocent and you should leave them alone
Should you retry on a 500 error? It depends. If the 500 is a deterministic application error (e.g., a null pointer), retrying is useless. If the 500 is a "Service Unavailable" or load-shedding signal, retrying without exponential backoff with jitter will trigger a storm.
Avoid cutting retries if:
- The amplification factor is near 1.0.
- The errors are deterministic (4xx).
- Your circuit breaker pattern has already tripped, meaning the budget is already bounding the damage.
What the evidence has to survive
Your postmortem shouldn't rely on a "hunch" that retries were the problem. It should document the two curves (logical vs. physical) and the resulting ratio. According to the RetryGuard paper (arXiv:2511.23278), the normalized retry rate rises sharply at a load factor of 1.0. This means any threshold between 0.95 and 1.1 is a robust trigger for automated circuit breaking—far more reliable than a fixed count alarm.
Why this matters now
In the era of AI-assisted engineering, this problem is accelerating. Large Language Models (LLMs) and agent frameworks often wrap tool calls in aggressive retry logic by default. When these agents are deployed at scale, they add new, invisible layers of amplification that aren't captured in traditional service maps.
How Operate Helps
Determining the amplification factor is trivial in theory but grueling at 3am. Operate is a self-hosted AI SRE platform that automates this evidence gathering. When an incident triggers, Operate:
- Correlates application traces with mesh counters and gateway logs.
- Calculates the amplification factor across every layer of your stack.
- Identifies the specific layer (SDK, Mesh, or Code) responsible for the surge.
- Drafts a PR to tune the retry policy or implement a circuit breaker, providing the evidence needed for a human to hit "Merge."
FAQ
Is a retry storm the same as a thundering herd? No. A thundering herd (or cache stampede) occurs when many different clients request the same resource at once (often after a cache eviction). A retry storm occurs when the same clients repeat the same failed requests multiple times, amplifying existing load.
How do I calculate retry amplification factor? Divide the total number of physical attempts made to a dependency by the number of unique logical requests that initiated them. A ratio significantly above 1.0 indicates amplification.
How do I tell a retry storm from a traffic spike? Check your inbound user traffic. In a traffic spike, both user requests and dependency calls rise together. In a retry storm, dependency calls skyrocket while inbound user traffic remains relatively flat.
Should I disable retries during an incident? Only if you have confirmed high amplification. If retries are merely "reacting" to transient errors, disabling them will cause the success rate to drop without significantly reducing load on the downstream service.
What is a retry budget and how do I size one? A retry budget limits the total percentage of requests that can be retried (e.g., 5-10%). This prevents a service from more than doubling its outbound traffic, regardless of how many errors it receives.
Does exponential backoff with jitter prevent retry storms on its own? It helps by preventing synchronized "pulses" of traffic, but it does not reduce the total volume of work. If the downstream system is already at 100% capacity, even jittered retries can sustain an outage.
Sources & further reading
- According to Microsoft Learn, retry storms are a common anti-pattern where recovery logic becomes the source of failure.
- Research from Agoda Engineering demonstrates that nested retries can amplify traffic by over 500x in a 10-service chain.
- The RetryGuard paper (arXiv:2511.23278) provides the mathematical basis for using a 1.0 load factor threshold for automated retry control.
- Finagle's documentation defines the retry storm as a feedback loop born of good intentions.