From 03efbf48d0d3b6089c570d0acee2739143e9b172 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Thu, 16 Apr 2026 21:37:00 -0700 Subject: [PATCH 1/6] Add enableOverloadRetargeting API - Add enableOverloadRetargeting boolean option to MongoClientSettings and ConnectionString to allow the driver to route requests to a different replica set member on retries when the previously used server is overloaded - Add prose test 3.3 to verify that overload errors are retried on the same server when retargeting is disabled JAVA-6167 --- .../main/com/mongodb/ConnectionString.java | 35 +++++++++- .../main/com/mongodb/MongoClientSettings.java | 52 ++++++++++++++- .../internal/connection/OperationContext.java | 64 +++++++++++++------ .../mongodb/AbstractConnectionStringTest.java | 5 +- .../com/mongodb/ConnectionStringUnitTest.java | 13 +++- .../MongoClientSettingsSpecification.groovy | 2 + .../ServerDeprioritizationTest.java | 41 ++++++++---- .../ServerSelectionSelectionTest.java | 12 +++- .../main/com/mongodb/MongoClientOptions.java | 26 ++++++++ .../src/main/com/mongodb/MongoClientURI.java | 8 +++ .../MongoClientOptionsSpecification.groovy | 5 ++ .../MongoClientURISpecification.groovy | 15 ++++- .../internal/OperationExecutorImpl.java | 3 +- .../client/internal/MongoClientImpl.java | 2 +- .../client/internal/MongoClusterImpl.java | 28 ++++---- .../AbstractRetryableReadsProseTest.java | 37 +++++++++-- .../internal/MongoClusterSpecification.groovy | 2 +- 17 files changed, 282 insertions(+), 68 deletions(-) diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index 36ab59d469..d47d756aaa 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -20,6 +20,7 @@ import com.mongodb.annotations.Beta; import com.mongodb.annotations.Reason; import com.mongodb.connection.ClusterSettings; +import com.mongodb.connection.ClusterType; import com.mongodb.connection.ConnectionPoolSettings; import com.mongodb.connection.ServerMonitoringMode; import com.mongodb.connection.ServerSettings; @@ -276,6 +277,9 @@ *
  • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. * The maximum number of retry attempts when encountering a retryable overload error. * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
  • + *
  • {@code enableOverloadRetargeting=true|false}. If true the driver may route a request to a different server on a subsequent + * retry attempt if the previously used server is overloaded. Does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}. + * Defaults to false.
  • //TODO-SSLAV add see *
  • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
  • @@ -313,6 +317,7 @@ public class ConnectionString { private Boolean retryWrites; private Boolean retryReads; private Integer maxAdaptiveRetries; + private Boolean enableOverloadRetargeting; private ReadConcern readConcern; private Integer minConnectionPoolSize; @@ -564,6 +569,7 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient GENERAL_OPTIONS_KEYS.add("retrywrites"); GENERAL_OPTIONS_KEYS.add("retryreads"); GENERAL_OPTIONS_KEYS.add("maxadaptiveretries"); + GENERAL_OPTIONS_KEYS.add("enableoverloadretargeting"); GENERAL_OPTIONS_KEYS.add("appname"); @@ -718,6 +724,9 @@ private void translateOptions(final Map> optionsMap) { throw new IllegalArgumentException("maxAdaptiveRetries must be >= 0"); } break; + case "enableoverloadretargeting": + enableOverloadRetargeting = parseBoolean(value, "enableoverloadretargeting"); + break; case "uuidrepresentation": uuidRepresentation = createUuidRepresentation(value); break; @@ -1511,6 +1520,29 @@ public Integer getMaxAdaptiveRetries() { return maxAdaptiveRetries; } + /** + * Gets whether overload retargeting is enabled. + * + *

    When enabled, the previously selected servers on which attempts failed with an error + * {@linkplain MongoException#hasErrorLabel(String) having} + * the {@value MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} label may be deprioritized during + * server selection on subsequent retry attempts. This applies to reads when retryReads is enabled, + * and to writes when retryWrites is enabled.

    + * + *

    This setting does not take effect for + * {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}.

    + * + *

    Defaults to {@code false}.

    + * + * @return the enableOverloadRetargeting value, or null if not set + * @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean) + * @since 5.7 + */ + @Nullable + public Boolean getEnableOverloadRetargeting() { + return enableOverloadRetargeting; + } + /** * Gets the minimum connection pool size specified in the connection string. * @return the minimum connection pool size @@ -1825,6 +1857,7 @@ public boolean equals(final Object o) { && Objects.equals(retryWrites, that.retryWrites) && Objects.equals(retryReads, that.retryReads) && Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries) + && Objects.equals(enableOverloadRetargeting, that.enableOverloadRetargeting) && Objects.equals(readConcern, that.readConcern) && Objects.equals(minConnectionPoolSize, that.minConnectionPoolSize) && Objects.equals(maxConnectionPoolSize, that.maxConnectionPoolSize) @@ -1856,7 +1889,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { return Objects.hash(credential, isSrvProtocol, hosts, database, collection, directConnection, readPreference, - writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime, + writeConcern, retryWrites, retryReads, maxAdaptiveRetries, enableOverloadRetargeting, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime, maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, timeout, socketTimeout, sslEnabled, sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency, serverMonitoringMode, applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts, proxyHost, diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index c1b3c4a069..9dcdcadfbc 100644 --- a/driver-core/src/main/com/mongodb/MongoClientSettings.java +++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java @@ -25,6 +25,7 @@ import com.mongodb.client.model.geojson.codecs.GeoJsonCodecProvider; import com.mongodb.client.model.mql.ExpressionCodecProvider; import com.mongodb.connection.ClusterSettings; +import com.mongodb.connection.ClusterType; import com.mongodb.connection.ConnectionPoolSettings; import com.mongodb.connection.ServerSettings; import com.mongodb.connection.SocketSettings; @@ -96,6 +97,7 @@ public final class MongoClientSettings { private final boolean retryReads; @Nullable private final Integer maxAdaptiveRetries; + private final boolean enableOverloadRetargeting; private final ReadConcern readConcern; private final MongoCredential credential; private final TransportSettings transportSettings; @@ -219,6 +221,7 @@ public static final class Builder { private boolean retryReads = true; @Nullable private Integer maxAdaptiveRetries; + private boolean enableOverloadRetargeting = false; private ReadConcern readConcern = ReadConcern.DEFAULT; private CodecRegistry codecRegistry = MongoClientSettings.getDefaultCodecRegistry(); private TransportSettings transportSettings; @@ -261,6 +264,7 @@ private Builder(final MongoClientSettings settings) { retryWrites = settings.getRetryWrites(); retryReads = settings.getRetryReads(); maxAdaptiveRetries = settings.getMaxAdaptiveRetries(); + enableOverloadRetargeting = settings.getEnableOverloadRetargeting(); readConcern = settings.getReadConcern(); credential = settings.getCredential(); uuidRepresentation = settings.getUuidRepresentation(); @@ -323,6 +327,10 @@ public Builder applyConnectionString(final ConnectionString connectionString) { if (connectionString.getMaxAdaptiveRetries() != null) { maxAdaptiveRetries = connectionString.getMaxAdaptiveRetries(); } + Boolean enableOverloadRetargetingValue = connectionString.getEnableOverloadRetargeting(); + if (enableOverloadRetargetingValue != null) { + enableOverloadRetargeting = enableOverloadRetargetingValue; + } if (connectionString.getUuidRepresentation() != null) { uuidRepresentation = connectionString.getUuidRepresentation(); } @@ -559,6 +567,30 @@ public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) { return this; } + /** + * Sets whether to enable overload retargeting. + * + *

    When enabled, the previously selected servers on which attempts failed with an error + * {@linkplain MongoException#hasErrorLabel(String) having} + * the {@value MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} label may be deprioritized during + * server selection on subsequent retry attempts. This applies to reads when + * {@linkplain #retryReads(boolean) retryReads} is enabled, and to writes when + * {@linkplain #retryWrites(boolean) retryWrites} is enabled.

    + * + *

    This setting does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}.

    + * + *

    Defaults to {@code false}.

    + * + * @param enableOverloadRetargeting whether to enable overload retargeting. + * @return this + * @see #getEnableOverloadRetargeting() + * @since 5.7 + */ + public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) { + this.enableOverloadRetargeting = enableOverloadRetargeting; + return this; + } + /** * Sets the read concern. * @@ -933,6 +965,18 @@ public Integer getMaxAdaptiveRetries() { return maxAdaptiveRetries; } + /** + * Returns whether overload retargeting is enabled. + * See {@link Builder#enableOverloadRetargeting(boolean)} for more information. + * + * @return the enableOverloadRetargeting value + * @see Builder#enableOverloadRetargeting(boolean) + * @since 5.7 + */ + public boolean getEnableOverloadRetargeting() { + return enableOverloadRetargeting; + } + /** * The read concern to use. * @@ -1207,6 +1251,7 @@ public boolean equals(final Object o) { return retryWrites == that.retryWrites && retryReads == that.retryReads && Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries) + && enableOverloadRetargeting == that.enableOverloadRetargeting && heartbeatSocketTimeoutSetExplicitly == that.heartbeatSocketTimeoutSetExplicitly && heartbeatConnectTimeoutSetExplicitly == that.heartbeatConnectTimeoutSetExplicitly && Objects.equals(readPreference, that.readPreference) @@ -1236,7 +1281,8 @@ public boolean equals(final Object o) { @Override public int hashCode() { - return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, credential, transportSettings, + return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, enableOverloadRetargeting, readConcern, + credential, transportSettings, commandListeners, codecRegistry, loggerSettings, clusterSettings, socketSettings, heartbeatSocketSettings, connectionPoolSettings, serverSettings, sslSettings, applicationName, compressorList, uuidRepresentation, serverApi, autoEncryptionSettings, heartbeatSocketTimeoutSetExplicitly, @@ -1252,6 +1298,7 @@ public String toString() { + ", retryWrites=" + retryWrites + ", retryReads=" + retryReads + ", maxAdaptiveRetries=" + maxAdaptiveRetries + + ", enableOverloadRetargeting=" + enableOverloadRetargeting + ", readConcern=" + readConcern + ", credential=" + credential + ", transportSettings=" + transportSettings @@ -1281,8 +1328,9 @@ private MongoClientSettings(final Builder builder) { readPreference = builder.readPreference; writeConcern = builder.writeConcern; retryWrites = builder.retryWrites; - maxAdaptiveRetries = builder.maxAdaptiveRetries; retryReads = builder.retryReads; + maxAdaptiveRetries = builder.maxAdaptiveRetries; + enableOverloadRetargeting = builder.enableOverloadRetargeting; readConcern = builder.readConcern; credential = builder.credential; transportSettings = builder.transportSettings; diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java index 71352abee6..adea5d3ba0 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java +++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java @@ -62,14 +62,14 @@ public class OperationContext { private Span tracingSpan; public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, - @Nullable final ServerApi serverApi) { + @Nullable final ServerApi serverApi) { this(requestContext, sessionContext, timeoutContext, TracingManager.NO_OP, serverApi, null); } public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, - final TracingManager tracingManager, - @Nullable final ServerApi serverApi, - @Nullable final String operationName) { + final TracingManager tracingManager, + @Nullable final ServerApi serverApi, + @Nullable final String operationName) { this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), tracingManager, serverApi, @@ -77,6 +77,18 @@ public OperationContext(final RequestContext requestContext, final SessionContex null); } + public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, + final TracingManager tracingManager, + @Nullable final ServerApi serverApi, + @Nullable final String operationName, + final ServerDeprioritization serverDeprioritization) { + this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, serverDeprioritization, + tracingManager, + serverApi, + operationName, + null); + } + static OperationContext simpleOperationContext( final TimeoutSettings timeoutSettings, @Nullable final ServerApi serverApi) { return new OperationContext( @@ -86,7 +98,7 @@ static OperationContext simpleOperationContext( TracingManager.NO_OP, serverApi, null - ); + ); } public static OperationContext simpleOperationContext(final TimeoutContext timeoutContext) { @@ -119,7 +131,8 @@ public OperationContext withOperationName(final String operationName) { * It is a temporary solution to handle cases where deprioritization state persists across operations. */ public OperationContext withNewServerDeprioritization() { - return new OperationContext(id, requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), tracingManager, serverApi, + return new OperationContext(id, requestContext, sessionContext, timeoutContext, + new ServerDeprioritization(serverDeprioritization.enableOverloadRetargeting), tracingManager, serverApi, operationName, tracingSpan); } @@ -163,14 +176,14 @@ public void setTracingSpan(final Span tracingSpan) { } private OperationContext(final long id, - final RequestContext requestContext, - final SessionContext sessionContext, - final TimeoutContext timeoutContext, - final ServerDeprioritization serverDeprioritization, - final TracingManager tracingManager, - @Nullable final ServerApi serverApi, - @Nullable final String operationName, - @Nullable final Span tracingSpan) { + final RequestContext requestContext, + final SessionContext sessionContext, + final TimeoutContext timeoutContext, + final ServerDeprioritization serverDeprioritization, + final TracingManager tracingManager, + @Nullable final ServerApi serverApi, + @Nullable final String operationName, + @Nullable final Span tracingSpan) { this.id = id; this.serverDeprioritization = serverDeprioritization; @@ -206,7 +219,8 @@ public OperationContext withConnectionEstablishmentSessionContext() { } public OperationContext withMinRoundTripTime(final ServerDescription serverDescription) { - return withTimeoutContext(timeoutContext.withMinRoundTripTime(TimeUnit.NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos()))); + return withTimeoutContext( + timeoutContext.withMinRoundTripTime(TimeUnit.NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos()))); } public OperationContext withOverride(final TimeoutContextOverride timeoutContextOverrideFunction) { @@ -219,11 +233,17 @@ public static final class ServerDeprioritization { @Nullable private ClusterType clusterType; private final Set deprioritized; + private final boolean enableOverloadRetargeting; + + public ServerDeprioritization() { + this(false); + } - private ServerDeprioritization() { - candidate = null; - deprioritized = new HashSet<>(); - clusterType = null; + public ServerDeprioritization(final boolean enableOverloadRetargeting) { + this.enableOverloadRetargeting = enableOverloadRetargeting; + this.candidate = null; + this.deprioritized = new HashSet<>(); + this.clusterType = null; } /** @@ -253,7 +273,8 @@ public void onAttemptFailure(final Throwable failure) { // As per spec: sharded clusters deprioritize on any error, other topologies only on overload boolean isSystemOverloadedError = failure instanceof MongoException && ((MongoException) failure).hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL); - if (clusterType == ClusterType.SHARDED || isSystemOverloadedError) { + + if (clusterType == ClusterType.SHARDED || (isSystemOverloadedError && enableOverloadRetargeting)) { deprioritized.add(candidate); } } @@ -303,6 +324,7 @@ public List select(final ClusterDescription clusterDescriptio } } - public interface TimeoutContextOverride extends Function {} + public interface TimeoutContextOverride extends Function { + } } diff --git a/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java b/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java index 8aa0b7d5a9..4be89f4d9a 100644 --- a/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java +++ b/driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java @@ -112,7 +112,7 @@ protected void testValidOptions() { if (option.getKey().equals("authmechanism")) { String expected = option.getValue().asString().getValue(); - if (expected.equals("MONGODB-CR")) { + if (expected.equals("MONGODB-CR")) { assertNotNull(connectionString.getCredential()); assertNull(connectionString.getCredential().getAuthenticationMechanism()); } else { @@ -125,6 +125,9 @@ protected void testValidOptions() { } else if (option.getKey().equalsIgnoreCase("maxadaptiveretries")) { int expected = option.getValue().asInt32().getValue(); assertEquals(expected, connectionString.getMaxAdaptiveRetries().intValue()); + } else if (option.getKey().equalsIgnoreCase("enableoverloadretargeting")) { + boolean expected = option.getValue().asBoolean().getValue(); + assertEquals(expected, connectionString.getEnableOverloadRetargeting().booleanValue()); } else if (option.getKey().equalsIgnoreCase("replicaset")) { String expected = option.getValue().asString().getValue(); assertEquals(expected, connectionString.getRequiredReplicaSetName()); diff --git a/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java b/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java index c40ea5a0ab..e0803bee6e 100644 --- a/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java +++ b/driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java @@ -46,7 +46,8 @@ void defaults() { @ParameterizedTest @ValueSource(strings = { "serverMonitoringMode=stream", - "maxAdaptiveRetries=42" + "maxAdaptiveRetries=42", + "enableOverloadRetargeting=true" }) void equalAndHashCode(final String connectionStringOptions) { ConnectionString default1 = new ConnectionString(DEFAULT_OPTIONS); @@ -129,4 +130,14 @@ void maxAdaptiveRetries() { () -> new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=invalid")) ); } + + @Test + void enableOverloadRetargeting() { + assertAll( + () -> assertNull(new ConnectionString("mongodb://localhost/").getEnableOverloadRetargeting()), + () -> assertEquals(false, new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=false").getEnableOverloadRetargeting()), + () -> assertEquals(true, new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=true").getEnableOverloadRetargeting()), + () -> assertNull(new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=foos").getEnableOverloadRetargeting()) + ); + } } diff --git a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy index d4b59f0cb5..57995d2651 100644 --- a/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy @@ -576,6 +576,7 @@ class MongoClientSettingsSpecification extends Specification { def actual = MongoClientSettings.Builder.declaredFields.grep { !it.synthetic } *.name.sort() def expected = ['applicationName', 'autoEncryptionSettings', 'clusterSettingsBuilder', 'codecRegistry', 'commandListeners', 'compressorList', 'connectionPoolSettingsBuilder', 'contextProvider', 'credential', 'dnsClient', + 'enableOverloadRetargeting', 'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'loggerSettingsBuilder', 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern', 'readPreference', 'retryReads', @@ -595,6 +596,7 @@ class MongoClientSettingsSpecification extends Specification { 'applyToConnectionPoolSettings', 'applyToLoggerSettings', 'applyToServerSettings', 'applyToSocketSettings', 'applyToSslSettings', 'autoEncryptionSettings', 'build', 'codecRegistry', 'commandListenerList', 'compressorList', 'contextProvider', 'credential', 'dnsClient', + 'enableOverloadRetargeting', 'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern', 'readPreference', diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java index 3c1a7aad39..9ac2bbe7d4 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerDeprioritizationTest.java @@ -41,8 +41,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.mongodb.ClusterFixture.TIMEOUT_SETTINGS; -import static com.mongodb.ClusterFixture.createOperationContext; +import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; @@ -62,11 +61,13 @@ final class ServerDeprioritizationTest { private static final ClusterDescription SHARDED_CLUSTER = multipleModeClusterDescription(ClusterType.SHARDED); private static final ClusterDescription UNKNOWN_CLUSTER = multipleModeClusterDescription(ClusterType.UNKNOWN); private static final List CLUSTERS = asList(SHARDED_CLUSTER, REPLICA_SET_CLUSTER, UNKNOWN_CLUSTER); + private static final RuntimeException RUNTIME_EXCEPTION = new RuntimeException(); + private static final MongoException MONGO_EXCEPTION_NO_LABEL = new MongoException(0, "test"); private ServerDeprioritization serverDeprioritization; @BeforeEach void beforeEach() { - serverDeprioritization = createOperationContext(TIMEOUT_SETTINGS).getServerDeprioritization(); + serverDeprioritization = new OperationContext.ServerDeprioritization(true); } private static Stream selectNoneDeprioritized() { @@ -105,8 +106,8 @@ void selectNoneDeprioritizedSingleServerCluster(final ClusterType clusterType) { private static Stream deprioritizableClusters() { return Stream.of( - of(SHARDED_CLUSTER, new RuntimeException()), - of(SHARDED_CLUSTER, new MongoException(0, "test")), + of(SHARDED_CLUSTER, RUNTIME_EXCEPTION), + of(SHARDED_CLUSTER, MONGO_EXCEPTION_NO_LABEL), of(REPLICA_SET_CLUSTER, createSystemOverloadedError()), of(UNKNOWN_CLUSTER, createSystemOverloadedError()) ); @@ -204,7 +205,7 @@ void onAttemptFailureIgnoresIfPoolClearedException() { @Test void onAttemptFailureDoesNotThrowIfNoCandidate() { - assertDoesNotThrow(() -> serverDeprioritization.onAttemptFailure(new RuntimeException())); + assertDoesNotThrow(() -> serverDeprioritization.onAttemptFailure(RUNTIME_EXCEPTION)); } @ParameterizedTest @@ -214,20 +215,32 @@ void onAttemptFailureIgnoresIfNonShardedWithoutOverloadError(final ClusterType c ServerSelector selector = createAssertingSelector(ALL_SERVERS, singletonList(SERVER_A)); assertAll(() -> { - serverDeprioritization.updateCandidate(SERVER_B.getAddress(), clusterType); - serverDeprioritization.onAttemptFailure(new RuntimeException()); + deprioritize(clusterType, RUNTIME_EXCEPTION, SERVER_B); assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), - "Expected no deprioritization for " + clusterType + " with RuntimeException"); - }, () -> { - serverDeprioritization = createOperationContext(TIMEOUT_SETTINGS).getServerDeprioritization(); - serverDeprioritization.updateCandidate(SERVER_B.getAddress(), clusterType); - serverDeprioritization.onAttemptFailure(new MongoException(1, "error")); + format("Expected no deprioritization for %s with RuntimeException", clusterType)); + }, + () -> { + deprioritize(clusterType, MONGO_EXCEPTION_NO_LABEL, SERVER_B); assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), - "Expected no deprioritization for " + clusterType + " with no SystemOverloadedError MongoException"); + format("Expected no deprioritization for %s with MongoException without SystemOverloadedError", clusterType)); } ); } + @ParameterizedTest + @EnumSource(value = ClusterType.class, names = "SHARDED", mode = EnumSource.Mode.EXCLUDE) + void onAttemptFailureIgnoresIfNonShardedWithOverloadErrorAndDisabledOverloadRetargeting(final ClusterType clusterType) { + ClusterDescription cluster = multipleModeClusterDescription(clusterType); + ServerSelector selector = createAssertingSelector(ALL_SERVERS, singletonList(SERVER_A)); + + ServerDeprioritization serverDeprioritization = new OperationContext.ServerDeprioritization(false); + serverDeprioritization.updateCandidate(SERVER_B.getAddress(), clusterType); + serverDeprioritization.onAttemptFailure(createSystemOverloadedError()); + + assertEquals(singletonList(SERVER_A), serverDeprioritization.apply(selector).select(cluster), + format("Expected no deprioritization when overloadRetargeting is disabled for %s with SystemOverloadedError", clusterType)); + } + private void deprioritize(final ClusterType clusterType, final Throwable exception, final ServerDescription... serverDescriptions) { for (ServerDescription serverDescription : serverDescriptions) { serverDeprioritization.updateCandidate(serverDescription.getAddress(), clusterType); diff --git a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java index ed8f6fa955..5d6b5e0e1e 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/connection/ServerSelectionSelectionTest.java @@ -35,8 +35,10 @@ import com.mongodb.connection.ServerSettings; import com.mongodb.connection.ServerType; import com.mongodb.event.ServerDescriptionChangedEvent; +import com.mongodb.internal.IgnorableRequestContext; import com.mongodb.internal.TimeoutContext; import com.mongodb.internal.mockito.MongoMockito; +import com.mongodb.internal.observability.micrometer.TracingManager; import com.mongodb.internal.selector.ReadPreferenceServerSelector; import com.mongodb.internal.selector.WritableServerSelector; import com.mongodb.internal.time.Timeout; @@ -297,8 +299,14 @@ private static List extractDeprioritizedServerAddresses(final Bso private OperationContext createOperationContext() { OperationContext operationContext = - OperationContext.simpleOperationContext( - new TimeoutContext(TIMEOUT_SETTINGS.withServerSelectionTimeoutMS(0))); + new OperationContext( + IgnorableRequestContext.INSTANCE, + NoOpSessionContext.INSTANCE, + new TimeoutContext(TIMEOUT_SETTINGS.withServerSelectionTimeoutMS(0)), + TracingManager.NO_OP, + null, + null, + new OperationContext.ServerDeprioritization(true)); OperationContext.ServerDeprioritization serverDeprioritization = operationContext.getServerDeprioritization(); for (ServerAddress address : extractDeprioritizedServerAddresses(definition)) { serverDeprioritization.updateCandidate(address, clusterDescription.getType()); diff --git a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java index fe7b827d36..8cc502942d 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java @@ -488,6 +488,18 @@ public Integer getMaxAdaptiveRetries() { return wrapped.getMaxAdaptiveRetries(); } + /** + * Returns whether overload retargeting is enabled. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information. + * + * @return the enableOverloadRetargeting value + * @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean) + * @since 5.7 + */ + public boolean getEnableOverloadRetargeting() { + return wrapped.getEnableOverloadRetargeting(); + } + /** *

    The read concern to use.

    * @@ -1093,6 +1105,20 @@ public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) { return this; } + /** + * Sets whether to enable overload retargeting. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information. + * + * @param enableOverloadRetargeting whether to enable overload retargeting + * @return {@code this} + * @see #getEnableOverloadRetargeting() + * @since 5.7 + */ + public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) { + wrapped.enableOverloadRetargeting(enableOverloadRetargeting); + return this; + } + /** * Sets the read concern. * diff --git a/driver-legacy/src/main/com/mongodb/MongoClientURI.java b/driver-legacy/src/main/com/mongodb/MongoClientURI.java index 5d129bbd07..8c7ff52efd 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientURI.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientURI.java @@ -218,6 +218,9 @@ *
  • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. * The maximum number of retry attempts when encountering a retryable overload error. * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
  • + *
  • {@code enableOverloadRetargeting=true|false}. If true the driver may route a request to a different server on a subsequent + * retry attempt if the previously used server is overloaded. Does not take effect for + * {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}. Defaults to false.
  • *
  • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientOptions#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
  • @@ -390,6 +393,11 @@ public MongoClientOptions getOptions() { builder.maxAdaptiveRetries(maxAdaptiveRetries); } + Boolean enableOverloadRetargeting = proxied.getEnableOverloadRetargeting(); + if (enableOverloadRetargeting != null) { + builder.enableOverloadRetargeting(enableOverloadRetargeting); + } + Integer maxConnectionPoolSize = proxied.getMaxConnectionPoolSize(); if (maxConnectionPoolSize != null) { builder.connectionsPerHost(maxConnectionPoolSize); diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy index 723dddcc28..a386cd7f68 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientOptionsSpecification.groovy @@ -47,6 +47,7 @@ class MongoClientOptionsSpecification extends Specification { options.getRetryWrites() options.getRetryReads() options.getMaxAdaptiveRetries() == null + !options.getEnableOverloadRetargeting() options.getCodecRegistry() == MongoClientSettings.defaultCodecRegistry options.getUuidRepresentation() == UuidRepresentation.UNSPECIFIED options.getMinConnectionsPerHost() == 0 @@ -123,6 +124,7 @@ class MongoClientOptionsSpecification extends Specification { .retryWrites(true) .retryReads(false) .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .writeConcern(WriteConcern.JOURNALED) .readConcern(ReadConcern.MAJORITY) .minConnectionsPerHost(30) @@ -170,6 +172,7 @@ class MongoClientOptionsSpecification extends Specification { options.getRetryWrites() !options.getRetryReads() options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getServerSelectionTimeout() == 150 options.getTimeout() == 10_000 options.getMaxWaitTime() == 200 @@ -328,6 +331,7 @@ class MongoClientOptionsSpecification extends Specification { .applicationName('appName') .readPreference(ReadPreference.secondary()) .retryReads(true) + .enableOverloadRetargeting(true) .uuidRepresentation(UuidRepresentation.STANDARD) .writeConcern(WriteConcern.JOURNALED) .minConnectionsPerHost(30) @@ -630,6 +634,7 @@ class MongoClientOptionsSpecification extends Specification { .retryWrites(true) .retryReads(true) .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .uuidRepresentation(UuidRepresentation.STANDARD) .minConnectionsPerHost(30) .connectionsPerHost(500) diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy index fb2509554a..95ed3de49d 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy @@ -132,6 +132,7 @@ class MongoClientURISpecification extends Specification { + 'retryWrites=true&' + 'retryReads=true&' + 'maxAdaptiveRetries=42&' + + 'enableOverloadRetargeting=true&' + 'uuidRepresentation=csharpLegacy&' + 'appName=app1&' + 'timeoutMS=10000') @@ -160,6 +161,7 @@ class MongoClientURISpecification extends Specification { options.getRetryWrites() options.getRetryReads() options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getUuidRepresentation() == UuidRepresentation.C_SHARP_LEGACY options.getApplicationName() == 'app1' } @@ -181,6 +183,7 @@ class MongoClientURISpecification extends Specification { options.getRetryWrites() options.getRetryReads() options.getMaxAdaptiveRetries() == null + !options.getEnableOverloadRetargeting() options.getUuidRepresentation() == UuidRepresentation.UNSPECIFIED } @@ -192,6 +195,7 @@ class MongoClientURISpecification extends Specification { .retryWrites(true) .retryReads(true) .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true) .writeConcern(WriteConcern.JOURNALED) .minConnectionsPerHost(30) .connectionsPerHost(500) @@ -225,6 +229,7 @@ class MongoClientURISpecification extends Specification { options.getRetryWrites() options.getRetryReads() options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() options.getTimeout() == 10_000 options.getServerSelectionTimeout() == 150 options.getMaxWaitTime() == 200 @@ -321,7 +326,8 @@ class MongoClientURISpecification extends Specification { given: def uri = new MongoClientURI('mongodb://localhost/', MongoClientOptions.builder() .connectionsPerHost(200) - .maxAdaptiveRetries(42)) + .maxAdaptiveRetries(42) + .enableOverloadRetargeting(true)) when: def options = uri.getOptions() @@ -329,6 +335,7 @@ class MongoClientURISpecification extends Specification { then: options.getConnectionsPerHost() == 200 options.getMaxAdaptiveRetries() == 42 + options.getEnableOverloadRetargeting() } def 'should override MongoClientOptions builder'() { @@ -336,8 +343,9 @@ class MongoClientURISpecification extends Specification { def uri = new MongoClientURI('mongodb://localhost/?' + 'maxPoolSize=250' + '&maxAdaptiveRetries=43', - MongoClientOptions.builder(). - connectionsPerHost(200) + + '&enableOverloadRetargeting=false', + MongoClientOptions.builder() + .connectionsPerHost(200) .maxAdaptiveRetries(42)) when: @@ -346,6 +354,7 @@ class MongoClientURISpecification extends Specification { then: options.getConnectionsPerHost() == 250 options.getMaxAdaptiveRetries() == 43 + !options.getEnableOverloadRetargeting() } def 'should be equal to another MongoClientURI with the same string values'() { diff --git a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java index ef18c2c6b1..35ff27f79e 100644 --- a/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java +++ b/driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/OperationExecutorImpl.java @@ -216,7 +216,8 @@ private OperationContext getOperationContext(final RequestContext requestContext createTimeoutContext(session, timeoutSettings), TracingManager.NO_OP, mongoClient.getSettings().getServerApi(), - commandName); + commandName, + new OperationContext.ServerDeprioritization(mongoClient.getSettings().getEnableOverloadRetargeting())); } private ReadPreference getReadPreferenceForBinding(final ReadPreference readPreference, @Nullable final ClientSession session) { diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java index bbeb7419bc..9ba2139f18 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClientImpl.java @@ -105,7 +105,7 @@ public MongoClientImpl(final Cluster cluster, (SynchronousContextProvider) settings.getContextProvider(), autoEncryptionSettings == null ? null : createCrypt(settings, autoEncryptionSettings), this, operationExecutor, settings.getReadConcern(), settings.getReadPreference(), settings.getRetryReads(), - settings.getRetryWrites(), settings.getServerApi(), + settings.getRetryWrites(), settings.getEnableOverloadRetargeting(), settings.getServerApi(), new ServerSessionPool(cluster, TimeoutSettings.create(settings), settings.getServerApi()), TimeoutSettings.create(settings), settings.getUuidRepresentation(), settings.getWriteConcern(), new TracingManager(settings.getObservabilitySettings())); diff --git a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java index 920feb1f98..b5604a7a84 100644 --- a/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java +++ b/driver-sync/src/main/com/mongodb/client/internal/MongoClusterImpl.java @@ -104,6 +104,7 @@ final class MongoClusterImpl implements MongoCluster { private final ReadPreference readPreference; private final boolean retryReads; private final boolean retryWrites; + private final boolean enableOverloadRetargeting; @Nullable private final ServerApi serverApi; private final ServerSessionPool serverSessionPool; @@ -117,10 +118,9 @@ final class MongoClusterImpl implements MongoCluster { @Nullable final AutoEncryptionSettings autoEncryptionSettings, final Cluster cluster, final CodecRegistry codecRegistry, @Nullable final SynchronousContextProvider contextProvider, @Nullable final Crypt crypt, final Object originator, @Nullable final OperationExecutor operationExecutor, final ReadConcern readConcern, final ReadPreference readPreference, - final boolean retryReads, final boolean retryWrites, @Nullable final ServerApi serverApi, - final ServerSessionPool serverSessionPool, final TimeoutSettings timeoutSettings, final UuidRepresentation uuidRepresentation, - final WriteConcern writeConcern, - final TracingManager tracingManager) { + final boolean retryReads, final boolean retryWrites, final boolean enableOverloadRetargeting, + @Nullable final ServerApi serverApi, final ServerSessionPool serverSessionPool, final TimeoutSettings timeoutSettings, + final UuidRepresentation uuidRepresentation, final WriteConcern writeConcern, final TracingManager tracingManager) { this.autoEncryptionSettings = autoEncryptionSettings; this.cluster = cluster; this.codecRegistry = codecRegistry; @@ -132,6 +132,7 @@ final class MongoClusterImpl implements MongoCluster { this.readPreference = readPreference; this.retryReads = retryReads; this.retryWrites = retryWrites; + this.enableOverloadRetargeting = enableOverloadRetargeting; this.serverApi = serverApi; this.serverSessionPool = serverSessionPool; this.timeoutSettings = timeoutSettings; @@ -180,35 +181,35 @@ public Long getTimeout(final TimeUnit timeUnit) { @Override public MongoCluster withCodecRegistry(final CodecRegistry codecRegistry) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, tracingManager); } @Override public MongoCluster withReadPreference(final ReadPreference readPreference) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, tracingManager); } @Override public MongoCluster withWriteConcern(final WriteConcern writeConcern) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, tracingManager); } @Override public MongoCluster withReadConcern(final ReadConcern readConcern) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, timeoutSettings, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings, uuidRepresentation, writeConcern, tracingManager); } @Override public MongoCluster withTimeout(final long timeout, final TimeUnit timeUnit) { return new MongoClusterImpl(autoEncryptionSettings, cluster, codecRegistry, contextProvider, crypt, originator, - operationExecutor, readConcern, readPreference, retryReads, retryWrites, serverApi, serverSessionPool, + operationExecutor, readConcern, readPreference, retryReads, retryWrites, enableOverloadRetargeting, serverApi, serverSessionPool, timeoutSettings.withTimeout(timeout, timeUnit), uuidRepresentation, writeConcern, tracingManager); } @@ -530,7 +531,8 @@ private OperationContext getOperationContext(final ClientSession session, final createTimeoutContext(session, executorTimeoutSettings), tracingManager, serverApi, - commandName); + commandName, + new OperationContext.ServerDeprioritization(enableOverloadRetargeting)); } private RequestContext getRequestContext() { @@ -591,9 +593,9 @@ ClientSession getClientSession(@Nullable final ClientSession clientSessionFromOp * Create a tracing span for the given operation, and set it on operation context. * * @param actualClientSession the session that the operation is part of - * @param operationContext the operation context for the operation - * @param commandName the name of the command - * @param namespace the namespace of the command + * @param operationContext the operation context for the operation + * @param commandName the name of the command + * @param namespace the namespace of the command * @return the created span, or null if tracing is not enabled */ @Nullable diff --git a/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java index fde4675c63..be55cf2fd0 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java +++ b/driver-sync/src/test/functional/com/mongodb/client/AbstractRetryableReadsProseTest.java @@ -109,10 +109,9 @@ void retriesOnSameMongosWhenAnotherNotAvailable() { } /** - * - * 3.1 Retryable Reads Caused by Overload Errors Are Retried on a Different Replicaset Server When One is Available. + * + * 3.1 Retryable Reads Caused by Overload Errors Are Retried on a Different Replicaset Server When One is Available and enableOverloadRetargeting is enabled. */ - //TODO-BACKPRESSURE Slav Babanin JAVA-6167 add overloadRetargeting into tests. @Test void overloadErrorRetriedOnDifferentReplicaSetServer() throws InterruptedException, TimeoutException { //given @@ -133,6 +132,7 @@ void overloadErrorRetriedOnDifferentReplicaSetServer() throws InterruptedExcepti MongoClient client = createClient(getMongoClientSettingsBuilder() .retryReads(true) .readPreference(ReadPreference.primaryPreferred()) + .enableOverloadRetargeting(true) .addCommandListener(commandListener) .applyToClusterSettings(builder -> builder.addClusterListener(clusterListener)) .build())) { @@ -164,12 +164,8 @@ void overloadErrorRetriedOnDifferentReplicaSetServer() throws InterruptedExcepti * * 3.2 Retryable Reads Caused by Non-Overload Errors Are Retried on the Same Replicaset Server. */ - //TODO-BACKPRESSURE Slav Babanin JAVA-6167 add overloadRetargeting into tests. @Test void nonOverloadErrorRetriedOnSameReplicaSetServer() throws InterruptedException, TimeoutException { - //given - assumeTrue(serverVersionAtLeast(4, 4)); - assumeTrue(isDiscoverableReplicaSet()); BsonDocument configureFailPoint = BsonDocument.parse( "{\n" + " configureFailPoint: \"failCommand\",\n" @@ -180,6 +176,33 @@ void nonOverloadErrorRetriedOnSameReplicaSetServer() throws InterruptedException + " errorCode: 6\n" + " }\n" + "}\n"); + testRetriedOnTheSameServer(configureFailPoint); + } + + /** + * + * 3.3 Retryable Reads Caused by Overload Errors Are Retried on Same Replicaset Server When enableOverloadRetargeting is disabled. + */ + @Test + void overloadErrorRetriedOnSameReplicaSetServerWhenRetargetingDisabled() throws InterruptedException, TimeoutException { + BsonDocument configureFailPoint = BsonDocument.parse( + "{\n" + + " configureFailPoint: \"failCommand\",\n" + + " mode: { times: 1 },\n" + + " data: {\n" + + " failCommands: [\"find\"],\n" + + " errorLabels: ['" + RETRYABLE_ERROR_LABEL + "', '" + SYSTEM_OVERLOADED_ERROR_LABEL + "'],\n" + + " errorCode: 6\n" + + " }\n" + + "}\n"); + testRetriedOnTheSameServer(configureFailPoint); + } + + private void testRetriedOnTheSameServer(final BsonDocument configureFailPoint) throws InterruptedException, TimeoutException { + //given + assumeTrue(serverVersionAtLeast(4, 4)); + assumeTrue(isDiscoverableReplicaSet()); + TestCommandListener commandListener = new TestCommandListener(asList("commandFailedEvent", "commandSucceededEvent"), emptyList()); try (FailPoint ignored = FailPoint.enable(configureFailPoint, getPrimary()); MongoClient client = createClient(getMongoClientSettingsBuilder() diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy index c75a425559..34f46e7b00 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoClusterSpecification.groovy @@ -259,7 +259,7 @@ class MongoClusterSpecification extends Specification { MongoClusterImpl createMongoCluster(final MongoClientSettings settings, final OperationExecutor operationExecutor) { new MongoClusterImpl(null, cluster, settings.codecRegistry, null, null, originator, operationExecutor, settings.readConcern, settings.readPreference, settings.retryReads, settings.retryWrites, - null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation, + settings.enableOverloadRetargeting, null, serverSessionPool, TimeoutSettings.create(settings), settings.uuidRepresentation, settings.writeConcern, TracingManager.NO_OP) } } From 9fbf11b4784cf879011ea2da3ceab9cb1fa5edc9 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 20 Apr 2026 19:14:53 -0700 Subject: [PATCH 2/6] Change javadoc. --- .../src/main/com/mongodb/ConnectionString.java | 14 ++------------ .../src/main/com/mongodb/MongoClientURI.java | 3 ++- .../com/mongodb/MongoClientURISpecification.groovy | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index d47d756aaa..ba91afc19d 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -279,7 +279,7 @@ * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information. *
  • {@code enableOverloadRetargeting=true|false}. If true the driver may route a request to a different server on a subsequent * retry attempt if the previously used server is overloaded. Does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}. - * Defaults to false.
  • //TODO-SSLAV add see + * Defaults to false. See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information. *
  • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
  • @@ -1522,17 +1522,7 @@ public Integer getMaxAdaptiveRetries() { /** * Gets whether overload retargeting is enabled. - * - *

    When enabled, the previously selected servers on which attempts failed with an error - * {@linkplain MongoException#hasErrorLabel(String) having} - * the {@value MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} label may be deprioritized during - * server selection on subsequent retry attempts. This applies to reads when retryReads is enabled, - * and to writes when retryWrites is enabled.

    - * - *

    This setting does not take effect for - * {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}.

    - * - *

    Defaults to {@code false}.

    + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information. * * @return the enableOverloadRetargeting value, or null if not set * @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean) diff --git a/driver-legacy/src/main/com/mongodb/MongoClientURI.java b/driver-legacy/src/main/com/mongodb/MongoClientURI.java index 8c7ff52efd..425db80787 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientURI.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientURI.java @@ -220,7 +220,8 @@ * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information. *
  • {@code enableOverloadRetargeting=true|false}. If true the driver may route a request to a different server on a subsequent * retry attempt if the previously used server is overloaded. Does not take effect for - * {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}. Defaults to false.
  • + * {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}. Defaults to false. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information. *
  • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientOptions#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
  • diff --git a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy index 95ed3de49d..3de1f77b6d 100644 --- a/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy +++ b/driver-legacy/src/test/unit/com/mongodb/MongoClientURISpecification.groovy @@ -342,7 +342,7 @@ class MongoClientURISpecification extends Specification { given: def uri = new MongoClientURI('mongodb://localhost/?' + 'maxPoolSize=250' - + '&maxAdaptiveRetries=43', + + '&maxAdaptiveRetries=43' + '&enableOverloadRetargeting=false', MongoClientOptions.builder() .connectionsPerHost(200) From 5c25924c3f53cb87ff4aa6283ae8a362d66e1cf9 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 20 Apr 2026 19:42:27 -0700 Subject: [PATCH 3/6] Change javadoc. --- driver-core/src/main/com/mongodb/ConnectionString.java | 5 ++--- driver-legacy/src/main/com/mongodb/MongoClientURI.java | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index ba91afc19d..14af446bf0 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -277,9 +277,8 @@ *
  • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. * The maximum number of retry attempts when encountering a retryable overload error. * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
  • - *
  • {@code enableOverloadRetargeting=true|false}. If true the driver may route a request to a different server on a subsequent - * retry attempt if the previously used server is overloaded. Does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}. - * Defaults to false. See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information.
  • + *
  • {@code enableOverloadRetargeting=true|false}: Whether to enable overload retargeting. Defaults to false. + * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information.
  • *
  • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but * will change to "unspecified" in the next major release.
  • diff --git a/driver-legacy/src/main/com/mongodb/MongoClientURI.java b/driver-legacy/src/main/com/mongodb/MongoClientURI.java index 425db80787..20dc41396f 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientURI.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientURI.java @@ -218,9 +218,7 @@ *
  • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. * The maximum number of retry attempts when encountering a retryable overload error. * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
  • - *
  • {@code enableOverloadRetargeting=true|false}. If true the driver may route a request to a different server on a subsequent - * retry attempt if the previously used server is overloaded. Does not take effect for - * {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}. Defaults to false. + *
  • {@code enableOverloadRetargeting=true|false}: Whether to enable overload retargeting. Defaults to false. * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information.
  • *
  • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientOptions#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but From 90131d65ae9c695b9a6636718112db8d74e250e6 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 20 Apr 2026 19:49:26 -0700 Subject: [PATCH 4/6] Revert formatting changes. --- .../internal/connection/OperationContext.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java index adea5d3ba0..69cb5c5780 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java +++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java @@ -62,14 +62,14 @@ public class OperationContext { private Span tracingSpan; public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, - @Nullable final ServerApi serverApi) { + @Nullable final ServerApi serverApi) { this(requestContext, sessionContext, timeoutContext, TracingManager.NO_OP, serverApi, null); } public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, - final TracingManager tracingManager, - @Nullable final ServerApi serverApi, - @Nullable final String operationName) { + final TracingManager tracingManager, + @Nullable final ServerApi serverApi, + @Nullable final String operationName) { this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), tracingManager, serverApi, @@ -78,10 +78,10 @@ public OperationContext(final RequestContext requestContext, final SessionContex } public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext, - final TracingManager tracingManager, - @Nullable final ServerApi serverApi, - @Nullable final String operationName, - final ServerDeprioritization serverDeprioritization) { + final TracingManager tracingManager, + @Nullable final ServerApi serverApi, + @Nullable final String operationName, + final ServerDeprioritization serverDeprioritization) { this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, serverDeprioritization, tracingManager, serverApi, @@ -98,7 +98,7 @@ static OperationContext simpleOperationContext( TracingManager.NO_OP, serverApi, null - ); + ); } public static OperationContext simpleOperationContext(final TimeoutContext timeoutContext) { @@ -176,13 +176,13 @@ public void setTracingSpan(final Span tracingSpan) { } private OperationContext(final long id, - final RequestContext requestContext, - final SessionContext sessionContext, - final TimeoutContext timeoutContext, - final ServerDeprioritization serverDeprioritization, - final TracingManager tracingManager, - @Nullable final ServerApi serverApi, - @Nullable final String operationName, + final RequestContext requestContext, + final SessionContext sessionContext, + final TimeoutContext timeoutContext, + final ServerDeprioritization serverDeprioritization, + final TracingManager tracingManager, + @Nullable final ServerApi serverApi, + @Nullable final String operationName, @Nullable final Span tracingSpan) { this.id = id; From 81c21e044835f73aae88f141f1f95c6f640b0174 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 20 Apr 2026 19:50:05 -0700 Subject: [PATCH 5/6] Revert formatting changes. --- .../main/com/mongodb/internal/connection/OperationContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java index 69cb5c5780..bf1021b072 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java +++ b/driver-core/src/main/com/mongodb/internal/connection/OperationContext.java @@ -183,7 +183,7 @@ private OperationContext(final long id, final TracingManager tracingManager, @Nullable final ServerApi serverApi, @Nullable final String operationName, - @Nullable final Span tracingSpan) { + @Nullable final Span tracingSpan) { this.id = id; this.serverDeprioritization = serverDeprioritization; From b01f97ae4ea16a400948ac647d8fcde0b9ae3064 Mon Sep 17 00:00:00 2001 From: "slav.babanin" Date: Mon, 20 Apr 2026 20:01:23 -0700 Subject: [PATCH 6/6] Add Beta. --- driver-core/src/main/com/mongodb/ConnectionString.java | 1 + driver-core/src/main/com/mongodb/MongoClientSettings.java | 2 ++ driver-legacy/src/main/com/mongodb/MongoClientOptions.java | 2 ++ driver-legacy/src/main/com/mongodb/MongoClientURI.java | 2 +- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/driver-core/src/main/com/mongodb/ConnectionString.java b/driver-core/src/main/com/mongodb/ConnectionString.java index 14af446bf0..f10ecb399d 100644 --- a/driver-core/src/main/com/mongodb/ConnectionString.java +++ b/driver-core/src/main/com/mongodb/ConnectionString.java @@ -1527,6 +1527,7 @@ public Integer getMaxAdaptiveRetries() { * @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean) * @since 5.7 */ + @Beta(Reason.CLIENT) @Nullable public Boolean getEnableOverloadRetargeting() { return enableOverloadRetargeting; diff --git a/driver-core/src/main/com/mongodb/MongoClientSettings.java b/driver-core/src/main/com/mongodb/MongoClientSettings.java index 9dcdcadfbc..d4a06c07d8 100644 --- a/driver-core/src/main/com/mongodb/MongoClientSettings.java +++ b/driver-core/src/main/com/mongodb/MongoClientSettings.java @@ -586,6 +586,7 @@ public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) { * @see #getEnableOverloadRetargeting() * @since 5.7 */ + @Beta(Reason.CLIENT) public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) { this.enableOverloadRetargeting = enableOverloadRetargeting; return this; @@ -973,6 +974,7 @@ public Integer getMaxAdaptiveRetries() { * @see Builder#enableOverloadRetargeting(boolean) * @since 5.7 */ + @Beta(Reason.CLIENT) public boolean getEnableOverloadRetargeting() { return enableOverloadRetargeting; } diff --git a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java index 8cc502942d..c269a810d2 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientOptions.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientOptions.java @@ -496,6 +496,7 @@ public Integer getMaxAdaptiveRetries() { * @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean) * @since 5.7 */ + @Beta(Reason.CLIENT) public boolean getEnableOverloadRetargeting() { return wrapped.getEnableOverloadRetargeting(); } @@ -1114,6 +1115,7 @@ public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) { * @see #getEnableOverloadRetargeting() * @since 5.7 */ + @Beta(Reason.CLIENT) public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) { wrapped.enableOverloadRetargeting(enableOverloadRetargeting); return this; diff --git a/driver-legacy/src/main/com/mongodb/MongoClientURI.java b/driver-legacy/src/main/com/mongodb/MongoClientURI.java index 20dc41396f..e7ce89566d 100644 --- a/driver-legacy/src/main/com/mongodb/MongoClientURI.java +++ b/driver-legacy/src/main/com/mongodb/MongoClientURI.java @@ -218,7 +218,7 @@ *
  • {@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}. * The maximum number of retry attempts when encountering a retryable overload error. * See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.
  • - *
  • {@code enableOverloadRetargeting=true|false}: Whether to enable overload retargeting. Defaults to false. +*
  • {@code enableOverloadRetargeting=true|false}: Whether to enable overload retargeting. Defaults to false. * See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information.
  • *
  • {@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See * {@link MongoClientOptions#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but