<!-- llms-explorer concept facts · https://llms-explorer.com/tree/load-balanced-cursor-and-transaction-connection-pinning/ · pack 2026-10-27 · ~1988 tokens -->

# Load-balanced cursor and transaction connection pinning

> Source-anchored research for Load-balanced cursor and transaction connection pinning. Run status: COMPLETED.

Parent: [CMAP Connection Pooling](https://llms-explorer.com/tree/cmap-connection-pooling/) · 4 facets · 20 facts · page: https://llms-explorer.com/tree/load-balanced-cursor-and-transaction-connection-pinning/

## How it works

- Successful commitTransaction does not itself unpin the session because commit can be repeated. Unpin on abort, TransientTransactionError, UnknownTransactionCommitResult, a subsequent transaction, a nontransactional operation, or session termination; retryable commit/abort errors can require unpinning before retry selection. (medium confidence) — [source](https://specifications.readthedocs.io/en/latest/transactions/transactions/)
- DRIVERS-2647 documents a leak trigger: start a session, start a transaction, insertMany, commitTransaction, then endSession without releasing the pinned connection. A bounded pool can eventually run out of available connections. The inspected Ruby session implementation explicitly unpins on end_session. (medium confidence) — [source](https://jira.mongodb.org/browse/DRIVERS-2647)
- The inspected Ruby cursor cleanup removes cursor pinning but checks the connection back in only when no other pin remains, including transaction ownership. Cursor cleanup alone therefore need not make that connection available. (low confidence) (verified as of 2026-10-27) — [source](https://github.com/mongodb/mongo-ruby-driver/blob/master/lib/mongo/cursor.rb)
- A cursor-initiating command keeps its connection checked out when it returns a nonzero cursor ID. Subsequent getMore commands use that connection. A failed initiating command or an initial cursor ID of zero does not require retaining a connection for that cursor. (medium confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- A cursor returns its connection when getMore drains the server cursor, or when close performs any necessary killCursors on that same connection. Cleanup must return the connection even if killCursors fails. A connection still owned by a transaction must remain checked out; the Ruby implementation explicitly checks for that ownership. (medium confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- Successful commitTransaction must not itself unpin the session because commit can be called again. Load-balanced connection release follows the sharded-transaction unpin rules, so assuming commit immediately restores a pool slot is unsafe. (high confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- Transaction unpin triggers include abort regardless of success, a TransientTransactionError, an UnknownTransactionCommitResult on commit, beginning a new transaction after commit, a nontransactional operation on the session, and ending the session. Relevant retry paths unpin before selecting a server again. (high confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)

## Problems, failure modes and limitations

- If getMore encounters a network error, the load-balancer specification requires retaining the cursor pin until close. Close skips killCursors on the invalid connection and returns it to the pool; returning it does not make the broken socket reusable. (medium confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- The inspected Ruby cursor constructor releases the checked-out load-balanced connection if construction raises, including interrupts. Its comments identify failure to return this connection as a permanent leak risk. This is implementation evidence, not a verified release inventory. (low confidence) (verified as of 2026-10-27) — [source](https://github.com/mongodb/mongo-ruby-driver/blob/master/lib/mongo/cursor.rb)

## Comparisons and alternatives

- Pinning consumes a checked-out pool slot across multiple commands and can hold it while application code is idle. Load-balanced mode has one maxPoolSize budget rather than one budget per mongos, so long-lived cursors and transactions can create pool pressure. (medium confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- CMAP events describe the pinned checkout lifetime: the driver emits one checkout-start event, one checkout success or failure event, and one check-in event, rather than a new checkout/check-in pair for each command using the pin. (low confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)

## Facts and statements

- In MongoDB load-balanced mode, retaining one physical connection preserves backend affinity for cursors and transactions. Selecting the same load-balancer address does not identify the same backend service; the driver must keep using the pinned socket. (high confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- A nonzero cursor ID keeps its connection checked out for getMore. Cursor exhaustion or close releases cursor ownership; necessary killCursors uses that connection. (medium confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- Load-balanced transactions use the same connection for their commands, except certain commitTransaction and abortTransaction retries. Two concurrent transactions in different sessions of the same client must not share a connection. (high confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- Load-balanced pinning emits one checkout lifecycle and one check-in, not a lifecycle for every command. Timeout diagnostics must distinguish cursor, transaction, and other connection usage. (low confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- The specification warns that switching from multiple mongos pools to one load-balancer pool reduces aggregate capacity to maxPoolSize; retained cursors and transactions create additional occupancy pressure. (low confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- The load-balancer specification requires pools to distinguish connections used by cursors, transactions, and other operations, and include those counts with maxPoolSize in a pool wait timeout. These counts describe retained resource ownership, not merely commands currently executing. (low confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- For multithreaded drivers, concurrent next and close on a pinned cursor must either be explicitly prevented or documented as unsupported; pinning alone does not provide safe concurrent cursor use. (low confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- Single-threaded drivers without a connection pool must use one socket to the load balancer. The historical libmongoc discussion explains that its one connection per client provides implicit affinity even without explicit session pin state; this is an implementation-specific exception to pooled-driver assumptions. (medium confidence) — [source](https://specifications.readthedocs.io/en/latest/load-balancers/load-balancers/)
- The transport rationale is connection-level routing: HAProxy documents balancing TCP connections, while Envoy documents per-connection upstream cluster selection. This supports the affinity model underlying a retained socket, but neither source independently establishes MongoDB cursor or transaction release rules. (medium confidence) — [source](https://www.haproxy.com/documentation/haproxy-configuration-tutorials/protocol-support/tcp/)
