hello
Use Case
Predictable retry intervals in microservices can lead to server overload during peak failures. Randomizing these intervals can mitigate such issues.
Current Solution
The existing ExponentialBackOff
mechanism predictably increases intervals, which can exacerbate server load during concurrent retries.
Proposed Enhancement
Propose adding a randomizationFactor
to ExponentialBackOff
to allow interval fluctuations, enhancing load distribution and reducing server stress.
Benefits
- Collision Reduction: Randomizing intervals reduces peak load times by avoiding simultaneous retries.
- Load Smoothing: Helps even out the demands on backend services, enhancing overall system stability.
- Greater Control: Allows for precise adjustments to backoff settings, accommodating diverse application environments.
Implementation
This enhancement is designed to be fully backward compatible.
Here is the proposed change to the ExponentialBackOff
class:
package org.springframework.util.backoff;
import org.springframework.util.Assert;
public class ExponentialBackOff implements BackOff {
// Existing fields...
private double randomizationFactor = 0.0; // Default to no randomization
// Existing constructors...
public ExponentialBackOff(long initialInterval, double multiplier, double randomizationFactor) {
checkMultiplier(multiplier);
this.initialInterval = initialInterval;
this.multiplier = multiplier;
this.randomizationFactor = randomizationFactor;
}
// Getter and setter for randomizationFactor...
private class ExponentialBackOffExecution implements BackOffExecution {
// Existing fields and methods...
private long applyRandomization(long interval) {
double random = (1 - randomizationFactor) + Math.random() * 2 * randomizationFactor;
return (long) (interval * random);
}
@Override
public long nextBackOff() {
long nextInterval = computeNextInterval();
return applyRandomization(nextInterval);
}
}
}
test code
@Test
void withRandomizationFactor() {
ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0, 0.5);
BackOffExecution execution = backOff.start();
long firstBackOff = execution.nextBackOff();
long secondBackOff = execution.nextBackOff();
// Check if the back off is within expected randomization range
assertThat(firstBackOff).isBetween(500L, 1500L);
assertThat(secondBackOff).isBetween(1000L, 3000L);
}
@Test
void randomizationFactorBounds() {
ExponentialBackOff backOff = new ExponentialBackOff();
assertThatIllegalArgumentException().isThrownBy(() ->
backOff.setRandomizationFactor(-0.1));
assertThatIllegalArgumentException().isThrownBy(() ->
backOff.setRandomizationFactor(1.1));
}
@Test
void randomizationEffectiveness() {
ExponentialBackOff backOff = new ExponentialBackOff(1000L, 2.0, 0.5);
BackOffExecution execution = backOff.start();
boolean different = false;
long previous = execution.nextBackOff();
for (int i = 0; i < 10; i++) {
long next = execution.nextBackOff();
if (previous != next) {
different = true;
break;
}
previous = next;
}
assertThat(different).isTrue();
}
Comment From: artembilan
Looks like a duplication of: https://github.com/spring-projects/spring-framework/issues/22009. We might still reconsider this for current state of things.
Comment From: jhoeller
A configurable jitter
property has been implemented as part of our core retry efforts (#34529) in the meantime. This is a milliseconds setting for a random runtime-determined value between interval - jitter
and interval + jitter
, rather than a jitter factor, but ultimately serving the same purpose. A configured multiplier applies to the interval as well as the jitter, effectively making it equivalent to a jitter factor. Please give it a try against 7.0 M7!
P.S.: Sorry for not noticing this issue earlier...