Skip to content

Commit 1cafc53

Browse files
authored
DRIVERS-3218 Avoid clearing the connection pool when the server connection rate limiter triggers (#1855)
1 parent e454a96 commit 1cafc53

19 files changed

+712
-294
lines changed

source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,14 @@ Endpoint. The pool has the following properties:
284284
- **Rate-limited:** A Pool MUST limit the number of [Connections](#connection) being
285285
[established](#establishing-a-connection-internal-implementation) concurrently via the **maxConnecting**
286286
[pool option](#connection-pool-options).
287+
- **Backpressure-enabled** - The pool MUST add the error labels `SystemOverloadedError` and `RetryableError` to network
288+
errors or network timeouts it encounters during the connection establishment or the `hello` message. These labels
289+
are used by the
290+
[SDAM error handling](../server-discovery-and-monitoring/server-discovery-and-monitoring.md#error-handling-pseudocode)
291+
to avoid clearing the pool. The pool MUST NOT add the backpressure error labels during an authentication step
292+
after the `hello` message. For errors that the driver can distinguish as never occurring due to server overload,
293+
such as DNS lookup failures, TLS related errors, or errors encountered establishing a connection to a socks5 proxy,
294+
the driver MUST clear the connection pool and MUST mark the server Unknown for these error types.
287295

288296
```typescript
289297
interface ConnectionPool {
@@ -461,6 +469,7 @@ try:
461469
return connection
462470
except error:
463471
close connection
472+
add `SystemOverloadedError` label if appropriate (see "backpressure-enabled" in [Connection Pool](#connection-pool))
464473
throw error # Propagate error in manner idiomatic to language.
465474
```
466475

@@ -1375,6 +1384,8 @@ to close and remove from its pool a [Connection](#connection) which has unread e
13751384

13761385
## Changelog
13771386

1387+
- 2025-11-21: Add handling of backpressure error labels.
1388+
13781389
- 2025-01-22: Clarify durationMS in logs may be Int32/Int64/Double.
13791390

13801391
- 2024-11-27: Relaxed the WaitQueue fairness requirement.

source/connection-monitoring-and-pooling/tests/cmap-format/pool-create-min-size-error.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/connection-monitoring-and-pooling/tests/cmap-format/pool-create-min-size-error.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ failPoint:
1111
mode: { times: 50 }
1212
data:
1313
failCommands: ["isMaster","hello"]
14-
closeConnection: true
14+
errorCode: 91
1515
appName: "poolCreateMinSizeErrorTest"
1616
poolOptions:
1717
minPoolSize: 1

source/load-balancers/tests/sdam-error-handling.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/load-balancers/tests/sdam-error-handling.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,14 @@ tests:
153153
mode: { times: 1 }
154154
data:
155155
failCommands: [isMaster, hello]
156-
closeConnection: true
156+
errorCode: 11600
157157
appName: *singleClientAppName
158158
- name: insertOne
159159
object: *singleColl
160160
arguments:
161161
document: { x: 1 }
162162
expectError:
163-
isClientError: true
163+
isError: true
164164
expectEvents:
165165
- client: *singleClient
166166
eventType: cmap

source/logging/logging.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Drivers MUST support configuring where log messages should be output, including
9595
> - If the value is "stdout" (case-insensitive), log to stdout.
9696
> - If the value is "stderr" (case-insensitive), log to stderr.
9797
> - Else, if direct logging to files is supported, log to a file at the specified path. If the file already exists, it
98-
> MUST be appended to.
98+
> MUST be appended to.
9999
>
100100
> If the variable is not provided or is set to an invalid value (which could be invalid for any reason, e.g. the path
101101
> does not exist or is not writeable), the driver MUST log to stderr and the driver MAY attempt to warn the user about

source/server-discovery-and-monitoring/server-discovery-and-monitoring-tests.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,40 @@ This test requires failCommand appName support which is only available in MongoD
172172
5. Then verify that a ServerHeartbeatSucceededEvent and a ConnectionPoolReadyEvent (CMAP) are emitted.
173173

174174
6. Disable the failpoint.
175+
176+
## Connection Pool Backpressure
177+
178+
This test will be used to ensure that connection establishment failures during the TLS handshake do not result in a pool
179+
clear event. We create a setup client to enable the ingress connection establishment rate limiter, and then induce a
180+
connection storm. After the storm, we verify that some of the connections failed to checkout, but that the pool was not
181+
cleared.
182+
183+
This test requires MongoDB 7.0+.
184+
185+
1. Create a test client that listens to CMAP events, with maxConnecting=100. The higher maxConnecting will help ensure
186+
contention for creating connections.
187+
188+
2. Run the following commands to set up the rate limiter.
189+
190+
```python
191+
client.admin.command("setParameter", 1, ingressConnectionEstablishmentRateLimiterEnabled=True)
192+
client.admin.command("setParameter", 1, ingressConnectionEstablishmentRatePerSec=20)
193+
client.admin.command("setParameter", 1, ingressConnectionEstablishmentBurstCapacitySecs=1)
194+
client.admin.command("setParameter", 1, ingressConnectionEstablishmentMaxQueueDepth=1)
195+
```
196+
197+
3. Add a document to the test collection so that the sleep operations will actually block:
198+
`client.test.test.insert_one({})`.
199+
200+
4. Run the following find command on the collection in 100 parallel threads/coroutines. Run these commands concurrently
201+
but block on their completion, and ignore errors raised by the command.
202+
`client.test.test.find_one({"$where": "function() { sleep(2000); return true; }})`
203+
204+
5. Assert that at least 10 `ConnectionCheckOutFailedEvent` occurred.
205+
206+
6. Assert that 0 `PoolClearedEvent` occurred.
207+
208+
7. Sleep for 1 second to clear the rate limiter.
209+
210+
8. Ensure that the following command runs at test teardown even if the test fails.
211+
`client.admin("setParameter", 1, ingressConnectionEstablishmentRateLimiterEnabled=False)`.

source/server-discovery-and-monitoring/server-discovery-and-monitoring.md

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -434,18 +434,18 @@ correspond to [replica set member states](https://www.mongodb.com/docs/manual/re
434434
some replica set member states like STARTUP and RECOVERING are identical from the client's perspective, so they are
435435
merged into "RSOther". Additionally, states like Standalone and Mongos are not replica set member states at all.
436436

437-
| State | Symptoms |
438-
| --------------- | ------------------------------------------------------------------------------------------------------------------------- |
439-
| Unknown | Initial, or after a network error or failed hello or legacy hello call, or "ok: 1" not in hello or legacy hello response. |
440-
| Standalone | No "msg: isdbgrid", no setName, and no "isreplicaset: true". |
441-
| Mongos | "msg: isdbgrid". |
442-
| PossiblePrimary | Not yet checked, but another member thinks it is the primary. |
443-
| RSPrimary | "isWritablePrimary: true" or "ismaster: true", "setName" in response. |
444-
| RSSecondary | "secondary: true", "setName" in response. |
445-
| RSArbiter | "arbiterOnly: true", "setName" in response. |
446-
| RSOther | "setName" in response, "hidden: true" or not primary, secondary, nor arbiter. |
447-
| RSGhost | "isreplicaset: true" in response. |
448-
| LoadBalanced | "loadBalanced=true" in URI. |
437+
| State | Symptoms |
438+
| --------------- | -------------------------------------------------------------------------------------------------------- |
439+
| Unknown | Initial, or after a failed hello or legacy hello call, or "ok: 1" not in hello or legacy hello response. |
440+
| Standalone | No "msg: isdbgrid", no setName, and no "isreplicaset: true". |
441+
| Mongos | "msg: isdbgrid". |
442+
| PossiblePrimary | Not yet checked, but another member thinks it is the primary. |
443+
| RSPrimary | "isWritablePrimary: true" or "ismaster: true", "setName" in response. |
444+
| RSSecondary | "secondary: true", "setName" in response. |
445+
| RSArbiter | "arbiterOnly: true", "setName" in response. |
446+
| RSOther | "setName" in response, "hidden: true" or not primary, secondary, nor arbiter. |
447+
| RSGhost | "isreplicaset: true" in response. |
448+
| LoadBalanced | "loadBalanced=true" in URI. |
449449

450450
A server can transition from any state to any other. For example, an administrator could shut down a secondary and bring
451451
up a mongos in its place.
@@ -1055,7 +1055,10 @@ def handleError(error):
10551055
# next full scan.
10561056
if isNotWritablePrimary(error):
10571057
check failing server
1058-
elif isNetworkError(error) or (not error.completedHandshake and (isNetworkTimeout(error) or isAuthError(error))):
1058+
elif isNetworkError(error) or (not error.completedHandshake):
1059+
# Ignore errors that have a backpressure error label applied.
1060+
if error.hasLabel("SystemOverloadedError"):
1061+
continue
10591062
if type != LoadBalanced
10601063
# Mark the server Unknown
10611064
unknown = new ServerDescription(type=Unknown, error=error)
@@ -1139,16 +1142,20 @@ errors, network timeout errors, state change errors, and authentication errors.
11391142

11401143
##### Network error when reading or writing
11411144

1142-
To describe how the client responds to network errors during application operations, we distinguish two phases of
1145+
To describe how the client responds to network errors during application operations, we distinguish three phases of
11431146
connecting to a server and using it for application operations:
11441147

1145-
- *Before the handshake completes*: the client establishes a new connection to the server and completes an initial
1146-
handshake by calling "hello" or legacy hello and reading the response, and optionally completing authentication
1148+
- *Connection establishment and hello*: the client establishes a new connection to the server and completes an initial
1149+
handshake by calling "hello" or legacy hello and reading the response
1150+
- *Authentication step*: the client optionally completes an authentication step
11471151
- *After the handshake completes*: the client uses the established connection for application operations
11481152

1149-
If there is a network error or timeout on the connection before the handshake completes, the client MUST replace the
1150-
server's description with a default ServerDescription of type Unknown when the TopologyType is not LoadBalanced, and
1151-
fill the ServerDescription's error field with useful information.
1153+
If there is a network error or timeout on the connection establishment or the hello, the client MUST NOT change the
1154+
server's description.
1155+
1156+
If there is an network error or timeout during the authentication step, the client MUST replace the server's description
1157+
with a default ServerDescription of type Unknown when the TopologyType is not LoadBalanced, and fill the
1158+
ServerDescription's error field with useful information.
11521159

11531160
If there is a network error or timeout on the connection before the handshake completes, and the TopologyType is
11541161
LoadBalanced, the client MUST keep the ServerDescription as LoadBalancer.
@@ -1253,11 +1260,12 @@ if and only if the error is "node is shutting down" or the error originated from
12531260
and [other transient errors](#other-transient-errors) and
12541261
[Why close connections when a node is shutting down?](#why-close-connections-when-a-node-is-shutting-down).)
12551262

1256-
##### Authentication and Handshake errors
1263+
##### MongoDB Handshake errors
12571264

1258-
If the driver encounters errors when establishing application connections (this includes the initial handshake and
1259-
authentication), the driver MUST mark the server Unknown and clear the server's connection pool if the TopologyType is
1260-
not LoadBalanced. (See [Why mark a server Unknown after an auth error?](#why-mark-a-server-unknown-after-an-auth-error))
1265+
If the driver encounters errors that do not have the backpressure error label (`SystemOverloadedError`) applied when
1266+
establishing application connections (this includes the initial handshake and authentication), the driver MUST mark the
1267+
server Unknown and clear the server's connection pool if the TopologyType is not LoadBalanced. (See
1268+
[Why mark a server Unknown after an auth error?](#why-mark-a-server-unknown-after-an-auth-error))
12611269

12621270
### Monitoring SDAM events
12631271

@@ -2027,6 +2035,8 @@ oversaw the specification process.
20272035
- 2025-01-22: Add error messages when a new primary is elected or a primary with a stale electionId or setVersion is
20282036
discovered.
20292037

2038+
- 2025-11-21: Add handling of backpressure error labels.
2039+
20302040
______________________________________________________________________
20312041

20322042
[^1]: "localThresholdMS" was called "secondaryAcceptableLatencyMS" in the Read Preferences Spec, before it was superseded

source/server-discovery-and-monitoring/server-monitoring.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ MUST be used to satisfy the check and update the topology.
163163
When a client successfully calls hello or legacy hello to handshake a new connection for application operations, it
164164
SHOULD use the hello or legacy hello reply to update the ServerDescription and TopologyDescription, the same as with a
165165
hello or legacy hello reply on a monitoring socket. If the hello or legacy hello call fails, the client SHOULD mark the
166-
server Unknown and update its TopologyDescription, the same as a failed server check on monitoring socket.
166+
server Unknown and update its TopologyDescription, the same as a failed server check on monitoring socket, unless the
167+
connection pool has added the `SystemOverloadedError` label to the error.
167168

168169
##### Clients use the streaming protocol when supported
169170

@@ -254,6 +255,12 @@ default lastUpdateTime "infinity ago", so it scans them in random order. This ra
254255
many clients start at once. A client's subsequent scans of the mongoses are always in the same order, since their
255256
lastUpdateTimes are always in the same order by the time a scan ends.
256257

258+
##### Handling of backpressure labels
259+
260+
Because the scan may occur on an authenticated connection in single-threaded monitors, the server may apply backpressure
261+
by failing the command with a `SystemOverloadedError` label. The driver MUST not close the connection when this label is
262+
encountered.
263+
257264
#### minHeartbeatFrequencyMS
258265

259266
If a client frequently rechecks a server, it MUST wait at least minHeartbeatFrequencyMS milliseconds since the previous

source/server-discovery-and-monitoring/tests/unified/backpressure-network-error-fail.json

Lines changed: 140 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)