Jitter throttling client retries so clients do not retry in lockstep - #4207
Open
1991santhu wants to merge 1 commit into
Open
Jitter throttling client retries so clients do not retry in lockstep#42071991santhu wants to merge 1 commit into
1991santhu wants to merge 1 commit into
Conversation
The retry backoff starts at a fixed 500ms, so every client that loses the throttling server computes an identical delay sequence and retries at the same instants. The clients are already synchronised by construction: they fail together because the server they share became unreachable. Each retry round therefore arrives as a burst against a service that exists to limit request rate, and the burst repeats as the sequence doubles. Randomise the initial delay. Every subsequent delay is a multiple of it, so spreading the initial value decorrelates the whole sequence rather than just the first retry. The random amount is added to the 500ms floor rather than centred on it, so a client never retries sooner than the fixed delay already allowed. This mirrors MysqlMultiActiveLeaseArbiter, which randomises its initial delay the same way, and leaves the shared ExponentialBackoff utility untouched: its maxWait is a total budget, and pollers such as AsyncConverter1to1Test rely on that budget admitting a predictable number of attempts. Signed-off-by: Santhosh Kumar Somarapu <somarapu.santhosh91@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The retry backoff in
RedirectAwareRestClientRequestSenderstarts at a fixed 500ms, so every client that loses the throttling server computes an identical delay sequence and retries at the same instants.These clients are already synchronised by construction. They fail together because the server they share became unreachable, so each retry round arrives at the throttling service as a burst, and the burst repeats as the sequence doubles. The service exists to limit request rate, which makes it a bad thing to hammer while it is recovering.
The fix randomises the initial delay:
Every subsequent delay is a multiple of the initial one, since
ExponentialBackoffcomputesnextDelay = alpha * nextDelay + 1. So randomising the initial value decorrelates the whole sequence rather than just the first retry.The random amount is added to the 500ms floor rather than centred on it, so a client never retries the throttling service sooner than the fixed delay already allowed.
Why the call site and not
ExponentialBackoffI looked at fixing this in the shared utility and decided against it, for three reasons.
maxWaitthere is a total budget rather than a per-delay cap.awaitNextRetryaccumulatestotalWait += nextDelayand stops once it passesmaxWait. Extending each delay would consume that budget in fewer iterations, so callers that poll would get fewer attempts inside the same window.AsyncConverter1to1Testpolls withawaitCondition().maxWait(100L), which is a 100ms budget, and would start flaking.The utility is also dual-purpose, and the two uses want opposite things. As a poller (
StreamModelTaskRunner,FlowCatalog) it wants tight predictable intervals inside a budget. As a contention backoff it wants spread. One knob cannot serve both.And this repo already has an idiom for exactly this.
MysqlMultiActiveLeaseArbiterrandomises its own initial delay at the call site:Worth noting that precedent is partial: it randomises only the initial delay, and subsequent delays still double deterministically. Same shape as this change.
The two Zookeeper callers turned up in my initial search but use Curator's
ExponentialBackoffRetry, a different class that already randomises internally, so they are unaffected.Tests
No new unit test. The change is a constructor argument on a private field inside
CallbackDecorator, and asserting on a randomised delay would mean either exposing the backoff or asserting a distribution, neither of which seemed worth it for this. Happy to add one if a reviewer disagrees, and happy to take a[GOBBLIN-xxxx]ticket for the title if you would prefer one.Verified it compiles on JDK 8, which is what the CI workflows pin: