4 ms·
How Uber Protects Against Retry Storms
- aftbit 16d agoI'd be interested to hear other strategies in this space. I've done the naive thing of allowing retries everywhere, and gotten into retry storms. When I was next presented with the problem, I tried the other naive thing of only allowing retries from the very top level service, which led me to redoing absolutely tons of work for each failure. What's a nice middle path that doesn't add too much complexity?
- tregoning 16d agohttps://en.wikipedia.org/wiki/Exponential_backoff https://en.wikipedia.org/wiki/Exponential_backoff
- anonymars 16d agohttps://devblogs.microsoft.com/oldnewthing/20051107-20/?p=33433 https://devblogs.microsoft.com/oldnewthing/20051107-20/?p=33...
- applfanboysbgon 16d agoThis is trading a good developer experience for a bad user experience. There are situations where it makes sense to force manual retry, but there's no reason to apply one universal rule to all possible situations. Lack of considering nuance for your situation is just intellectual laziness.
- anonymars 16d agoMy point was that just throwing exponential backoffs at the retry problem is not a magic solution I don't follow how being cautious about avoiding multiplicative layers of backoffs is trading a good developer experience for a bad user experience. The described situation is an awful user experience. Simply adding a retry and calling it a day sounds like the easy developer experience at the expense of the user experience
- applfanboysbgon 16d ago> The described situation is an awful user experience Sure, the worst case scenario is. 99.9999% of the time, a transient error will actually just work on the first or second auto-retry and save your users the effort of paying attention and manually retrying things. This is especially prudent for background tasks where the failure may not be noticed right away; coming back to something fire-and-forget 30m later to see it never tried to finish is not a good user experience. > My point was that just throwing exponential backoffs at the retry problem is not a magic solution Nobody said it was. In fact, I suggested the exact opposite - a proper solution takes dev effort. Adhering to an iron rule of "just make them manually retry" is throwing your hands up and not even trying to solve the problem because laziness is convenient.
- anonymars 16d ago> Nobody said it was I responded to a post that merely linked to the Wikipedia article for exponential backoff (in response to "I'd be interested to hear other strategies in [protecting against retry storms]") The submitted article is precisely about the degenerate case and the difficult work of dealing with it > This approach works for transient or low-rate failures. However, during moderate or severe degradation, it becomes counterproductive. Aggressively retrying against an already struggling service increases load, accelerates failure, and amplifies retry traffic across upstream dependencies. What begins as a localized outage can quickly escalate into a stack-wide incident—ultimately degrading, or in the worst case, completely breaking, the end user experience.
- applfanboysbgon 16d agoYes, and then responds to that degenerate case by suggesting that you never automatically retry. It's like saying "you should never drive a car/take a flight/ride public transportation because it's gone wrong so many times". Things go wrong. You should absolutely consider the impact and what will happen when they go wrong, but the end takeaway to just never engage with them because they can go wrong is, frankly speaking, lazy and bad advice.
- mitxela 16d agoTitle is "Take it easy on the automatic retries" Microsoft breaks all Old New Thing links every few years so it's necessary to post the title so the right post can still be found.
- sroussey 16d agoYes, exponential back off and jitter are the first things to work on, and good if you don’t have a better signal (like loss of network). Also, a simple signal status server or queue system helps to keep global state such that everyone doesn’t retry all at once. If you have a central error rate server you can skip your retry based on the error rate (100% error rate, don’t retry, etc).
- sroussey 16d agoSo many variables, but the simple thing is to set things up like normal rate limiting (which you would want to do anyways). The one generating the errors passes back a retry time. You can add jitter here, tell low priority requests to wait longer, etc. BTW: do keep track of priority. It’s like having a database that gets flooded with connections and won’t allow new ones in—but will for admin users (btw, it did not used to be that way in the early days of MySQL).
- otterley 16d agohttps://aws.amazon.com/blogs/developer/introducing-retry-throttling/ https://aws.amazon.com/blogs/developer/introducing-retry-thr... (2016 -- 10 years ago!) https://builder.aws.com/content/3EumjoZascWd1oZiEgL8ORlv3qE/timeouts-retries-and-backoff-with-jitter https://builder.aws.com/content/3EumjoZascWd1oZiEgL8ORlv3qE/... (originally published 2020, republished 2026) https://docs.aws.amazon.com/sdkref/latest/guide/feature-retry-behavior.html#retry-quota-token-bucket https://docs.aws.amazon.com/sdkref/latest/guide/feature-retr... https://aws.amazon.com/blogs/developer/announcing-updated-retry-behavior-for-aws-sdks-and-tools/ https://aws.amazon.com/blogs/developer/announcing-updated-re... (2026)
- sfraxo 16d ago[flagged]
- CBLT 16d agoThere's a good amount of literature about this (check the other comments), but you can vastly simplify this into two things you need to do: 1. Your service that retries should have some retry budget. This is a good place to be "smart", because you can reason entirely locally instead of turning it into a distributed systems problem. The best library I've seen for this was doing Exponential Moving Average of requests per second sent down that pipe (not counting retries) and only allowing 20% more requests per second as retries, total. Each individual request could be retried 3 times. This was critical as it bounds the additional load from retries. 2. Whenever a service retries but has to give up, the error it sends to its callers should never be retried. There has to be some agreement that that HTTP code will never be retried. This prevents the multiplicative factor of retry on top of retry, which is why those storms can generate so much load. Everything else is nice-to-have, but those two alone should bound the total requests you get in a retry storm.
- otterley 16d ago> 2. Whenever a service retries but has to give up, the error it sends to its callers should never be retried. There has to be some agreement that that HTTP code will never be retried. This prevents the multiplicative factor of retry on top of retry, which is why those storms can generate so much load. Ooh, I like the idea of propagating "no retries" hints in the responses back upstream. Have you seen it implemented in the wild, or in public discussions about the practice?
- CBLT 16d agoI've only seen it in bigcorp cross-service typedefs, or in startup's code that re-implements the checks in every service.
- hxtk 16d agoIn gRPC, the statuses it returns in trailers can include arbitrary details, and Google has a well-known proto for common ones in `google/rpc/error_details.proto`. One such detail is RetryInfo [1]. When we implement retries where I work, the general rule is that if a request is suitable for retry, it should include the RetryInfo in the error status and use it as the base delay for the exponential backoff. The absence of that detail means don’t retry, and we have a client interceptor that parses the response status and retries according to that logic. 1: https://github.com/googleapis/googleapis/blob/bba4c646b1f85a4cd499207926865205caad0384/google/rpc/error_details.proto#L92 https://github.com/googleapis/googleapis/blob/bba4c646b1f85a...
- mandevil 16d agoAt $previousJob we implemented circuit breakers: centralize all requests to the foreign service (every call to service theta went through the service theta client which had some shared state so everything so we could keep track of requests) and then monitor, when error % got above a certain limit start to dump requests to a text file for sending in the future instead of now. And the centralized caller will send one message every time gap (we started at 30s) and as long as that errors out we keep writing. We did that because otherwise we would get 2x30 second timeouts to a dead service on every user interaction and it made for a terrible user experience. Keeping track and handling it smartly made the average user experience a lot better.
- cyberax 16d agoOne good option that is not (yet?) mentioned here is a deadline for retries. You can cap the request duration by, say, 500ms and pass the remaining time budget to downstream services. This can be done via an HTTP header and enforced by the middleware.
- konaraddi 16d agoDepending on the context, circuit breakers
- nirmeetimthebes 16d ago[flagged]
- jeffbee 16d agoJust limiting your retry budget to 1% of normal rates using a client-local token bucket with no distributed coordination will eliminate the possibility of long-lived retry storms.
- sisuo30390 16d ago[flagged]
- maxchisto 16d agoI'm suspicious of load shedding not mentioned in the article. Combine that with exp backoff in the caller and you got yourself a pretty robust starting point
- otterley 16d ago429 (and sometimes 503) errors returned by servers might well be a symptom of intentional load shedding. Perhaps it's just not explicitly called out as a server behavior that induces client retries.
- hxtk 16d agoI’d like to mention it since Uber has a really cool load shedder [1], also implemented similarly by Netflix [2] and failsafe-go [3]. It basically looks for points where more requests per second suddenly cause a significant increase in latency and calls that the concurrency limit. 1: https://www.uber.com/us/en/blog/cinnamon-using-century-old-tech-to-build-a-mean-load-shedder/ https://www.uber.com/us/en/blog/cinnamon-using-century-old-t... 2: https://github.com/Netflix/concurrency-limits https://github.com/Netflix/concurrency-limits 3: https://failsafe-go.dev/adaptive-limiter/ https://failsafe-go.dev/adaptive-limiter/
- whoevercares 16d agoToken bucket is all you need
- prologic 16d agoNot quite. A token. bucket alone would not prevent a retry storm if you have a chain of services A -> B -> C -> D with a failure in D, you'd end up still having A, B & C all performing retries as they cascade through the chain, albeit (yes) at your configured rate-limits (using token bucket), but you'd still end up with an amplification effect.
- prologic 16d agoSo, effectively if A → B → C → D and D is failing, C may retry D, but B and A are discouraged from retrying the whole chain. This is quite slever. I also really like the concept of an "Error Budget", inspired by SRE and SLO(s) no doubt :)
- BobbyTables2 16d agoI had a former colleague who would put a “retry 10x with sleeps” in each of A, B, C, D. None of these were even expected to fail. But the code was buggy as well, so after D being retried 10000 times, it’d eventually give up. Managed to convert a sub-second operation into a half hour affair.
- prologic 16d agoLOL
- Scoundreller 16d agoMeanwhile Google keeps giving me “please wait, do not reload page” walls, so I ctrl-r as rapidly as possible. Or is that the human test and response?
- UltraSane 16d agoThis feels like trying to reinvent Fibre Channel's flow control mechanism.
- iwontberude 16d ago[dead]
- whatever1 16d agoEasy. Take a larger cut from the driver for each retry.
- penguin_booze 16d agoIt's not related to the article content, but I can't help but notice the "diversity" in authorship grades: a senior staff, a (regular) senior, and a principal. I suppose grades are a key mechanism by which corporates keep their hamster wheels running. I'm thankful that, at my $WORK, we're all software engineers by title, from the lowly interns to the CTO. Still, everybody knows who's who and whom to talk to, depending on the gravity of the issue.
- high_na_euv 16d agoTitle should indicate work and responsibility, right? CTOs work is totally different from SE, so why s/he is SE? Lets make your janitor can be SE too
- orf 16d ago> I'm thankful that, at my $WORK, we're all software engineers by title, from the lowly interns to the CTO All engineers are equal, however some engineers are more equal than others?
- ahoka 16d agoAh, the classic tyranny of structurelessness.
- OJFord 16d ago> at my $WORK, we're all software engineers by title, from the lowly interns to the CTO. Still, everybody knows who's who and whom to talk to, depending on the gravity of the issue. It's not exactly conducive to knowing that if you're a new starter though, is it. I imagine it takes quite a while to get to know, especially outside of your immediate remit.
- tancop 16d agoPositions are better than titles for that. Compare "Principal Engineer" with no other info to "Software Engineer, Lead at Mobile UX". You know if they are your boss, not how big of a boss they are. That's way more important unless your organization is a chaotic mess where managers feel like they can order around people from a different team. And then you got bigger problems.
- cynicalsecurity 16d ago> Deepanshu Mehndiratta, Alok Srivastava, Vibhor Dhingra, Ankit Srivastava It's not AI that is going to replace you, folks. Btw is it a coincide or has Uber become a family business now?
- siscia 16d agoIt seems VERY cooperative. If you can afford that, with all the coordination costs that it comes from it, good. An alternative is just to let the downstream service own the retry logic. Too many requests? Just error out as soon as possible. Each team manages its budget and each other team adapts.
- nirui 16d agoI think the example is still too generic. First thing is, if your service has 7 call layers, maybe it's just too deep. Second thing is, I found that retry strategy works the best if you define it based on what the nodes are actually doing (instead of treating them as generic nodes). For example, if node D is a database failing a transaction, you may just configure it to retry the transaction instead of doing an application-initialized request resubmit, because the database probably knows better about why the transaction has failed than the application connected to it. Third thing is, retry is worth it only when progress has been and/or can still be made. If the resources is no longer available forever, then there's no point of retrying.
- zyngaro 16d agoTLDR: retry only if the error is owned by the immediate next hop (assuming you can determine ownership). The engineering is sound, but the article misses a important connection to queueing theory which shows in much simpler terms how retries can increase the instability of a system already under stress.