MongoDB Mastery
MongoDB Mastery lessons / Module 31: Drivers & Connection Pooling

Drivers, connection pools, timeouts and retries

Level 5: Database Architect26 min

Configure the application side correctly: one client per process, pool sizing, server selection and timeouts, retryable writes, and avoiding connection storms.

This lesson is interactive. Run every example against an in-browser MongoDB, see explain() plans drawn as trees, and take the quiz.
Open the interactive lesson
You will learn to
  • Size connection pools and compute total connections
  • Set timeouts that fail fast instead of hanging
  • Use retryable writes and reads
  • Avoid connection storms in serverless and autoscaling environments

conceptThe concept

  • Create one MongoClient per process and reuse it. It holds a pool per server plus monitoring connections.
  • maxPoolSize (default 100), minPoolSize (0), maxIdleTimeMS, maxConnecting (2, limits simultaneous connection establishment), waitQueueTimeoutMS.
  • serverSelectionTimeoutMS (30 s default): how long to wait for a suitable server (e.g. during an election). connectTimeoutMS and socketTimeoutMS; newer drivers support a single client-side operation timeout (timeoutMS).
  • retryWrites=true (default) retries a failed single write once on a new primary; retryReads=true does the same for reads.
  • Total server connections = app instances x pool size per instance (per mongos/member) + monitoring connections. With 200 pods x 100 = 20,000 potential connections per member.

whyWhy it matters

Connection storms are a classic outage: an autoscaler adds 300 pods, each opens up to 100 connections, the server spends its CPU on authentication handshakes (SCRAM is deliberately expensive) and memory on connection threads, and everything slows down, causing more scaling. Bad timeouts turn a 10 s failover into minutes of hung requests.

Use caseReal-world scenario

A service with 40 pods runs ~30 concurrent DB operations per pod at peak. Setting maxPoolSize=40, minPoolSize=5, maxConnecting=2, maxIdleTimeMS=60000 caps the cluster at 1,600 connections per member. serverSelectionTimeoutMS=5000 and an operation timeout of 2 s make requests fail fast during incidents, and retryable writes absorb failovers.

Try itExamples

Run and edit these in the interactive lesson.

Node.js client done right
// create once at startup, share everywhere
const client = new MongoClient(process.env.MONGO_URI, {
  maxPoolSize: 40,
  minPoolSize: 5,
  maxConnecting: 2,
  maxIdleTimeMS: 60_000,
  serverSelectionTimeoutMS: 5_000,
  timeoutMS: 2_000,                // client-side operation timeout (newer drivers)
  retryWrites: true,
  retryReads: true,
  appName: "orders-api",          // shows up in currentOp and logs
});
await client.connect();
export const db = client.db("shop");
Serverless (AWS Lambda) pattern
let client;                       // module scope survives warm invocations
export async function handler(event) {
  client ??= await new MongoClient(process.env.MONGO_URI, { maxPoolSize: 5 }).connect();
  return client.db("shop").collection("orders").findOne({ orderNo: event.orderNo });
}
See connections from the server side
db.serverStatus().connections
db.currentOp({ appName: "mongosh" }).inprog.length

internalsUnder the hood

Each server connection uses a thread on mongod (or a pooled ingress thread with newer async networking) and roughly 1 MB of memory. Drivers monitor each server with a dedicated connection (and an RTT connection in newer drivers), using hello every heartbeatFrequencyMS (10 s) or immediately after an error. Retryable writes use a transaction number in a logical session so the server can detect and ignore a duplicate retry.

Avoid / preferDo this, not that

Avoid
app.get("/orders", async (req, res) => {
  const client = new MongoClient(uri)
  await client.connect()
  // ...
})
Prefer
const client = new MongoClient(uri)   // once, at startup
await client.connect()
app.get("/orders", async (req, res) => { /* use client */ })

A client per request means a TCP and TLS handshake and authentication every time, and connection storms under load. One client per process shares a pool.

Avoid
new MongoClient(uri)   // default timeouts everywhere
Prefer
new MongoClient(uri, { maxPoolSize: 50, serverSelectionTimeoutMS: 5000, connectTimeoutMS: 5000 })
// plus maxTimeMS on queries that must not run long

Explicit pool size and timeouts make failures fast and visible instead of piling up waiting requests.

PitfallsCommon mistakes

  • Creating a new MongoClient per request.
  • Huge default pools multiplied by hundreds of pods.
  • No timeouts: requests hang for 30 s+ during elections or network issues.
  • Catching and silently retrying all errors in application code, duplicating non-idempotent writes.
  • Not setting appName, making it impossible to tell which service owns which connections.

LimitsWhen not to use it

Raising maxPoolSize is not a fix for slowness. Every connection costs server memory and a thread; twenty pods with a pool of 500 each can open 10,000 connections. Find the slow operations holding connections first.

SecuritySecurity notes

Use tls=true in production URIs, load credentials from a secrets manager, and never log the connection string: it often contains the password.

ChallengeTry it yourself

From serverStatus().connections, show current and available connections and the percentage of the limit in use.

Show one solution
One solution
const c = db.serverStatus().connections
printjson({ current: c.current, available: c.available, usedPct: +(100 * c.current / (c.current + c.available)).toFixed(3) })

InterviewQuestions you should be able to answer

How do you size a connection pool?

From concurrency, not from defaults: peak concurrent operations per instance (Little's law: throughput x latency) plus some headroom. Multiply by instance count and check against server limits and memory. Use maxConnecting to avoid storms and minPoolSize to avoid cold-start latency. Monitor wait queue time in the driver and connection counts on the server.

What are retryable writes?

Drivers automatically retry certain write operations once after a network error or a "not primary" error, after selecting a new primary. The server uses the session id and transaction number to ensure the write is applied only once. Multi-document writes like updateMany are not retryable as a whole; single-document writes and bulk writes of retryable operations are.

QuizCheck your understanding

4 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.