Skip to content

Commit aded7d7

Browse files
committed
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
1 parent 08c1164 commit aded7d7

10 files changed

Lines changed: 190 additions & 51 deletions

File tree

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.mongodb.annotations.Alpha;
2020
import com.mongodb.annotations.Reason;
2121
import com.mongodb.connection.ClusterSettings;
22+
import com.mongodb.connection.ClusterType;
2223
import com.mongodb.connection.ConnectionPoolSettings;
2324
import com.mongodb.connection.ServerMonitoringMode;
2425
import com.mongodb.connection.ServerSettings;
@@ -272,6 +273,9 @@
272273
* Defaults to true.</li>
273274
* <li>{@code retryReads=true|false}. If true the driver will retry supported read operations if they fail due to a network error.
274275
* Defaults to true.</li>
276+
* <li>{@code enableOverloadRetargeting=true|false}. If true the driver may route a request to a different server on a subsequent
277+
* retry attempt if the previously used server is overloaded. Does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}.
278+
* Defaults to false.</li>
275279
* <li>{@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See
276280
* {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but
277281
* will change to "unspecified" in the next major release.</li>
@@ -308,6 +312,7 @@ public class ConnectionString {
308312
private WriteConcern writeConcern;
309313
private Boolean retryWrites;
310314
private Boolean retryReads;
315+
private Boolean enableOverloadRetargeting;
311316
private ReadConcern readConcern;
312317

313318
private Integer minConnectionPoolSize;
@@ -558,6 +563,7 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient
558563
GENERAL_OPTIONS_KEYS.add("servermonitoringmode");
559564
GENERAL_OPTIONS_KEYS.add("retrywrites");
560565
GENERAL_OPTIONS_KEYS.add("retryreads");
566+
GENERAL_OPTIONS_KEYS.add("enableoverloadretargeting");
561567

562568
GENERAL_OPTIONS_KEYS.add("appname");
563569

@@ -706,6 +712,9 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
706712
case "retryreads":
707713
retryReads = parseBoolean(value, "retryreads");
708714
break;
715+
case "enableoverloadretargeting":
716+
enableOverloadRetargeting = parseBoolean(value, "enableoverloadretargeting");
717+
break;
709718
case "uuidrepresentation":
710719
uuidRepresentation = createUuidRepresentation(value);
711720
break;
@@ -1482,6 +1491,28 @@ public Boolean getRetryReads() {
14821491
return retryReads;
14831492
}
14841493

1494+
/**
1495+
* Gets whether overload retargeting is enabled.
1496+
*
1497+
* <p>When enabled, the driver may route a request to a different server on a subsequent retry attempt
1498+
* if the previously used server is overloaded. This applies
1499+
* to reads when retryReads is enabled, and to writes when retryWrites is enabled. Enabling this setting
1500+
* generally improves availability during server overload.</p>
1501+
*
1502+
* <p>This setting does not take effect for
1503+
* {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}.</p>
1504+
*
1505+
* <p>Defaults to {@code false}.</p>
1506+
*
1507+
* @return the enableOverloadRetargeting value, or null if not set
1508+
* @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean)
1509+
* @since 5.7
1510+
*/
1511+
@Nullable
1512+
public Boolean getEnableOverloadRetargeting() {
1513+
return enableOverloadRetargeting;
1514+
}
1515+
14851516
/**
14861517
* Gets the minimum connection pool size specified in the connection string.
14871518
* @return the minimum connection pool size
@@ -1795,6 +1826,7 @@ public boolean equals(final Object o) {
17951826
&& Objects.equals(writeConcern, that.writeConcern)
17961827
&& Objects.equals(retryWrites, that.retryWrites)
17971828
&& Objects.equals(retryReads, that.retryReads)
1829+
&& Objects.equals(enableOverloadRetargeting, that.enableOverloadRetargeting)
17981830
&& Objects.equals(readConcern, that.readConcern)
17991831
&& Objects.equals(minConnectionPoolSize, that.minConnectionPoolSize)
18001832
&& Objects.equals(maxConnectionPoolSize, that.maxConnectionPoolSize)
@@ -1826,8 +1858,8 @@ public boolean equals(final Object o) {
18261858
@Override
18271859
public int hashCode() {
18281860
return Objects.hash(credential, isSrvProtocol, hosts, database, collection, directConnection, readPreference,
1829-
writeConcern, retryWrites, retryReads, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime,
1830-
maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, timeout, socketTimeout, sslEnabled,
1861+
writeConcern, retryWrites, retryReads, enableOverloadRetargeting, readConcern, minConnectionPoolSize, maxConnectionPoolSize,
1862+
maxWaitTime, maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, timeout, socketTimeout, sslEnabled,
18311863
sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency,
18321864
serverMonitoringMode, applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts, proxyHost,
18331865
proxyPort, proxyUsername, proxyPassword);

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

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.mongodb.client.model.geojson.codecs.GeoJsonCodecProvider;
2525
import com.mongodb.client.model.mql.ExpressionCodecProvider;
2626
import com.mongodb.connection.ClusterSettings;
27+
import com.mongodb.connection.ClusterType;
2728
import com.mongodb.connection.ConnectionPoolSettings;
2829
import com.mongodb.connection.ServerSettings;
2930
import com.mongodb.connection.SocketSettings;
@@ -93,6 +94,7 @@ public final class MongoClientSettings {
9394
private final WriteConcern writeConcern;
9495
private final boolean retryWrites;
9596
private final boolean retryReads;
97+
private final boolean enableOverloadRetargeting;
9698
private final ReadConcern readConcern;
9799
private final MongoCredential credential;
98100
private final TransportSettings transportSettings;
@@ -214,6 +216,7 @@ public static final class Builder {
214216
private WriteConcern writeConcern = WriteConcern.ACKNOWLEDGED;
215217
private boolean retryWrites = true;
216218
private boolean retryReads = true;
219+
private boolean enableOverloadRetargeting = false;
217220
private ReadConcern readConcern = ReadConcern.DEFAULT;
218221
private CodecRegistry codecRegistry = MongoClientSettings.getDefaultCodecRegistry();
219222
private TransportSettings transportSettings;
@@ -255,6 +258,7 @@ private Builder(final MongoClientSettings settings) {
255258
writeConcern = settings.getWriteConcern();
256259
retryWrites = settings.getRetryWrites();
257260
retryReads = settings.getRetryReads();
261+
enableOverloadRetargeting = settings.getEnableOverloadRetargeting();
258262
readConcern = settings.getReadConcern();
259263
credential = settings.getCredential();
260264
uuidRepresentation = settings.getUuidRepresentation();
@@ -314,6 +318,10 @@ public Builder applyConnectionString(final ConnectionString connectionString) {
314318
if (retryReadsValue != null) {
315319
retryReads = retryReadsValue;
316320
}
321+
Boolean enableOverloadRetargetingValue = connectionString.getEnableOverloadRetargeting();
322+
if (enableOverloadRetargetingValue != null) {
323+
enableOverloadRetargeting = enableOverloadRetargetingValue;
324+
}
317325
if (connectionString.getUuidRepresentation() != null) {
318326
uuidRepresentation = connectionString.getUuidRepresentation();
319327
}
@@ -456,6 +464,29 @@ public Builder retryReads(final boolean retryReads) {
456464
return this;
457465
}
458466

467+
/**
468+
* Sets whether to enable overload retargeting.
469+
*
470+
* <p>When enabled, the driver may route a request to a different server on a subsequent retry attempt
471+
* if the previously used server is overloaded. This applies
472+
* to reads when {@linkplain #retryReads(boolean) retryReads} is enabled, and to writes when
473+
* {@linkplain #retryWrites(boolean) retryWrites} is enabled. Enabling this setting generally
474+
* improves availability during server overload.</p>
475+
*
476+
* <p>This setting does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}.</p>
477+
*
478+
* <p>Defaults to {@code false}.</p>
479+
*
480+
* @param enableOverloadRetargeting whether to enable overload retargeting.
481+
* @return this
482+
* @see #getEnableOverloadRetargeting()
483+
* @since 5.7
484+
*/
485+
public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) {
486+
this.enableOverloadRetargeting = enableOverloadRetargeting;
487+
return this;
488+
}
489+
459490
/**
460491
* Sets the read concern.
461492
*
@@ -807,6 +838,27 @@ public boolean getRetryReads() {
807838
return retryReads;
808839
}
809840

841+
/**
842+
* Returns whether overload retargeting is enabled.
843+
*
844+
* <p>When enabled, the driver may route a request to a different server on a subsequent retry attempt
845+
* if the previously used server is overloaded. This applies
846+
* to reads when {@linkplain Builder#retryReads(boolean) retryReads} is enabled, and to writes when
847+
* {@linkplain Builder#retryWrites(boolean) retryWrites} is enabled. Enabling this setting generally
848+
* improves availability during server overload.</p>
849+
*
850+
* <p>This setting does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}.</p>
851+
*
852+
* <p>Defaults to {@code false}.</p>
853+
*
854+
* @return the enableOverloadRetargeting value
855+
* @see Builder#enableOverloadRetargeting(boolean)
856+
* @since 5.7
857+
*/
858+
public boolean getEnableOverloadRetargeting() {
859+
return enableOverloadRetargeting;
860+
}
861+
810862
/**
811863
* The read concern to use.
812864
*
@@ -1080,6 +1132,7 @@ public boolean equals(final Object o) {
10801132
MongoClientSettings that = (MongoClientSettings) o;
10811133
return retryWrites == that.retryWrites
10821134
&& retryReads == that.retryReads
1135+
&& enableOverloadRetargeting == that.enableOverloadRetargeting
10831136
&& heartbeatSocketTimeoutSetExplicitly == that.heartbeatSocketTimeoutSetExplicitly
10841137
&& heartbeatConnectTimeoutSetExplicitly == that.heartbeatConnectTimeoutSetExplicitly
10851138
&& Objects.equals(readPreference, that.readPreference)
@@ -1109,8 +1162,8 @@ public boolean equals(final Object o) {
11091162

11101163
@Override
11111164
public int hashCode() {
1112-
return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, readConcern, credential, transportSettings,
1113-
commandListeners, codecRegistry, loggerSettings, clusterSettings, socketSettings,
1165+
return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, enableOverloadRetargeting, readConcern, credential,
1166+
transportSettings, commandListeners, codecRegistry, loggerSettings, clusterSettings, socketSettings,
11141167
heartbeatSocketSettings, connectionPoolSettings, serverSettings, sslSettings, applicationName, compressorList,
11151168
uuidRepresentation, serverApi, autoEncryptionSettings, heartbeatSocketTimeoutSetExplicitly,
11161169
heartbeatConnectTimeoutSetExplicitly, dnsClient, inetAddressResolver, contextProvider, timeoutMS);
@@ -1124,6 +1177,7 @@ public String toString() {
11241177
+ ", writeConcern=" + writeConcern
11251178
+ ", retryWrites=" + retryWrites
11261179
+ ", retryReads=" + retryReads
1180+
+ ", enableOverloadRetargeting=" + enableOverloadRetargeting
11271181
+ ", readConcern=" + readConcern
11281182
+ ", credential=" + credential
11291183
+ ", transportSettings=" + transportSettings
@@ -1154,6 +1208,7 @@ private MongoClientSettings(final Builder builder) {
11541208
writeConcern = builder.writeConcern;
11551209
retryWrites = builder.retryWrites;
11561210
retryReads = builder.retryReads;
1211+
enableOverloadRetargeting = builder.enableOverloadRetargeting;
11571212
readConcern = builder.readConcern;
11581213
credential = builder.credential;
11591214
transportSettings = builder.transportSettings;

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

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,33 @@ public class OperationContext {
6262
private Span tracingSpan;
6363

6464
public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext,
65-
@Nullable final ServerApi serverApi) {
65+
@Nullable final ServerApi serverApi) {
6666
this(requestContext, sessionContext, timeoutContext, TracingManager.NO_OP, serverApi, null);
6767
}
6868

6969
public OperationContext(final RequestContext requestContext, final SessionContext sessionContext, final TimeoutContext timeoutContext,
70-
final TracingManager tracingManager,
71-
@Nullable final ServerApi serverApi,
72-
@Nullable final String operationName) {
70+
final TracingManager tracingManager,
71+
@Nullable final ServerApi serverApi,
72+
@Nullable final String operationName) {
7373
this(NEXT_ID.incrementAndGet(), requestContext, sessionContext, timeoutContext, new ServerDeprioritization(),
7474
tracingManager,
7575
serverApi,
7676
operationName,
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(
@@ -86,7 +98,7 @@ static OperationContext simpleOperationContext(
8698
TracingManager.NO_OP,
8799
serverApi,
88100
null
89-
);
101+
);
90102
}
91103

92104
public static OperationContext simpleOperationContext(final TimeoutContext timeoutContext) {
@@ -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

@@ -163,14 +176,14 @@ public void setTracingSpan(final Span tracingSpan) {
163176
}
164177

165178
private OperationContext(final long id,
166-
final RequestContext requestContext,
167-
final SessionContext sessionContext,
168-
final TimeoutContext timeoutContext,
169-
final ServerDeprioritization serverDeprioritization,
170-
final TracingManager tracingManager,
171-
@Nullable final ServerApi serverApi,
172-
@Nullable final String operationName,
173-
@Nullable final Span tracingSpan) {
179+
final RequestContext requestContext,
180+
final SessionContext sessionContext,
181+
final TimeoutContext timeoutContext,
182+
final ServerDeprioritization serverDeprioritization,
183+
final TracingManager tracingManager,
184+
@Nullable final ServerApi serverApi,
185+
@Nullable final String operationName,
186+
@Nullable final Span tracingSpan) {
174187

175188
this.id = id;
176189
this.serverDeprioritization = serverDeprioritization;
@@ -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;
237+
238+
public ServerDeprioritization() {
239+
this(false);
240+
}
222241

223-
private ServerDeprioritization() {
224-
candidate = null;
225-
deprioritized = new HashSet<>();
226-
clusterType = null;
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
/**
@@ -252,7 +272,8 @@ public void onAttemptFailure(final Throwable failure) {
252272

253273
boolean isSystemOverloadedError = failure instanceof MongoException
254274
&& ((MongoException) failure).hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL);
255-
if (clusterType == ClusterType.SHARDED || isSystemOverloadedError) {
275+
276+
if (clusterType == ClusterType.SHARDED || (isSystemOverloadedError && enableOverloadRetargeting)) {
256277
deprioritized.add(candidate);
257278
}
258279
}
@@ -302,6 +323,7 @@ public List<ServerDescription> select(final ClusterDescription clusterDescriptio
302323
}
303324
}
304325

305-
public interface TimeoutContextOverride extends Function<TimeoutContext, TimeoutContext> {}
326+
public interface TimeoutContextOverride extends Function<TimeoutContext, TimeoutContext> {
327+
}
306328
}
307329

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 {
@@ -122,6 +122,9 @@ protected void testValidOptions() {
122122
} else if (option.getKey().equalsIgnoreCase("retrywrites")) {
123123
boolean expected = option.getValue().asBoolean().getValue();
124124
assertEquals(expected, connectionString.getRetryWritesValue().booleanValue());
125+
} else if (option.getKey().equalsIgnoreCase("enableoverloadretargeting")) {
126+
boolean expected = option.getValue().asBoolean().getValue();
127+
assertEquals(expected, connectionString.getEnableOverloadRetargeting().booleanValue());
125128
} else if (option.getKey().equalsIgnoreCase("replicaset")) {
126129
String expected = option.getValue().asString().getValue();
127130
assertEquals(expected, connectionString.getRequiredReplicaSetName());

0 commit comments

Comments
 (0)