Skip to content

Commit 03efbf4

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 44541fc commit 03efbf4

17 files changed

Lines changed: 282 additions & 68 deletions

File tree

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.mongodb.annotations.Beta;
2121
import com.mongodb.annotations.Reason;
2222
import com.mongodb.connection.ClusterSettings;
23+
import com.mongodb.connection.ClusterType;
2324
import com.mongodb.connection.ConnectionPoolSettings;
2425
import com.mongodb.connection.ServerMonitoringMode;
2526
import com.mongodb.connection.ServerSettings;
@@ -276,6 +277,9 @@
276277
* <li>{@code maxAdaptiveRetries=n}: This is {@linkplain Beta Beta API}.
277278
* The maximum number of retry attempts when encountering a retryable overload error.
278279
* See {@link MongoClientSettings.Builder#maxAdaptiveRetries(Integer)} for more information.</li>
280+
* <li>{@code enableOverloadRetargeting=true|false}. If true the driver may route a request to a different server on a subsequent
281+
* retry attempt if the previously used server is overloaded. Does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}.
282+
* Defaults to false.</li> //TODO-SSLAV add see
279283
* <li>{@code uuidRepresentation=unspecified|standard|javaLegacy|csharpLegacy|pythonLegacy}. See
280284
* {@link MongoClientSettings#getUuidRepresentation()} for documentation of semantics of this parameter. Defaults to "javaLegacy", but
281285
* will change to "unspecified" in the next major release.</li>
@@ -313,6 +317,7 @@ public class ConnectionString {
313317
private Boolean retryWrites;
314318
private Boolean retryReads;
315319
private Integer maxAdaptiveRetries;
320+
private Boolean enableOverloadRetargeting;
316321
private ReadConcern readConcern;
317322

318323
private Integer minConnectionPoolSize;
@@ -564,6 +569,7 @@ public ConnectionString(final String connectionString, @Nullable final DnsClient
564569
GENERAL_OPTIONS_KEYS.add("retrywrites");
565570
GENERAL_OPTIONS_KEYS.add("retryreads");
566571
GENERAL_OPTIONS_KEYS.add("maxadaptiveretries");
572+
GENERAL_OPTIONS_KEYS.add("enableoverloadretargeting");
567573

568574
GENERAL_OPTIONS_KEYS.add("appname");
569575

@@ -718,6 +724,9 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
718724
throw new IllegalArgumentException("maxAdaptiveRetries must be >= 0");
719725
}
720726
break;
727+
case "enableoverloadretargeting":
728+
enableOverloadRetargeting = parseBoolean(value, "enableoverloadretargeting");
729+
break;
721730
case "uuidrepresentation":
722731
uuidRepresentation = createUuidRepresentation(value);
723732
break;
@@ -1511,6 +1520,29 @@ public Integer getMaxAdaptiveRetries() {
15111520
return maxAdaptiveRetries;
15121521
}
15131522

1523+
/**
1524+
* Gets whether overload retargeting is enabled.
1525+
*
1526+
* <p>When enabled, the previously selected servers on which attempts failed with an error
1527+
* {@linkplain MongoException#hasErrorLabel(String) having}
1528+
* the {@value MongoException#SYSTEM_OVERLOADED_ERROR_LABEL} label may be deprioritized during
1529+
* server selection on subsequent retry attempts. This applies to reads when retryReads is enabled,
1530+
* and to writes when retryWrites is enabled.</p>
1531+
*
1532+
* <p>This setting does not take effect for
1533+
* {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}.</p>
1534+
*
1535+
* <p>Defaults to {@code false}.</p>
1536+
*
1537+
* @return the enableOverloadRetargeting value, or null if not set
1538+
* @see MongoClientSettings.Builder#enableOverloadRetargeting(boolean)
1539+
* @since 5.7
1540+
*/
1541+
@Nullable
1542+
public Boolean getEnableOverloadRetargeting() {
1543+
return enableOverloadRetargeting;
1544+
}
1545+
15141546
/**
15151547
* Gets the minimum connection pool size specified in the connection string.
15161548
* @return the minimum connection pool size
@@ -1825,6 +1857,7 @@ public boolean equals(final Object o) {
18251857
&& Objects.equals(retryWrites, that.retryWrites)
18261858
&& Objects.equals(retryReads, that.retryReads)
18271859
&& Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries)
1860+
&& Objects.equals(enableOverloadRetargeting, that.enableOverloadRetargeting)
18281861
&& Objects.equals(readConcern, that.readConcern)
18291862
&& Objects.equals(minConnectionPoolSize, that.minConnectionPoolSize)
18301863
&& Objects.equals(maxConnectionPoolSize, that.maxConnectionPoolSize)
@@ -1856,7 +1889,7 @@ public boolean equals(final Object o) {
18561889
@Override
18571890
public int hashCode() {
18581891
return Objects.hash(credential, isSrvProtocol, hosts, database, collection, directConnection, readPreference,
1859-
writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime,
1892+
writeConcern, retryWrites, retryReads, maxAdaptiveRetries, enableOverloadRetargeting, readConcern, minConnectionPoolSize, maxConnectionPoolSize, maxWaitTime,
18601893
maxConnectionIdleTime, maxConnectionLifeTime, maxConnecting, connectTimeout, timeout, socketTimeout, sslEnabled,
18611894
sslInvalidHostnameAllowed, requiredReplicaSetName, serverSelectionTimeout, localThreshold, heartbeatFrequency,
18621895
serverMonitoringMode, applicationName, compressorList, uuidRepresentation, srvServiceName, srvMaxHosts, proxyHost,

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

Lines changed: 50 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,30 @@ 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+
public Builder enableOverloadRetargeting(final boolean enableOverloadRetargeting) {
590+
this.enableOverloadRetargeting = enableOverloadRetargeting;
591+
return this;
592+
}
593+
562594
/**
563595
* Sets the read concern.
564596
*
@@ -933,6 +965,18 @@ public Integer getMaxAdaptiveRetries() {
933965
return maxAdaptiveRetries;
934966
}
935967

968+
/**
969+
* Returns whether overload retargeting is enabled.
970+
* See {@link Builder#enableOverloadRetargeting(boolean)} for more information.
971+
*
972+
* @return the enableOverloadRetargeting value
973+
* @see Builder#enableOverloadRetargeting(boolean)
974+
* @since 5.7
975+
*/
976+
public boolean getEnableOverloadRetargeting() {
977+
return enableOverloadRetargeting;
978+
}
979+
936980
/**
937981
* The read concern to use.
938982
*
@@ -1207,6 +1251,7 @@ public boolean equals(final Object o) {
12071251
return retryWrites == that.retryWrites
12081252
&& retryReads == that.retryReads
12091253
&& Objects.equals(maxAdaptiveRetries, that.maxAdaptiveRetries)
1254+
&& enableOverloadRetargeting == that.enableOverloadRetargeting
12101255
&& heartbeatSocketTimeoutSetExplicitly == that.heartbeatSocketTimeoutSetExplicitly
12111256
&& heartbeatConnectTimeoutSetExplicitly == that.heartbeatConnectTimeoutSetExplicitly
12121257
&& Objects.equals(readPreference, that.readPreference)
@@ -1236,7 +1281,8 @@ public boolean equals(final Object o) {
12361281

12371282
@Override
12381283
public int hashCode() {
1239-
return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, readConcern, credential, transportSettings,
1284+
return Objects.hash(readPreference, writeConcern, retryWrites, retryReads, maxAdaptiveRetries, enableOverloadRetargeting, readConcern,
1285+
credential, transportSettings,
12401286
commandListeners, codecRegistry, loggerSettings, clusterSettings, socketSettings,
12411287
heartbeatSocketSettings, connectionPoolSettings, serverSettings, sslSettings, applicationName, compressorList,
12421288
uuidRepresentation, serverApi, autoEncryptionSettings, heartbeatSocketTimeoutSetExplicitly,
@@ -1252,6 +1298,7 @@ public String toString() {
12521298
+ ", retryWrites=" + retryWrites
12531299
+ ", retryReads=" + retryReads
12541300
+ ", maxAdaptiveRetries=" + maxAdaptiveRetries
1301+
+ ", enableOverloadRetargeting=" + enableOverloadRetargeting
12551302
+ ", readConcern=" + readConcern
12561303
+ ", credential=" + credential
12571304
+ ", transportSettings=" + transportSettings
@@ -1281,8 +1328,9 @@ private MongoClientSettings(final Builder builder) {
12811328
readPreference = builder.readPreference;
12821329
writeConcern = builder.writeConcern;
12831330
retryWrites = builder.retryWrites;
1284-
maxAdaptiveRetries = builder.maxAdaptiveRetries;
12851331
retryReads = builder.retryReads;
1332+
maxAdaptiveRetries = builder.maxAdaptiveRetries;
1333+
enableOverloadRetargeting = builder.enableOverloadRetargeting;
12861334
readConcern = builder.readConcern;
12871335
credential = builder.credential;
12881336
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
/**
@@ -253,7 +273,8 @@ public void onAttemptFailure(final Throwable failure) {
253273
// As per spec: sharded clusters deprioritize on any error, other topologies only on overload
254274
boolean isSystemOverloadedError = failure instanceof MongoException
255275
&& ((MongoException) failure).hasErrorLabel(SYSTEM_OVERLOADED_ERROR_LABEL);
256-
if (clusterType == ClusterType.SHARDED || isSystemOverloadedError) {
276+
277+
if (clusterType == ClusterType.SHARDED || (isSystemOverloadedError && enableOverloadRetargeting)) {
257278
deprioritized.add(candidate);
258279
}
259280
}
@@ -303,6 +324,7 @@ public List<ServerDescription> select(final ClusterDescription clusterDescriptio
303324
}
304325
}
305326

306-
public interface TimeoutContextOverride extends Function<TimeoutContext, TimeoutContext> {}
327+
public interface TimeoutContextOverride extends Function<TimeoutContext, TimeoutContext> {
328+
}
307329
}
308330

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());

0 commit comments

Comments
 (0)