Skip to content

Commit dd8d662

Browse files
vbabaninrozza
authored andcommitted
Add enableOverloadRetargeting API (mongodb#1943)
- 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 --------- Co-authored-by: Ross Lawley <[email protected]>
1 parent 135749d commit dd8d662

20 files changed

Lines changed: 304 additions & 78 deletions

File tree

driver-core/src/main/com/mongodb/ConnectionString.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@
276276
* <li>{@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}.
277277
* The maximum number of retry attempts when encountering a retryable overload error.
278278
* See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.</li>
279+
* <li>{@code enableOverloadRetargeting=true|false}: This is {@linkplain Beta Beta API}.
280+
* Whether to enable overload retargeting. Defaults to false.
281+
* See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information.</li>
279282
* <li>{@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See
280283
* {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but
281284
* will change to "unspecified" in the next major release.</li>
@@ -313,6 +316,7 @@ public class ConnectionString {
313316
private Boolean retryWrites;
314317
private Boolean retryReads;
315318
private Integer maxAdaptiveRetries;
319+
private Boolean enableOverloadRetargeting;
316320
private ReadConcern readConcern;
317321

318322
private Integer minConnectionPoolSize;
@@ -564,6 +568,7 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient
564568
GENERAL_OPTIONS_KEYS.add("retrywrites");
565569
GENERAL_OPTIONS_KEYS.add("retryreads");
566570
GENERAL_OPTIONS_KEYS.add("maxadaptiveretries");
571+
GENERAL_OPTIONS_KEYS.add("enableoverloadretargeting");
567572

568573
GENERAL_OPTIONS_KEYS.add("appname");
569574

@@ -718,6 +723,9 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
718723
throw new IllegalArgumentException("maxAdaptiveRetries must be >= 0");
719724
}
720725
break;
726+
case "enableoverloadretargeting":
727+
enableOverloadRetargeting = parseBoolean(value, "enableoverloadretargeting");
728+
break;
721729
case "uuidrepresentation":
722730
uuidRepresentation = createUuidRepresentation(value);
723731
break;
@@ -1511,6 +1519,20 @@ public Integer getMaxAdaptiveRetries() {
15111519
return maxAdaptiveRetries;
15121520
}
15131521

1522+
/**
1523+
* Gets whether overload retargeting is enabled.
1524+
* See {@link MongoClientSettings.Builder#enableOverloadRetargeting(boolean)} for more information.
1525+
*
1526+
* @return the enableOverloadRetargeting value, or null if not set
1527+
* @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean)
1528+
* @since 5.7
1529+
*/
1530+
@Beta(Reason.CLIENT)
1531+
@Nullable
1532+
public Boolean getEnableOverloadRetargeting() {
1533+
return enableOverloadRetargeting;
1534+
}
1535+
15141536
/**
15151537
* Gets the minimum connection pool size specified in the connection string.
15161538
* @return the minimum connection pool size
@@ -1825,6 +1847,7 @@ public boolean equals(final Object o) {
18251847
&& Objects.equals(retryWrites, that.retryWrites)
18261848
&& Objects.equals(retryReads, that.retryReads)
18271849
&& Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries)
1850+
&& Objects.equals(enableOverloadRetargeting, that.enableOverloadRetargeting)
18281851
&& Objects.equals(readConcern, that.readConcern)
18291852
&& Objects.equals(minConnectionPoolSize, that.minConnectionPoolSize)
18301853
&& Objects.equals(maxConnectionPoolSize, that.maxConnectionPoolSize)
@@ -1856,7 +1879,7 @@ public boolean equals(final Object o) {
18561879
@Override
18571880
public int hashCode() {
18581881
return Objects.hash(credential, isSrvProtocol, hosts, database, collection, directConnection, readPreference,
1859-
writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime,
1882+
writeConcern, retryWrites, retryReads, maxAdaptiveRetries, enableOverloadRetargeting, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime,
18601883
maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, timeout, socketTimeout, sslEnabled,
18611884
sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency,
18621885
serverMonitoringMode, applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts, proxyHost,

driver-core/src/main/com/mongodb/MongoClientSettings.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.mongodb.client.model.geojson.codecs.GeoJsonCodecProvider;
2626
import com.mongodb.client.model.mql.ExpressionCodecProvider;
2727
import com.mongodb.connection.ClusterSettings;
28+
import com.mongodb.connection.ClusterType;
2829
import com.mongodb.connection.ConnectionPoolSettings;
2930
import com.mongodb.connection.ServerSettings;
3031
import com.mongodb.connection.SocketSettings;
@@ -96,6 +97,7 @@ public final class MongoClientSettings {
9697
private final boolean retryReads;
9798
@Nullable
9899
private final Integer maxAdaptiveRetries;
100+
private final boolean enableOverloadRetargeting;
99101
private final ReadConcern readConcern;
100102
private final MongoCredential credential;
101103
private final TransportSettings transportSettings;
@@ -219,6 +221,7 @@ public static final class Builder {
219221
private boolean retryReads = true;
220222
@Nullable
221223
private Integer maxAdaptiveRetries;
224+
private boolean enableOverloadRetargeting = false;
222225
private ReadConcern readConcern = ReadConcern.DEFAULT;
223226
private CodecRegistry codecRegistry = MongoClientSettings.getDefaultCodecRegistry();
224227
private TransportSettings transportSettings;
@@ -261,6 +264,7 @@ private Builder(final MongoClientSettings settings) {
261264
retryWrites = settings.getRetryWrites();
262265
retryReads = settings.getRetryReads();
263266
maxAdaptiveRetries = settings.getMaxAdaptiveRetries();
267+
enableOverloadRetargeting = settings.getEnableOverloadRetargeting();
264268
readConcern = settings.getReadConcern();
265269
credential = settings.getCredential();
266270
uuidRepresentation = settings.getUuidRepresentation();
@@ -323,6 +327,10 @@ public Builder applyConnectionString(final ConnectionString connectionString) {
323327
if (connectionString.getMaxAdaptiveRetries() != null) {
324328
maxAdaptiveRetries = connectionString.getMaxAdaptiveRetries();
325329
}
330+
Boolean enableOverloadRetargetingValue = connectionString.getEnableOverloadRetargeting();
331+
if (enableOverloadRetargetingValue != null) {
332+
enableOverloadRetargeting = enableOverloadRetargetingValue;
333+
}
326334
if (connectionString.getUuidRepresentation() != null) {
327335
uuidRepresentation = connectionString.getUuidRepresentation();
328336
}
@@ -559,6 +567,31 @@ public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) {
559567
return this;
560568
}
561569

570+
/**
571+
* Sets whether to enable overload retargeting.
572+
*
573+
* <p>When enabled, the previously selected servers on which attempts failed with an error
574+
* {@linkplain MongoException#hasErrorLabel(String) having}
575+
* the {@value MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} label may be deprioritized during
576+
* server selection on subsequent retry attempts. This applies to reads when
577+
* {@linkplain #retryReads(boolean) retryReads} is enabled, and to writes when
578+
* {@linkplain #retryWrites(boolean) retryWrites} is enabled.</p>
579+
*
580+
* <p>This setting does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}.</p>
581+
*
582+
* <p>Defaults to {@code false}.</p>
583+
*
584+
* @param enableOverloadRetargeting whether to enable overload retargeting.
585+
* @return this
586+
* @see #getEnableOverloadRetargeting()
587+
* @since 5.7
588+
*/
589+
@Beta(Reason.CLIENT)
590+
public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) {
591+
this.enableOverloadRetargeting = enableOverloadRetargeting;
592+
return this;
593+
}
594+
562595
/**
563596
* Sets the read concern.
564597
*
@@ -933,6 +966,19 @@ public Integer getMaxAdaptiveRetries() {
933966
return maxAdaptiveRetries;
934967
}
935968

969+
/**
970+
* Returns whether overload retargeting is enabled.
971+
* See {@link Builder#enableOverloadRetargeting(boolean)} for more information.
972+
*
973+
* @return the enableOverloadRetargeting value
974+
* @see Builder#enableOverloadRetargeting(boolean)
975+
* @since 5.7
976+
*/
977+
@Beta(Reason.CLIENT)
978+
public boolean getEnableOverloadRetargeting() {
979+
return enableOverloadRetargeting;
980+
}
981+
936982
/**
937983
* The read concern to use.
938984
*
@@ -1207,6 +1253,7 @@ public boolean equals(final Object o) {
12071253
return retryWrites == that.retryWrites
12081254
&& retryReads == that.retryReads
12091255
&& Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries)
1256+
&& enableOverloadRetargeting == that.enableOverloadRetargeting
12101257
&& heartbeatSocketTimeoutSetExplicitly == that.heartbeatSocketTimeoutSetExplicitly
12111258
&& heartbeatConnectTimeoutSetExplicitly == that.heartbeatConnectTimeoutSetExplicitly
12121259
&& Objects.equals(readPreference, that.readPreference)
@@ -1236,7 +1283,8 @@ public boolean equals(final Object o) {
12361283

12371284
@Override
12381285
public int hashCode() {
1239-
return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, credential, transportSettings,
1286+
return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, enableOverloadRetargeting, readConcern,
1287+
credential, transportSettings,
12401288
commandListeners, codecRegistry, loggerSettings, clusterSettings, socketSettings,
12411289
heartbeatSocketSettings, connectionPoolSettings, serverSettings, sslSettings, applicationName, compressorList,
12421290
uuidRepresentation, serverApi, autoEncryptionSettings, heartbeatSocketTimeoutSetExplicitly,
@@ -1252,6 +1300,7 @@ public String toString() {
12521300
+ ", retryWrites=" + retryWrites
12531301
+ ", retryReads=" + retryReads
12541302
+ ", maxAdaptiveRetries=" + maxAdaptiveRetries
1303+
+ ", enableOverloadRetargeting=" + enableOverloadRetargeting
12551304
+ ", readConcern=" + readConcern
12561305
+ ", credential=" + credential
12571306
+ ", transportSettings=" + transportSettings
@@ -1281,8 +1330,9 @@ private MongoClientSettings(final Builder builder) {
12811330
readPreference = builder.readPreference;
12821331
writeConcern = builder.writeConcern;
12831332
retryWrites = builder.retryWrites;
1284-
maxAdaptiveRetries = builder.maxAdaptiveRetries;
12851333
retryReads = builder.retryReads;
1334+
maxAdaptiveRetries = builder.maxAdaptiveRetries;
1335+
enableOverloadRetargeting = builder.enableOverloadRetargeting;
12861336
readConcern = builder.readConcern;
12871337
credential = builder.credential;
12881338
transportSettings = builder.transportSettings;

driver-core/src/main/com/mongodb/internal/connection/OperationContext.java

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ public OperationContext(final RequestContext requestContext, final SessionContex
7777
null);
7878
}
7979

80+
public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext,
81+
final TracingManager tracingManager,
82+
@Nullable final ServerApi serverApi,
83+
@Nullable final String operationName,
84+
final ServerDeprioritization serverDeprioritization) {
85+
this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, serverDeprioritization,
86+
tracingManager,
87+
serverApi,
88+
operationName,
89+
null);
90+
}
91+
8092
static OperationContext simpleOperationContext(
8193
final TimeoutSettings timeoutSettings, @Nullable final ServerApi serverApi) {
8294
return new OperationContext(
@@ -119,7 +131,8 @@ public OperationContext withOperationName(final String operationName) {
119131
* It is a temporary solution to handle cases where deprioritization state persists across operations.
120132
*/
121133
public OperationContext withNewServerDeprioritization() {
122-
return new OperationContext(id, requestContext, sessionContext, timeoutContext, new ServerDeprioritization(), tracingManager, serverApi,
134+
return new OperationContext(id, requestContext, sessionContext, timeoutContext,
135+
new ServerDeprioritization(serverDeprioritization.enableOverloadRetargeting), tracingManager, serverApi,
123136
operationName, tracingSpan);
124137
}
125138

@@ -206,7 +219,8 @@ public OperationContext withConnectionEstablishmentSessionContext() {
206219
}
207220

208221
public OperationContext withMinRoundTripTime(final ServerDescription serverDescription) {
209-
return withTimeoutContext(timeoutContext.withMinRoundTripTime(TimeUnit.NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos())));
222+
return withTimeoutContext(
223+
timeoutContext.withMinRoundTripTime(TimeUnit.NANOSECONDS.toMillis(serverDescription.getMinRoundTripTimeNanos())));
210224
}
211225

212226
public OperationContext withOverride(final TimeoutContextOverride timeoutContextOverrideFunction) {
@@ -219,11 +233,17 @@ public static final class ServerDeprioritization {
219233
@Nullable
220234
private ClusterType clusterType;
221235
private final Set<ServerAddress> deprioritized;
236+
private final boolean enableOverloadRetargeting;
222237

223-
private ServerDeprioritization() {
224-
candidate = null;
225-
deprioritized = new HashSet<>();
226-
clusterType = null;
238+
public ServerDeprioritization() {
239+
this(false);
240+
}
241+
242+
public ServerDeprioritization(final boolean enableOverloadRetargeting) {
243+
this.enableOverloadRetargeting = enableOverloadRetargeting;
244+
this.candidate = null;
245+
this.deprioritized = new HashSet<>();
246+
this.clusterType = null;
227247
}
228248

229249
/**
@@ -250,10 +270,12 @@ public void onAttemptFailure(final Throwable failure) {
250270
return;
251271
}
252272

253-
// As per spec: sharded clusters deprioritize on any error, other topologies only on overload
273+
// As per spec: sharded clusters deprioritize on any error,
274+
// other topologies deprioritize on overload only when retargeting is enabled.
254275
boolean isSystemOverloadedError = failure instanceof MongoException
255276
&& ((MongoException) failure).hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL);
256-
if (clusterType == ClusterType.SHARDED || isSystemOverloadedError) {
277+
278+
if (clusterType == ClusterType.SHARDED || (isSystemOverloadedError && enableOverloadRetargeting)) {
257279
deprioritized.add(candidate);
258280
}
259281
}
@@ -303,6 +325,7 @@ public List<ServerDescription> select(final ClusterDescription clusterDescriptio
303325
}
304326
}
305327

306-
public interface TimeoutContextOverride extends Function<TimeoutContext, TimeoutContext> {}
328+
public interface TimeoutContextOverride extends Function<TimeoutContext, TimeoutContext> {
329+
}
307330
}
308331

driver-core/src/test/unit/com/mongodb/AbstractConnectionStringTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ protected void testValidOptions() {
112112

113113
if (option.getKey().equals("authmechanism")) {
114114
String expected = option.getValue().asString().getValue();
115-
if (expected.equals("MONGODB-CR")) {
115+
if (expected.equals("MONGODB-CR")) {
116116
assertNotNull(connectionString.getCredential());
117117
assertNull(connectionString.getCredential().getAuthenticationMechanism());
118118
} else {
@@ -125,6 +125,9 @@ protected void testValidOptions() {
125125
} else if (option.getKey().equalsIgnoreCase("maxadaptiveretries")) {
126126
int expected = option.getValue().asInt32().getValue();
127127
assertEquals(expected, connectionString.getMaxAdaptiveRetries().intValue());
128+
} else if (option.getKey().equalsIgnoreCase("enableoverloadretargeting")) {
129+
boolean expected = option.getValue().asBoolean().getValue();
130+
assertEquals(expected, connectionString.getEnableOverloadRetargeting().booleanValue());
128131
} else if (option.getKey().equalsIgnoreCase("replicaset")) {
129132
String expected = option.getValue().asString().getValue();
130133
assertEquals(expected, connectionString.getRequiredReplicaSetName());

driver-core/src/test/unit/com/mongodb/ConnectionStringUnitTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ void defaults() {
4646
@ParameterizedTest
4747
@ValueSource(strings = {
4848
"serverMonitoringMode=stream",
49-
"maxAdaptiveRetries=42"
49+
"maxAdaptiveRetries=42",
50+
"enableOverloadRetargeting=true"
5051
})
5152
void equalAndHashCode(final String connectionStringOptions) {
5253
ConnectionString default1 = new ConnectionString(DEFAULT_OPTIONS);
@@ -129,4 +130,14 @@ void maxAdaptiveRetries() {
129130
() -> new ConnectionString(DEFAULT_OPTIONS + "maxAdaptiveRetries=invalid"))
130131
);
131132
}
133+
134+
@Test
135+
void enableOverloadRetargeting() {
136+
assertAll(
137+
() -> assertNull(new ConnectionString("mongodb://localhost/").getEnableOverloadRetargeting()),
138+
() -> assertEquals(false, new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=false").getEnableOverloadRetargeting()),
139+
() -> assertEquals(true, new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=true").getEnableOverloadRetargeting()),
140+
() -> assertNull(new ConnectionString(DEFAULT_OPTIONS + "enableOverloadRetargeting=foos").getEnableOverloadRetargeting())
141+
);
142+
}
132143
}

driver-core/src/test/unit/com/mongodb/MongoClientSettingsSpecification.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ class MongoClientSettingsSpecification extends Specification {
576576
def actual = MongoClientSettings.Builder.declaredFields.grep { !it.synthetic } *.name.sort()
577577
def expected = ['applicationName', 'autoEncryptionSettings', 'clusterSettingsBuilder', 'codecRegistry', 'commandListeners',
578578
'compressorList', 'connectionPoolSettingsBuilder', 'contextProvider', 'credential', 'dnsClient',
579+
'enableOverloadRetargeting',
579580
'heartbeatConnectTimeoutMS', 'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'loggerSettingsBuilder',
580581
'maxAdaptiveRetries', 'observabilitySettings',
581582
'readConcern', 'readPreference', 'retryReads',
@@ -595,6 +596,7 @@ class MongoClientSettingsSpecification extends Specification {
595596
'applyToConnectionPoolSettings', 'applyToLoggerSettings', 'applyToServerSettings', 'applyToSocketSettings',
596597
'applyToSslSettings', 'autoEncryptionSettings', 'build', 'codecRegistry', 'commandListenerList',
597598
'compressorList', 'contextProvider', 'credential', 'dnsClient',
599+
'enableOverloadRetargeting',
598600
'heartbeatConnectTimeoutMS',
599601
'heartbeatSocketTimeoutMS', 'inetAddressResolver', 'maxAdaptiveRetries', 'observabilitySettings', 'readConcern',
600602
'readPreference',

0 commit comments

Comments
 (0)