Skip to content

feat: add ExponentialJitterBackoff backoff strategy #256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

wandering-tales
Copy link

The new strategy is an extension of the default one that applies a jitter to avoid thundering herd.

@wandering-tales wandering-tales requested review from a team as code owners April 18, 2025 10:46
Copy link

hashicorp-cla-app bot commented Apr 18, 2025

CLA assistant check
All committers have signed the CLA.

@abhijeetviswa abhijeetviswa added enhancement go Pull requests that update Go code labels Apr 22, 2025
func ExponentialJitterBackoff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration {
baseBackoff := DefaultBackoff(min, max, attemptNum, resp)

if resp != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Default backoff already handles Retry-After. Is this check required again? ref

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is needed to avoid adding the jitter when the Retry-After header has been handled, that is returning the backoff returned by DefaultBackoff as it is.

}

// Seed randomization; it's OK to do it every time
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: To maintain consistency, can we use the same source like here?

Copy link
Author

@wandering-tales wandering-tales Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can factor out a new function to get the randomization source. Is that what you mean?

Comment on lines +655 to +658
jitter := rnd.Float64()*0.5 - 0.25 // Random value between -0.25 e +0.25
jitteredSleep := time.Duration(float64(baseBackoff) * (1.0 + jitter))

return clampDuration(jitteredSleep, min, max)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why is the jitter between -0.25 and +0.25 and not between min and max like in LinearJitterBackoff?

Also, since min and max mean different things between the two functions, it would be worthwhile adding it to the function documentation.

Copy link
Author

@wandering-tales wandering-tales Apr 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why is the jitter between -0.25 and +0.25 and not between min and max like in LinearJitterBackoff?

As far as I can see, the min and max arguments should be respectively treated as absolute lower and
upper limits for the computed backoff. In that regard, the LinearJitterBackoff function is the only one that treats them slightly differently. Quoting its documentation:

min and max here are not absolute values. The number to be multiplied by
the attempt number will be chosen at random from between them, thus they are
bounding the jitter.

Said that, to guarantee the exponential trend of the backoff handled by DefaultBackoff, I think the jitter should necessarily be within a "neighborhood", in a mathematical sense, of the value returned by DefaultBackoff. Hence, a random value is picked between the interval [ backoff - 0.25 * backoff, backoff + 0.25 * backoff ].

Regarding your other observation, instead:

Also, since min and max mean different things between the two functions, it would be worthwhile adding it to the function documentation.

I'll improve the documentation of the new function to explain how the arguments are used.

Comment on lines +661 to +670
func clampDuration(d, min, max time.Duration) time.Duration {
if d < min {
return min
}
if d > max {
return max
}
return d
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This function could've been inlined.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this function to exist is to fully test it in isolation.

As this clamping is performed for a random value, there's no guarantee of full test coverage.

@wandering-tales
Copy link
Author

@abhijeetviswa Sorry for the noise in the unit tests. Fixing those as well.

The new strategy is an extension of the default one that applies a
jitter to avoid thundering herd.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement go Pull requests that update Go code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants