Skip to content
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

core: Update outlier detection max ejection logic. (1.49.x backport) #9550

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 11 additions & 8 deletions core/src/main/java/io/grpc/util/OutlierDetectionLoadBalancer.java
Expand Up @@ -654,10 +654,9 @@ void maybeUnejectOutliers(Long detectionTimerStartNanos) {
}

/**
* How many percent of the addresses would have their subchannels ejected if we proceeded
* with the next ejection.
* How many percent of the addresses have been ejected.
*/
double nextEjectionPercentage() {
double ejectionPercentage() {
if (trackerMap.isEmpty()) {
return 0;
}
Expand All @@ -669,7 +668,7 @@ void maybeUnejectOutliers(Long detectionTimerStartNanos) {
ejectedAddresses++;
}
}
return ((double)(ejectedAddresses + 1) / totalAddresses) * 100;
return ((double)ejectedAddresses / totalAddresses) * 100;
}
}

Expand Down Expand Up @@ -733,8 +732,10 @@ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos)
mean - stdev * (config.successRateEjection.stdevFactor / 1000f);

for (AddressTracker tracker : trackersWithVolume) {
// If an ejection now would take us past the max configured ejection percentage, stop here.
if (trackerMap.nextEjectionPercentage() > config.maxEjectionPercent) {
// If we are above the max ejection percentage, don't eject any more. This will allow the
// total ejections to go one above the max, but at the same time it assures at least one
// ejection, which the spec calls for. This behavior matches what Envoy proxy does.
if (trackerMap.ejectionPercentage() > config.maxEjectionPercent) {
return;
}

Expand Down Expand Up @@ -803,8 +804,10 @@ public void ejectOutliers(AddressTrackerMap trackerMap, long ejectionTimeNanos)

// If this address does not have enough volume to be considered, skip to the next one.
for (AddressTracker tracker : trackerMap.values()) {
// If an ejection now would take us past the max configured ejection percentage stop here.
if (trackerMap.nextEjectionPercentage() > config.maxEjectionPercent) {
// If we are above the max ejection percentage, don't eject any more. This will allow the
// total ejections to go one above the max, but at the same time it assures at least one
// ejection, which the spec calls for. This behavior matches what Envoy proxy does.
if (trackerMap.ejectionPercentage() > config.maxEjectionPercent) {
return;
}

Expand Down
Expand Up @@ -574,10 +574,11 @@ public void successRateTwoOutliers() {
}

/**
* Two outliers. but only one gets ejected because we have reached the max ejection percentage.
* Three outliers, second one ejected even if ejecting it goes above the max ejection percentage,
* as this matches Envoy behavior. The third one should not get ejected.
*/
@Test
public void successRateTwoOutliers_maxEjectionPercentage() {
public void successRateThreeOutliers_maxEjectionPercentage() {
OutlierDetectionLoadBalancerConfig config = new OutlierDetectionLoadBalancerConfig.Builder()
.setMaxEjectionPercent(20)
.setSuccessRateEjection(
Expand All @@ -591,7 +592,8 @@ public void successRateTwoOutliers_maxEjectionPercentage() {

generateLoad(ImmutableMap.of(
subchannel1, Status.DEADLINE_EXCEEDED,
subchannel2, Status.DEADLINE_EXCEEDED), 7);
subchannel2, Status.DEADLINE_EXCEEDED,
subchannel3, Status.DEADLINE_EXCEEDED), 7);

// Move forward in time to a point where the detection timer has fired.
forwardTime(config);
Expand All @@ -603,10 +605,7 @@ public void successRateTwoOutliers_maxEjectionPercentage() {
: 0;
}

// Even if all subchannels were failing, we should have not ejected more than the configured
// maximum percentage.
assertThat((double) totalEjected / servers.size()).isAtMost(
(double) config.maxEjectionPercent / 100);
assertThat(totalEjected).isEqualTo(2);
}


Expand Down