Skip to content
35 changes: 34 additions & 1 deletion driver-core/src/main/com/mongodb/ConnectionString.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.mongodb.annotations.Beta;
import com.mongodb.annotations.Reason;
import com.mongodb.connection.ClusterSettings;
import com.mongodb.connection.ClusterType;
Comment thread
vbabanin marked this conversation as resolved.
Outdated
Comment thread
vbabanin marked this conversation as resolved.
Outdated
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.connection.ServerMonitoringMode;
import com.mongodb.connection.ServerSettings;
Expand Down Expand Up @@ -276,6 +277,9 @@
* <li>{@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.</li>
* <li>{@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.</li> //TODO-SSLAV add see
Comment thread
vbabanin marked this conversation as resolved.
Outdated
* <li>{@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.</li>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");

Expand Down Expand Up @@ -718,6 +724,9 @@ private void translateOptions(final Map<String, List<String>> optionsMap) {
throw new IllegalArgumentException("maxAdaptiveRetries must be >= 0");
}
break;
case "enableoverloadretargeting":
enableOverloadRetargeting = parseBoolean(value, "enableoverloadretargeting");
break;
case "uuidrepresentation":
uuidRepresentation = createUuidRepresentation(value);
break;
Expand Down Expand Up @@ -1511,6 +1520,29 @@ public Integer getMaxAdaptiveRetries() {
return maxAdaptiveRetries;
}

/**
* Gets whether overload retargeting is enabled.
*
* <p>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.</p>
*
* <p>This setting does not take effect for
* {@linkplain com.mongodb.connection.ClusterType#SHARDED sharded clusters}.</p>
*
* <p>Defaults to {@code false}.</p>
*
* @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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
52 changes: 50 additions & 2 deletions driver-core/src/main/com/mongodb/MongoClientSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment thread
vbabanin marked this conversation as resolved.
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.connection.ServerSettings;
import com.mongodb.connection.SocketSettings;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -559,6 +567,30 @@ public Builder maxAdaptiveRetries(@Nullable final Integer maxAdaptiveRetries) {
return this;
}

/**
* Sets whether to enable overload retargeting.
*
* <p>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.</p>
*
* <p>This setting does not take effect for {@linkplain ClusterType#SHARDED sharded clusters}.</p>
*
* <p>Defaults to {@code false}.</p>
*
* @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.
*
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -1252,6 +1298,7 @@ public String toString() {
+ ", retryWrites=" + retryWrites
+ ", retryReads=" + retryReads
+ ", maxAdaptiveRetries=" + maxAdaptiveRetries
+ ", enableOverloadRetargeting=" + enableOverloadRetargeting
+ ", readConcern=" + readConcern
+ ", credential=" + credential
+ ", transportSettings=" + transportSettings
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,33 @@ 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,
operationName,
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(
Expand All @@ -86,7 +98,7 @@ static OperationContext simpleOperationContext(
TracingManager.NO_OP,
serverApi,
null
);
);
}

public static OperationContext simpleOperationContext(final TimeoutContext timeoutContext) {
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -219,11 +233,17 @@ public static final class ServerDeprioritization {
@Nullable
private ClusterType clusterType;
private final Set<ServerAddress> 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;
}

/**
Expand Down Expand Up @@ -253,7 +273,8 @@ public void onAttemptFailure(final Throwable failure) {
// As per spec: sharded clusters deprioritize on any error, other topologies only on overload
Comment thread
vbabanin marked this conversation as resolved.
Outdated
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);
}
}
Expand Down Expand Up @@ -303,6 +324,7 @@ public List<ServerDescription> select(final ClusterDescription clusterDescriptio
}
}

public interface TimeoutContextOverride extends Function<TimeoutContext, TimeoutContext> {}
public interface TimeoutContextOverride extends Function<TimeoutContext, TimeoutContext> {
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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());
Expand Down
Loading