<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Databases on Krishnatejaswi S</title>
    <link>https://KTS-o7.github.io/bear/tags/databases/</link>
    <description>Recent content in Databases on Krishnatejaswi S</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</managingEditor>
    <webMaster>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</webMaster>
    <copyright>Krishnatejaswi S</copyright>
    <lastBuildDate>Sun, 03 May 2026 00:00:00 +0530</lastBuildDate>
    <atom:link href="https://KTS-o7.github.io/bear/tags/databases/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>How Spanner Does Distributed Transactions Without Classic 2PC</title>
      <link>https://KTS-o7.github.io/bear/systems/spanner_distributed_transactions/</link>
      <pubDate>Sun, 03 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/spanner_distributed_transactions/</guid>
      <description>&lt;p&gt;Two-phase commit has a well-known problem: crash the coordinator between Phase 1 and Phase 2 and every participant is stuck holding locks, waiting, with no safe way to proceed. 2PC is a blocking protocol.&lt;/p&gt;&#xA;&lt;p&gt;Google Spanner runs distributed transactions across a globally replicated database at scale. It can&amp;rsquo;t afford to block on coordinator failure. Its solution is to not use a standalone coordinator at all &amp;ndash; it runs 2PC on top of Paxos groups, which means the coordinator itself is fault-tolerant.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Two-phase commit has a well-known problem: crash the coordinator between Phase 1 and Phase 2 and every participant is stuck holding locks, waiting, with no safe way to proceed. 2PC is a blocking protocol.</p>
<p>Google Spanner runs distributed transactions across a globally replicated database at scale. It can&rsquo;t afford to block on coordinator failure. Its solution is to not use a standalone coordinator at all &ndash; it runs 2PC on top of Paxos groups, which means the coordinator itself is fault-tolerant.</p>
<h2 id="the-core-move-paxos-groups-as-participants">The Core Move: Paxos Groups as Participants</h2>
<p>In classic 2PC, each participant is a single node. It votes, acquires locks, and waits. If it crashes, the transaction is stuck.</p>
<p>Spanner&rsquo;s participants are Paxos groups. Each shard of data is managed by a Paxos group &ndash; typically 5 replicas spread across datacenters. The Paxos leader for each group acts as the 2PC participant. Lock acquisition and voting happen on the leader, and the decision is durably replicated to the group before the leader responds.</p>
<p>If the coordinator crashes, a new coordinator can be elected (it&rsquo;s also a Paxos group). If a participant leader crashes, a new leader in that group takes over &ndash; the lock state and vote are already replicated, so the protocol can continue.</p>
<p>The blocking problem is solved not by changing the protocol but by making every node in the protocol fault-tolerant.</p>
<h2 id="truetime">TrueTime</h2>
<p>Spanner adds something 2PC doesn&rsquo;t have: globally synchronized timestamps, accurate enough to use for transaction ordering.</p>
<p>Google runs GPS receivers and atomic clocks in each datacenter and exposes a <code>TrueTime</code> API that returns a time interval <code>[earliest, latest]</code> rather than a single timestamp. The interval accounts for clock uncertainty. The guarantee is that the real current time lies somewhere in that interval.</p>
<p>Spanner uses TrueTime to assign commit timestamps. The rule: a transaction&rsquo;s commit timestamp must be after the <code>latest</code> bound of TrueTime at the moment the coordinator decides to commit. This means Spanner waits for a brief period &ndash; typically 1-7ms &ndash; before declaring a transaction committed, to ensure that any other transaction starting after this one will see a strictly later timestamp.</p>
<p>This is the &ldquo;commit wait.&rdquo; It&rsquo;s intentional latency, paid to guarantee that commit timestamps accurately reflect real-world ordering.</p>
<h2 id="external-consistency">External Consistency</h2>
<p>The property this buys is external consistency: if transaction T1 commits before T2 starts (in real-world time), T2 will see T1&rsquo;s writes. This is stronger than serializability &ndash; serializable transactions can be ordered in any way consistent with their conflicts, even if that reorders real-world causality. External consistency preserves real-world order.</p>
<p>In practical terms: if you commit a write and then tell a colleague &ldquo;it&rsquo;s committed, go read it,&rdquo; their read will see your write. There&rsquo;s no window where a causally later transaction misses a causally earlier committed write.</p>
<p>Most distributed databases don&rsquo;t provide this. They provide serializability, which is strong, but allows committed writes to be temporarily invisible to transactions that started after the commit in real time. Spanner&rsquo;s external consistency closes that gap.</p>
<h2 id="what-the-2pc-layer-actually-looks-like">What the 2PC Layer Actually Looks Like</h2>
<p>A read-write transaction in Spanner:</p>
<ol>
<li>Client reads data, buffers writes locally (no locks yet)</li>
<li>Client sends commit request to the coordinator Paxos group</li>
<li>Coordinator runs 2PC with each participant Paxos group:
<ul>
<li>Sends <code>PREPARE</code> to each participant leader</li>
<li>Each participant leader acquires locks, logs the prepare record to its Paxos group, replies <code>YES</code></li>
<li>Coordinator picks a commit timestamp (TrueTime <code>latest</code> + epsilon), logs it to its group</li>
<li>Sends <code>COMMIT</code> with the timestamp to all participants</li>
</ul>
</li>
<li>Coordinator waits out the commit wait interval</li>
<li>Participants apply the writes at the commit timestamp, release locks</li>
<li>Client sees commit confirmation</li>
</ol>
<p>The 2PC messages are still there. The key difference is that every step that touches durable state goes through a Paxos replication round first. The coordinator can crash and be replaced. A participant leader can crash and its successor has the full prepare state. Nothing blocks permanently.</p>
<h2 id="the-cost">The Cost</h2>
<p>The commit wait adds latency. For single-region transactions it&rsquo;s small (sub-millisecond when clocks are well-synchronized). For multi-region transactions where the coordinator and participants are in different continents, round-trip latency dominates and the TrueTime uncertainty is larger &ndash; you&rsquo;re looking at tens of milliseconds.</p>
<p>Spanner is designed for this tradeoff: strong global consistency in exchange for latency that scales with geographic distance. For financial systems, global inventory, or anything that requires real external consistency, that tradeoff is often correct. For low-latency local transactions, it&rsquo;s usually not.</p>
<p>Classic 2PC is blocking because the coordinator is a single node. Spanner&rsquo;s answer is not to fix 2PC &ndash; it&rsquo;s to make every node in the 2PC protocol a replicated state machine.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Saga Outbox Pattern: Reliable Event Delivery Without Two-Phase Commit</title>
      <link>https://KTS-o7.github.io/bear/systems/saga_outbox_pattern/</link>
      <pubDate>Sun, 03 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/saga_outbox_pattern/</guid>
      <description>&lt;p&gt;The Saga pattern gives you distributed transactions without cross-service locks. Each step publishes an event when it completes, triggering the next step. But there&amp;rsquo;s a gap in the naive implementation: publishing an event and updating your local database are two separate operations. If your service crashes between them, you get a committed database write with no event published, or an event published with no matching database write. The saga is now in an inconsistent state.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The Saga pattern gives you distributed transactions without cross-service locks. Each step publishes an event when it completes, triggering the next step. But there&rsquo;s a gap in the naive implementation: publishing an event and updating your local database are two separate operations. If your service crashes between them, you get a committed database write with no event published, or an event published with no matching database write. The saga is now in an inconsistent state.</p>
<p>The outbox pattern closes that gap.</p>
<h2 id="the-dual-write-problem">The Dual-Write Problem</h2>
<p>Say the flight reservation step succeeds. You update your database to mark the reservation as confirmed and then publish a <code>FlightReserved</code> event to your message broker. These are two operations against two different systems. There&rsquo;s no way to make them atomic without 2PC &ndash; which is exactly what sagas are trying to avoid.</p>
<p>If you write to the database first and then crash before publishing, the event is never delivered. The hotel reservation step never starts. The saga stalls. Depending on your monitoring, this might sit undetected for a while.</p>
<p>If you publish first and then crash before writing to the database, you&rsquo;ve told the hotel step to proceed even though the flight reservation isn&rsquo;t durably recorded. The flight service comes back up with no record of the reservation. You&rsquo;ve introduced a phantom.</p>
<h2 id="the-outbox">The Outbox</h2>
<p>The fix: write the event to an outbox table in the same database, in the same local transaction as your state update. Then have a separate process relay events from the outbox to the message broker.</p>





<pre tabindex="0"><code>BEGIN TRANSACTION
  UPDATE flights SET status = &#39;reserved&#39; WHERE id = ?
  INSERT INTO outbox (event_type, payload, created_at)
    VALUES (&#39;FlightReserved&#39;, &#39;{&#34;booking_id&#34;: 123, ...}&#39;, NOW())
COMMIT</code></pre><p>The state change and the event are now atomic with respect to the database. Either both are committed or neither is. The outbox row is durable.</p>
<p>A separate relay process &ndash; sometimes called the outbox processor or message relay &ndash; polls the outbox table, publishes each event to the message broker, and marks the row as processed once delivery is confirmed.</p>





<pre tabindex="0"><code>Relay process:
  SELECT * FROM outbox WHERE processed_at IS NULL ORDER BY created_at LIMIT 100
  FOR EACH row:
    publish to message broker
    UPDATE outbox SET processed_at = NOW() WHERE id = row.id</code></pre><h2 id="at-least-once-delivery">At-Least-Once Delivery</h2>
<p>The relay delivers at-least-once. If the relay crashes after publishing but before marking the row as processed, it will publish the same event again on restart. Downstream consumers have to be idempotent &ndash; processing the same event twice should produce the same result as processing it once.</p>
<p>This is a real design constraint. Your hotel reservation step needs to check whether it already processed a given <code>FlightReserved</code> event before creating a new reservation. Deduplication is typically done with a unique constraint on the event ID or booking ID in the receiving service&rsquo;s database.</p>
<p>At-least-once with idempotent consumers is the standard contract in event-driven systems. Exactly-once is theoretically possible with additional coordination but practically rare &ndash; most systems accept at-least-once and invest in idempotency instead.</p>
<h2 id="change-data-capture-as-an-alternative-relay">Change Data Capture as an Alternative Relay</h2>
<p>Polling the outbox table works but adds load to the database. An alternative is Change Data Capture (CDC): instead of a polling process, you read directly from the database replication log.</p>
<p>Tools like Debezium connect to PostgreSQL&rsquo;s logical replication stream or MySQL&rsquo;s binlog and emit a change event for every row insert. Point Debezium at the outbox table and it publishes events automatically, with no polling query needed.</p>
<p>CDC-based relay has lower overhead and lower latency. The tradeoff: it&rsquo;s another piece of infrastructure to run and operate. For smaller deployments, polling is simpler. For high-throughput systems where polling lag or database load is a concern, CDC is worth it.</p>
<h2 id="saga-state-persistence">Saga State Persistence</h2>
<p>Related to the outbox is the question of where saga state itself lives.</p>
<p>The orchestrator driving the saga needs to know the current step, which steps have completed, and what the compensating actions are if something fails. Two common approaches:</p>
<p><strong>Dedicated saga store</strong>: the orchestrator writes saga state to its own database table after each step. Simple, queryable, easy to inspect when debugging a stuck saga. The outbox pattern applies here too &ndash; saga state update and event publish happen in the same transaction.</p>
<p><strong>Event sourcing</strong>: saga state is derived from the event log itself. There&rsquo;s no explicit state table &ndash; you reconstruct the saga&rsquo;s current position by replaying events. More complex to implement but gives you a full audit trail of everything that happened and an easy path to replaying failed sagas.</p>
<p>Most teams start with a dedicated saga store and move to event sourcing only if they need the audit trail or replay capability.</p>
<h2 id="what-fails-in-compensation">What Fails in Compensation</h2>
<p>One more gap worth naming: compensating transactions can fail too.</p>
<p>You cancel the hotel reservation as part of rolling back a failed booking. The cancel call times out. Now your saga is stuck in a partially compensated state &ndash; the flight is cancelled but the hotel reservation is still live.</p>
<p>The standard answer is retry with backoff. Compensating transactions need to be idempotent for the same reason forward steps do. The outbox pattern helps here too &ndash; compensation requests go through the same outbox mechanism, so they&rsquo;re durably enqueued even if the downstream service is temporarily unavailable.</p>
<p>If compensation keeps failing after retries, the saga is in a &ldquo;saga failure&rdquo; state that requires human intervention or a dead-letter queue. Building an operator UI for stuck sagas is not glamorous work, but it&rsquo;s necessary in any production saga implementation.</p>
<p>The outbox pattern doesn&rsquo;t make sagas simple. It removes the dual-write problem and gives you reliable event delivery &ndash; which turns a fundamentally broken approach into one that&rsquo;s merely complex.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Consistent Hashing: Scaling a Cluster Without Reshuffling the World</title>
      <link>https://KTS-o7.github.io/bear/systems/consistent_hashing/</link>
      <pubDate>Sat, 02 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/consistent_hashing/</guid>
      <description>&lt;p&gt;The naive approach to distributing keys across N cache nodes is &lt;code&gt;hash(key) % N&lt;/code&gt;. It works fine until you add or remove a node.&lt;/p&gt;&#xA;&lt;p&gt;When N changes to N+1 or N-1, nearly every key remaps to a different node. You go from a warm cache to a cold one in an instant &amp;ndash; a thundering herd of cache misses hitting your database all at once. For a large cluster, that&amp;rsquo;s enough to take down a service.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The naive approach to distributing keys across N cache nodes is <code>hash(key) % N</code>. It works fine until you add or remove a node.</p>
<p>When N changes to N+1 or N-1, nearly every key remaps to a different node. You go from a warm cache to a cold one in an instant &ndash; a thundering herd of cache misses hitting your database all at once. For a large cluster, that&rsquo;s enough to take down a service.</p>
<p>Consistent hashing fixes this. Add or remove a node, and only the keys that were assigned to that node need to move. Everything else stays put.</p>
<h2 id="the-hash-ring">The Hash Ring</h2>
<p>Instead of <code>hash(key) % N</code>, arrange all possible hash values on a ring from 0 to 2^32. Each node gets a position on the ring by hashing its name or ID. To find which node owns a key: hash the key, then walk clockwise around the ring until you hit a node.</p>





<pre tabindex="0"><code>          0
         / \
    Node C   Node A
       |         |
    Node B --- Node D</code></pre><p>Key K hashes to a point on the ring. The first node clockwise from that point owns K.</p>
<p>Now add a new node between B and C. Only the keys that were between B and the new node&rsquo;s position need to move &ndash; from C to the new node. Everything else is unaffected. In an N-node cluster, adding one node moves roughly 1/N of the keys instead of nearly all of them.</p>
<h2 id="virtual-nodes">Virtual Nodes</h2>
<p>Raw consistent hashing has a load distribution problem. With a small number of physical nodes, the gaps between them on the ring are uneven &ndash; one node might own 30% of the keyspace, another 10%. Remove a node and all its load lands on a single neighbor.</p>
<p>Virtual nodes fix this: each physical node gets multiple positions on the ring, typically by hashing it with different suffixes (<code>node1-0</code>, <code>node1-1</code>, <code>node1-2</code>, &hellip;). With enough virtual nodes per physical node, the load distributes evenly across the ring. Removing one node scatters its keys across many neighbors rather than dumping them all on one.</p>
<p>This is the production configuration in every serious deployment.</p>
<h2 id="where-its-used">Where It&rsquo;s Used</h2>
<ul>
<li><strong>Amazon DynamoDB</strong> &ndash; consistent hashing across storage nodes, virtual nodes for even distribution</li>
<li><strong>Apache Cassandra</strong> &ndash; the token ring is consistent hashing; each node owns a range of tokens</li>
<li><strong>Memcached</strong> &ndash; the ketama client implements consistent hashing for cache node assignment</li>
<li>Basically every distributed cache or storage system that needs to scale nodes without full reshuffling</li>
</ul>
<p>The pattern generalizes beyond caching. Any system that needs to assign work or data to nodes &ndash; and occasionally add or remove nodes &ndash; runs into the same problem. Consistent hashing is the standard solution.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Two-Phase Commit: Distributed Atomicity and Its Fatal Flaw</title>
      <link>https://KTS-o7.github.io/bear/systems/two_phase_commit/</link>
      <pubDate>Sat, 02 May 2026 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/two_phase_commit/</guid>
      <description>&lt;p&gt;The problem is simple to state: you have a transaction that touches multiple nodes. You want all of them to commit, or none of them to. No partial states, no one node committing while another aborts.&lt;/p&gt;&#xA;&lt;p&gt;Two-phase commit (2PC) solves this. It&amp;rsquo;s been in production databases for decades. It also has a flaw that makes it unusable for anything long-running, which is why most modern large-scale systems don&amp;rsquo;t use it directly.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>The problem is simple to state: you have a transaction that touches multiple nodes. You want all of them to commit, or none of them to. No partial states, no one node committing while another aborts.</p>
<p>Two-phase commit (2PC) solves this. It&rsquo;s been in production databases for decades. It also has a flaw that makes it unusable for anything long-running, which is why most modern large-scale systems don&rsquo;t use it directly.</p>
<h2 id="the-protocol">The Protocol</h2>
<p>There&rsquo;s a coordinator and a set of participants. The coordinator drives the protocol.</p>
<p><strong>Phase 1 &ndash; Prepare</strong></p>
<p>The coordinator sends <code>PREPARE</code> to every participant. Each participant:</p>
<ul>
<li>Writes the transaction to a durable log</li>
<li>Acquires the necessary locks</li>
<li>Replies <code>YES</code> (ready to commit) or <code>NO</code> (something failed)</li>
</ul>
<p><strong>Phase 2 &ndash; Commit or Abort</strong></p>
<ul>
<li>If all participants said YES: coordinator sends <code>COMMIT</code> to everyone</li>
<li>If anyone said NO: coordinator sends <code>ABORT</code></li>
</ul>
<p>Participants execute the decision and release their locks.</p>





<pre tabindex="0"><code>Coordinator         Node A        Node B
    |--- PREPARE -------&gt;|             |
    |--- PREPARE ---------|-----------&gt;|
    |&lt;-- YES ------------|             |
    |&lt;-- YES ------------|-------------|
    |--- COMMIT --------&gt;|             |
    |--- COMMIT ----------|-----------&gt;|</code></pre><p>Clean path works fine. Every node ends up in the same state. Locks are held only for the duration of the protocol.</p>
<h2 id="the-fatal-flaw">The Fatal Flaw</h2>
<p>Crash the coordinator after phase 1 but before phase 2.</p>
<p>Participants are now stuck. They voted YES and locked their resources. They don&rsquo;t know whether the coordinator decided to commit or abort. They can&rsquo;t safely proceed in either direction &ndash; committing might be wrong if another participant said NO, aborting might be wrong if the coordinator was about to send COMMIT.</p>
<p>They wait. The locks stay held. The system is in limbo until the coordinator recovers.</p>
<p>This is the <strong>blocking problem</strong>. 2PC is a blocking protocol under failure. A single coordinator crash can stall the entire transaction indefinitely.</p>
<h2 id="the-attempted-fix-3pc">The Attempted Fix: 3PC</h2>
<p>Three-phase commit inserts a pre-commit phase between prepare and commit:</p>
<p><code>PREPARE → PRE-COMMIT → COMMIT</code></p>
<p>Pre-commit lets participants infer the coordinator&rsquo;s intent even if it crashes: if you received a pre-commit, the coordinator had seen all YES votes and was going to commit. You can safely commit yourself if the coordinator disappears after that point.</p>
<p>3PC eliminates most blocking scenarios. It costs one extra round of messages and still has edge cases under network partitions. It&rsquo;s not widely used in practice &ndash; the complexity tradeoff isn&rsquo;t worth it for most systems.</p>
<h2 id="what-systems-actually-do">What Systems Actually Do</h2>
<table>
  <thead>
      <tr>
          <th>Approach</th>
          <th>Mechanism</th>
          <th>Tradeoff</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Saga pattern</td>
          <td>Compensating transactions</td>
          <td>No locks, but no true atomicity</td>
      </tr>
      <tr>
          <td>Paxos / Raft</td>
          <td>Consensus algorithm</td>
          <td>Strong guarantees, higher complexity</td>
      </tr>
      <tr>
          <td>Optimistic concurrency</td>
          <td>Detect conflicts at commit time</td>
          <td>Works well for low-contention workloads</td>
      </tr>
  </tbody>
</table>
<p>Most large-scale systems avoid 2PC for anything user-facing. Short, internal database transactions &ndash; a single service committing to one database &ndash; are fine. Cross-service transactions that could run for seconds or involve user interaction are not.</p>
<p>2PC is worth understanding because it makes the tradeoff concrete: you can have distributed atomicity, but you&rsquo;re paying for it with availability under coordinator failure. Everything else in distributed transactions is a different point on that curve.</p>
]]></content:encoded>
    </item>
    <item>
      <title>MongoDB Connection Pool Exhaustion: Diagnosis, Fixes, and SDAM</title>
      <link>https://KTS-o7.github.io/bear/systems/connection_pool_exhaustion/</link>
      <pubDate>Sat, 06 Dec 2025 12:00:00 +0000</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/connection_pool_exhaustion/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;Why increasing maxPoolSize breaks your database.&lt;/strong&gt; A deep dive into MongoDB connection pooling, SDAM, WiredTiger tickets, and preventing transaction pinning.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;In the MongoDB ecosystem, the &amp;ldquo;connection pool&amp;rdquo; is often treated as a set-and-forget configuration within &lt;code&gt;MongoClient&lt;/code&gt;. However, when latency spikes or the database becomes unresponsive during high traffic, the pool is usually the first mechanism to break.&lt;/p&gt;&#xA;&lt;p&gt;Unlike relational databases that often sit behind proxies like PgBouncer, MongoDB drivers are &amp;ldquo;smart&amp;rdquo;—they maintain direct connections to multiple nodes in a Replica Set or Sharded Cluster. This complexity means exhaustion isn&amp;rsquo;t just about running out of sockets; it&amp;rsquo;s about topology monitoring, transaction pinning, and the brutal reality of the thread-per-connection model on the server side.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p><strong>Why increasing maxPoolSize breaks your database.</strong> A deep dive into MongoDB connection pooling, SDAM, WiredTiger tickets, and preventing transaction pinning.</p>
</blockquote>
<h2 id="introduction">Introduction</h2>
<p>In the MongoDB ecosystem, the &ldquo;connection pool&rdquo; is often treated as a set-and-forget configuration within <code>MongoClient</code>. However, when latency spikes or the database becomes unresponsive during high traffic, the pool is usually the first mechanism to break.</p>
<p>Unlike relational databases that often sit behind proxies like PgBouncer, MongoDB drivers are &ldquo;smart&rdquo;—they maintain direct connections to multiple nodes in a Replica Set or Sharded Cluster. This complexity means exhaustion isn&rsquo;t just about running out of sockets; it&rsquo;s about topology monitoring, transaction pinning, and the brutal reality of the thread-per-connection model on the server side.</p>
<h2 id="how-mongodb-connects-and-executes-queries">How MongoDB Connects and Executes Queries</h2>
<p>Before diving into pool exhaustion, it&rsquo;s worth understanding the lifecycle of a MongoDB connection and query. The process is more involved than a simple socket open/close.</p>
<h3 id="connection-establishment">Connection Establishment</h3>
<p>When your application calls <code>MongoClient.connect()</code>, the driver doesn&rsquo;t immediately open a socket. Instead, it:</p>
<ol>
<li>
<p><strong>TCP Socket Creation:</strong> Establishes a TCP/IP connection to the MongoDB server (default port 27017). This is a standard three-way handshake at the network layer.</p>
</li>
<li>
<p><strong>Wire Protocol Handshake:</strong> The client sends a <code>hello</code> command (formerly <code>isMaster</code> in older versions) to the server. This serves multiple purposes:</p>
<ul>
<li>Verifies the server is a MongoDB instance</li>
<li>Retrieves server capabilities (wire protocol version, supported authentication mechanisms)</li>
<li>In MongoDB 4.4+, can include <code>speculativeAuthenticate</code> to start authentication concurrently, reducing latency</li>
</ul>
</li>
<li>
<p><strong>Authentication:</strong> If credentials are provided, the client authenticates using mechanisms like SCRAM (Salted Challenge Response Authentication Mechanism):</p>
<ul>
<li>Client sends <code>saslStart</code> with chosen mechanism and initial payload</li>
<li>Server responds with a challenge</li>
<li>Client computes and sends proof derived from the challenge</li>
<li>Server verifies and grants access</li>
</ul>
</li>
</ol>
<p>This entire process—TCP handshake, TLS negotiation (if enabled), handshake, and authentication—typically takes 10-50ms depending on network latency and encryption overhead.</p>
<h3 id="query-execution-flow">Query Execution Flow</h3>
<p>Once authenticated, the connection is ready for operations. When you execute a query:</p>
<ol>
<li>
<p><strong>Connection Checkout:</strong> The driver checks out a connection from the pool (or waits if none are available—this is where exhaustion manifests).</p>
</li>
<li>
<p><strong>Message Construction:</strong> The query is serialized into MongoDB&rsquo;s wire protocol format. Modern MongoDB uses the <code>OP_MSG</code> opcode (introduced in 3.6), which includes:</p>
<ul>
<li>Standard message header (request ID, response-to ID)</li>
<li>Flags indicating message properties</li>
<li>Sections containing the actual command/query data</li>
<li>Optional checksum for integrity</li>
</ul>
</li>
<li>
<p><strong>Network Transmission:</strong> The message is sent over the TCP socket to the server.</p>
</li>
<li>
<p><strong>Server Processing:</strong> The <code>mongod</code> process:</p>
<ul>
<li>Receives the message on a dedicated thread (in the thread-per-connection model)</li>
<li>Parses the query</li>
<li>Plans execution strategy</li>
<li>Accesses data through WiredTiger (subject to ticket limits)</li>
<li>Creates a cursor for read operations</li>
</ul>
</li>
<li>
<p><strong>Response:</strong> The server sends results back over the same connection. For large result sets, the driver uses cursors to fetch documents in batches (default batch size is 101 documents).</p>
</li>
<li>
<p><strong>Connection Return:</strong> After the operation completes, the connection is returned to the pool for reuse—unless it&rsquo;s pinned to a transaction (see &ldquo;The Specific Pathology: Pinning&rdquo; below).</p>
</li>
</ol>
<h3 id="why-this-matters-for-pooling">Why This Matters for Pooling</h3>
<p>Each of these steps has latency and resource costs. The connection establishment overhead (10-50ms) is why pooling exists: reusing connections avoids paying this cost for every operation. However, the server-side thread allocation means each connection has a real memory and scheduling cost on <code>mongod</code>, which is why blindly increasing pool size backfires.</p>
<h2 id="connection-pool-internals">Connection Pool Internals</h2>
<p>Understanding how the pool manages connections internally is crucial for diagnosing exhaustion. The pool maintains connections in different states:</p>
<h3 id="connection-states">Connection States</h3>
<ol>
<li>
<p><strong>Idle:</strong> Connections that are established and authenticated but not currently in use. These sit in the pool, ready for immediate checkout. They consume server-side resources (thread + memory) but are available for operations.</p>
</li>
<li>
<p><strong>Checked Out:</strong> Connections actively executing an operation. When you call <code>collection.find()</code>, the driver checks out a connection, performs the operation, then returns it to idle—unless it&rsquo;s pinned (see &ldquo;The Specific Pathology: Pinning&rdquo;).</p>
</li>
<li>
<p><strong>Pending:</strong> Connections in the process of being established (TCP handshake, authentication). These don&rsquo;t count toward <code>maxPoolSize</code> until fully established.</p>
</li>
<li>
<p><strong>Closed:</strong> Connections that have been terminated, either due to errors, idle timeout, or explicit closure.</p>
</li>
</ol>
<h3 id="the-wait-queue">The Wait Queue</h3>
<p>When all connections are checked out and the pool has reached <code>maxPoolSize</code>, new requests enter a <strong>wait queue</strong>. This queue has its own limits:</p>
<ul>
<li><strong>waitQueueTimeoutMS:</strong> How long a request waits for a connection before throwing <code>ConnectionPoolCheckoutFailed</code>. Default is 0 (infinite wait—dangerous in production). <strong>Always set this</strong> to fail fast rather than hang indefinitely.</li>
</ul>
<p>If the wait queue fills up, your application starts failing requests. This is often the first symptom of pool exhaustion, not the root cause.</p>
<h3 id="connection-lifecycle">Connection Lifecycle</h3>
<p>The connection lifecycle follows these states and transitions:</p>





<pre tabindex="0"><code>[Not Created]
    │
    │ connect()
    ▼
[Pending] ── TCP handshake, authentication ──► [Idle]
    │                                              │
    │                                              │ Driver request
    │                                              ▼
    │                                        [Checked Out]
    │                                              │
    │                                              │ Operation complete
    │                                              │
    │                                              ▼
    │                                          [Idle] ◄──┐
    │                                              │     │
    │                                              │     │ Return to pool
    │                                              │     │
    │                                              │     │
    │                                              │ startTransaction()
    │                                              ▼     │
    │                                          [Pinned] ──┘
    │                                              │
    │                                              │ ʕ•ᴥ•ʔ DANGEROUS:
    │                                              │ Exclusive to session
    │                                              │ until commit/abort
    │                                              │
    │                                              │ commit/abortTransaction()
    │                                              ▼
    │                                          [Idle]
    │                                              │
    │                                              │ maxIdleTimeMS reached
    │                                              ▼
    │                                          [Closed]
    │                                              │
    └──────────────────────────────────────────────┘</code></pre><p><strong>State Descriptions:</strong></p>
<ul>
<li><strong>Pending:</strong> Connection is being established (TCP handshake, authentication)</li>
<li><strong>Idle:</strong> Connection is established and available in the pool for immediate use</li>
<li><strong>Checked Out:</strong> Connection is actively executing an operation</li>
<li><strong>Pinned:</strong> Connection is reserved for a transaction (removed from pool until transaction completes)</li>
<li><strong>Closed:</strong> Connection has been terminated</li>
</ul>
<p>The pool lazily creates connections up to <code>maxPoolSize</code>. If <code>minPoolSize &gt; 0</code>, it pre-warms that many connections at startup. Connections idle longer than <code>maxIdleTimeMS</code> are closed to free server resources.</p>
<h2 id="sdam-the-hidden-connection-overhead">SDAM: The Hidden Connection Overhead</h2>
<p>MongoDB drivers implement <strong>SDAM</strong> (Server Discovery and Monitoring), which maintains connections to <em>all</em> nodes in a Replica Set or Sharded Cluster, not just the primary.</p>
<h3 id="how-sdam-works">How SDAM Works</h3>
<ol>
<li>
<p><strong>Initial Discovery:</strong> On <code>MongoClient.connect()</code>, the driver connects to seed addresses and sends <code>hello</code> commands to discover all replica set members.</p>
</li>
<li>
<p><strong>Continuous Monitoring:</strong> Every <code>heartbeatFrequencyMS</code> (default 10 seconds), the driver sends <code>hello</code> to each known node to:</p>
<ul>
<li>Detect role changes (primary elections, step-downs)</li>
<li>Check server capabilities</li>
<li>Update topology description</li>
</ul>
</li>
<li>
<p><strong>Topology Events:</strong> When a primary election occurs, the driver:</p>
<ul>
<li>Closes connections to the old primary</li>
<li>Opens connections to the new primary</li>
<li>Potentially drains and recreates pools</li>
</ul>
</li>
</ol>
<h3 id="the-connection-multiplier">The Connection Multiplier</h3>
<p>If you have a 3-node replica set and set <code>maxPoolSize=50</code>, you&rsquo;re not creating 50 connections—you&rsquo;re creating up to <strong>150 connections</strong> (50 per node). SDAM maintains separate pools for each server in the topology.</p>
<p>During a primary election, the driver may temporarily exceed this as it transitions between nodes. If you&rsquo;re running 20 app instances, that&rsquo;s 3,000 potential connections during failover, even if only one node is serving traffic.</p>
<p><strong>Takeaway:</strong> Your <code>maxPoolSize</code> is per-server, not per-client. In replica sets, multiply by the number of nodes you&rsquo;re monitoring.</p>
<h2 id="connection-pool-configuration-options">Connection Pool Configuration Options</h2>
<p>Here are the key options that affect pool behavior:</p>
<ul>
<li>
<p><strong>maxPoolSize:</strong> Maximum connections per server. Default 100. This is the most commonly misconfigured setting.</p>
</li>
<li>
<p><strong>minPoolSize:</strong> Minimum idle connections to maintain. Default 0. Setting this &gt; 0 pre-warms the pool but consumes server resources even when idle.</p>
</li>
<li>
<p><strong>maxIdleTimeMS:</strong> Close idle connections after this duration. Default 0 (never close). Useful in serverless where you want aggressive cleanup.</p>
</li>
<li>
<p><strong>waitQueueTimeoutMS:</strong> How long to wait for a connection before failing. Default 0 (wait forever—dangerous in production). <strong>Always set this</strong> to fail fast rather than hang indefinitely.</p>
</li>
<li>
<p><strong>connectTimeoutMS:</strong> Timeout for initial connection establishment. Default 30 seconds.</p>
</li>
<li>
<p><strong>socketTimeoutMS:</strong> Timeout for individual operations. Default 0 (no timeout). Different from connection checkout timeout.</p>
</li>
<li>
<p><strong>heartbeatFrequencyMS:</strong> How often SDAM checks server status. Default 10 seconds. Lower values detect failovers faster but increase network overhead.</p>
</li>
<li>
<p><strong>TCP Keepalive:</strong> Often overlooked. If your app sits behind a firewall or load balancer (e.g., AWS Network Load Balancer) with a 350s idle timeout, and your pool has <code>maxIdleTimeMS</code> &gt; 350s, the LB will silently drop the connection. The driver won&rsquo;t know until it tries to write to the socket and fails. <strong>Important:</strong> <code>socketTimeoutMS</code> does not send keepalive probes. <strong>Best Practice:</strong> Set <code>maxIdleTimeMS</code> to be slightly lower than your infrastructure&rsquo;s timeout (e.g., 120000ms). On Linux, also tune <code>net.ipv4.tcp_keepalive_time</code> to be less than the cloud provider&rsquo;s timeout (usually &lt; 120 seconds).</p>
</li>
</ul>
<p><strong>Example Configuration (Node.js):</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="kr">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MongoClient</span><span class="p">(</span><span class="nx">uri</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nx">maxPoolSize</span><span class="o">:</span> <span class="mi">10</span><span class="p">,</span>              <span class="c1">// Small pool for 16-core DB
</span></span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="nx">minPoolSize</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>               <span class="c1">// Pre-warm 2 connections
</span></span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="nx">maxIdleTimeMS</span><span class="o">:</span> <span class="mi">30000</span><span class="p">,</span>         <span class="c1">// Close idle after 30s
</span></span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="nx">waitQueueTimeoutMS</span><span class="o">:</span> <span class="mi">5000</span><span class="p">,</span>     <span class="c1">// Fail after 5s wait
</span></span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="nx">connectTimeoutMS</span><span class="o">:</span> <span class="mi">10000</span><span class="p">,</span>      <span class="c1">// 10s connection timeout
</span></span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="nx">socketTimeoutMS</span><span class="o">:</span> <span class="mi">45000</span><span class="p">,</span>       <span class="c1">// 45s operation timeout
</span></span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="nx">heartbeatFrequencyMS</span><span class="o">:</span> <span class="mi">10000</span>   <span class="c1">// Check every 10s
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><h2 id="the-cost-of-a-connection-in-mongod">The Cost of a Connection in mongod</h2>
<p>While MongoDB has moved toward a more asynchronous networking model in recent versions, historically (and effectively, for resource planning), it relies on a synchronous thread-per-connection model.</p>
<p>When your application opens a new connection:</p>
<ol>
<li><strong>Network:</strong> TCP handshake + TLS negotiation (CPU intensive).</li>
<li><strong>Authentication:</strong> SCRAM or X.509 handshakes occur.</li>
<li><strong>Memory:</strong> The <code>mongod</code> process allocates stack memory (typically ~1MB) for the thread handling that connection.</li>
<li><strong>Context:</strong> The internal <code>serviceExecutor</code> must schedule this thread.</li>
</ol>
<p>If you set <code>maxPoolSize=100</code> on 50 Kubernetes pods, you are allowing 5,000 potential concurrent threads on the Primary. Before you hit hardware limits, you will likely hit <strong>WiredTiger ticket exhaustion</strong>.</p>
<h2 id="the-math-sizing-for-wiredtiger">The Math: Sizing for WiredTiger</h2>
<p>The naive approach is to increase <code>maxPoolSize</code> when you see <code>ConnectionPoolCheckoutFailed</code> errors. This is usually wrong.</p>
<p>WiredTiger (the storage engine) uses &ldquo;tickets&rdquo; to control concurrency. In MongoDB versions prior to 7.0, there are 128 read tickets and 128 write tickets by default. MongoDB 7.0+ uses dynamic ticketing that adjusts based on workload, but still caps at 128 per type. <strong>Note on MongoDB 7.0+:</strong> While the hard cap remains 128 tickets, the dynamic algorithm often lowers this limit (e.g., to 60 or 80) in real-time to preserve latency. This means you might see queuing before you hit 128 concurrent active queries. If 1,000 connections try to read simultaneously, 872 of them are just queuing inside the kernel, consuming RAM but doing zero work.</p>
<p>The optimal pool size formula remains tied to hardware capability, not traffic volume:</p>
$$
PoolSize \approx \frac{\text{CoreCount} \times 2}{\text{AppInstances}}
$$<p>If your database has 16 cores and you have 20 application instances, a <code>maxPoolSize</code> of 100 is overkill. You might actually achieve <em>higher</em> throughput with a pool size of 2 or 3 per instance, preventing the database from spending all its cycles on context switching.</p>
<p><strong>Note:</strong> The <code>(Cores * 2)</code> rule applies strictly to the <em>active</em> executing threads. You can have a larger pool to handle network jitter, but <code>waitQueueTimeoutMS</code> is your safety valve. If you need 100 connections to saturate the CPU, your queries are likely too slow (unindexed). Fix the queries, don&rsquo;t inflate the pool.</p>
<p><strong>Workload Considerations:</strong></p>
<ul>
<li><strong>CPU-Bound Workloads:</strong> The formula above applies directly. Each active connection should map to an executing thread.</li>
<li><strong>IO-Bound Workloads:</strong> If the working set doesn&rsquo;t fit in RAM and disk I/O is the bottleneck, a slightly larger pool <em>might</em> help hide I/O latency, though typically <code>minPoolSize</code> is more relevant here to avoid establishing connections during I/O spikes.</li>
<li><strong>Atlas Proxy / Serverless:</strong> If using <strong>MongoDB Atlas Serverless</strong> or the <strong>Atlas Proxy</strong>, the connection limits are handled differently (via a proxy layer), and the &ldquo;100 limit&rdquo; on the client side is less about server stress and more about client-side throttling.</li>
</ul>
<h2 id="the-specific-pathology-pinning">The Specific Pathology: &ldquo;Pinning&rdquo;</h2>
<p>Since you are versed in Mongo, you know the drivers implement the <strong>SDAM</strong> (Server Discovery and Monitoring) specification. But the most dangerous feature regarding pooling is <strong>Client-Side Operation Pinning</strong>, specifically with Transactions.</p>
<p>In a standard read/write, the driver grabs a connection, executes, and returns it.
In a Transaction (Multi-Document ACID):</p>
<ol>
<li><code>session.startTransaction()</code> is called.</li>
<li>The driver <strong>pins</strong> a connection to that session.</li>
<li><strong>That connection is removed from the pool</strong> and reserved exclusively for this session until <code>commitTransaction</code> or <code>abortTransaction</code> is called.</li>
</ol>
<h3 id="the-exhaustion-scenario">The Exhaustion Scenario</h3>
<p>If you have <code>maxPoolSize=50</code> and an API endpoint that takes 2 seconds to execute some logic <em>inside</em> a transaction, you can only serve 25 requests per second. The 26th request will wait. If the logic hangs (e.g., waiting for a third-party HTTP call), you will drain the pool instantly, causing a cascading failure across the app.</p>
<blockquote>
<p><strong>ʕ•ᴥ•ʔ Critical Rule:</strong> Never perform network I/O or long computations inside a MongoDB transaction. Transactions should be atomic, fast, and only contain database operations.</p>
</blockquote>
<h2 id="practical-examples-the-right-way-vs-the-wrong-way">Practical Examples: The Right Way vs. The Wrong Way</h2>
<h3 id="proper-client-initialization">Proper Client Initialization</h3>
<p><strong>Node.js - Express (Correct):</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">// app.js - Initialize once at startup
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">MongoClient</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;mongodb&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="kd">let</span> <span class="nx">client</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="kd">let</span> <span class="nx">db</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="kr">async</span> <span class="kd">function</span> <span class="nx">initDatabase</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MongoClient</span><span class="p">(</span><span class="nx">uri</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="nx">maxPoolSize</span><span class="o">:</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="nx">waitQueueTimeoutMS</span><span class="o">:</span> <span class="mi">5000</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="kr">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">connect</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">  <span class="nx">db</span> <span class="o">=</span> <span class="nx">client</span><span class="p">.</span><span class="nx">db</span><span class="p">(</span><span class="s1">&#39;myapp&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Database connected&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c1">// Initialize before starting server
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="nx">initDatabase</span><span class="p">().</span><span class="nx">then</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">3000</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1">// Use the same client instance everywhere
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;/users&#39;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">  <span class="kr">const</span> <span class="nx">users</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">db</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="s1">&#39;users&#39;</span><span class="p">).</span><span class="nx">find</span><span class="p">({}).</span><span class="nx">toArray</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">  <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">users</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p><strong>Node.js - Express (Wrong - Creates New Pool Per Request):</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// ʕ•̀ω•́ʔ DON&#39;T DO THIS
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;/users&#39;</span><span class="p">,</span> <span class="kr">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">  <span class="kr">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MongoClient</span><span class="p">(</span><span class="nx">uri</span><span class="p">);</span>  <span class="c1">// New pool every request!
</span></span></span><span class="line"><span class="ln">4</span><span class="cl">  <span class="kr">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">connect</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="kr">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="nx">client</span><span class="p">.</span><span class="nx">db</span><span class="p">(</span><span class="s1">&#39;myapp&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">  <span class="kr">const</span> <span class="nx">users</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">db</span><span class="p">.</span><span class="nx">collection</span><span class="p">(</span><span class="s1">&#39;users&#39;</span><span class="p">).</span><span class="nx">find</span><span class="p">({}).</span><span class="nx">toArray</span><span class="p">();</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">  <span class="kr">await</span> <span class="nx">client</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>  <span class="c1">// Closes pool, but too late
</span></span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">users</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p><strong>Python - Gunicorn (Correct):</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># app.py - Create client after fork</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pymongo</span> <span class="kn">import</span> <span class="n">MongoClient</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">db</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">def</span> <span class="nf">on_starting</span><span class="p">(</span><span class="n">server</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="c1"># This runs in the master process</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">def</span> <span class="nf">when_ready</span><span class="p">(</span><span class="n">server</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># This runs in each worker after fork</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="k">global</span> <span class="n">client</span><span class="p">,</span> <span class="n">db</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">client</span> <span class="o">=</span> <span class="n">MongoClient</span><span class="p">(</span><span class="n">uri</span><span class="p">,</span> <span class="n">maxPoolSize</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="n">db</span> <span class="o">=</span> <span class="n">client</span><span class="p">[</span><span class="s1">&#39;myapp&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/users&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">def</span> <span class="nf">get_users</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="n">users</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="n">find</span><span class="p">({}))</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="k">return</span> <span class="n">jsonify</span><span class="p">(</span><span class="n">users</span><span class="p">)</span></span></span></code></pre></div><p><strong>Python - Gunicorn (Wrong - Fork-Unsafe):</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># ʕ•̀ω•́ʔ DON&#39;T DO THIS</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="kn">from</span> <span class="nn">pymongo</span> <span class="kn">import</span> <span class="n">MongoClient</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># Created in master process - NOT fork-safe!</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="n">MongoClient</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">db</span> <span class="o">=</span> <span class="n">client</span><span class="p">[</span><span class="s1">&#39;myapp&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/users&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">get_users</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># Child processes share parent&#39;s file descriptors = deadlocks</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># This often results in AutoReconnect errors or </span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="c1"># &#39;file descriptor bad&#39; errors because the socket </span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="c1"># is shared across memory boundaries</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">users</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">users</span><span class="o">.</span><span class="n">find</span><span class="p">({}))</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">return</span> <span class="n">jsonify</span><span class="p">(</span><span class="n">users</span><span class="p">)</span></span></span></code></pre></div><p><strong>Java - Spring Boot:</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nl">spring</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="n">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="n">mongodb</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">      </span><span class="n">uri</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;mongodb://user:pass@host1:27017,host2:27017/?replicaSet=myRS&amp;maxPoolSize=10&amp;minPoolSize=2&amp;maxIdleTimeMS=30000&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="c1">// Or if using a Configurer Bean (safe way to customize without breaking auto-config)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nd">@Configuration</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="kd">class</span> <span class="nc">MongoConfig</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="nd">@Bean</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"> </span><span class="n">MongoClientSettingsBuilderCustomizer</span><span class="w"> </span><span class="nf">customizer</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">builder</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">builder</span><span class="p">.</span><span class="na">applyToConnectionPoolSettings</span><span class="p">(</span><span class="n">pool</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">            </span><span class="n">pool</span><span class="p">.</span><span class="na">maxSize</span><span class="p">(</span><span class="n">10</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">            </span><span class="n">pool</span><span class="p">.</span><span class="na">maxWaitTime</span><span class="p">(</span><span class="n">5</span><span class="p">,</span><span class="w"> </span><span class="n">TimeUnit</span><span class="p">.</span><span class="na">SECONDS</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong>The Health Check Trap:</strong></p>
<blockquote>
<p><strong>ʕ•ᴥ•ʔ Warning:</strong> Do not create a new <code>MongoClient</code> inside your Kubernetes readiness/liveness probe. This is a classic DOS attack on your own database. The probe runs every 10s; if it creates a new connection each time without proper closure, you will exhaust the server limits in minutes. Use the singleton client to run a lightweight command like <code>db.command({ ping: 1 })</code>.</p>
</blockquote>
<h3 id="monitoring-pool-metrics">Monitoring Pool Metrics</h3>
<p><strong>Node.js - Command Monitoring:</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">MongoClient</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;mongodb&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="kr">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MongoClient</span><span class="p">(</span><span class="nx">uri</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">  <span class="nx">monitorCommands</span><span class="o">:</span> <span class="kc">true</span>  <span class="c1">// Enable command monitoring
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nx">client</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;commandStarted&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Command:&#39;</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">commandName</span><span class="p">);</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="nx">client</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;commandSucceeded&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Duration:&#39;</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">duration</span><span class="p">,</span> <span class="s1">&#39;ms&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="c1">// For connection pool metrics, use pool monitoring events
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="nx">client</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;connectionCheckedOut&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">duration</span> <span class="o">&gt;</span> <span class="mi">100</span><span class="p">)</span> <span class="p">{</span>  <span class="c1">// Checkout took &gt; 100ms
</span></span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="s1">&#39;Slow connection checkout:&#39;</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">duration</span><span class="p">,</span> <span class="s1">&#39;ms&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="nx">client</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;commandFailed&#39;</span><span class="p">,</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">code</span> <span class="o">===</span> <span class="s1">&#39;ConnectionPoolCheckoutFailed&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Pool exhausted!&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="p">});</span></span></span></code></pre></div><p><strong>Go - Pool Monitoring:</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">    </span><span class="s">&#34;go.mongodb.org/mongo-driver/event&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="s">&#34;go.mongodb.org/mongo-driver/mongo&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">poolMonitor</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">event</span><span class="p">.</span><span class="nx">PoolMonitor</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nx">Event</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">evt</span><span class="w"> </span><span class="o">*</span><span class="nx">event</span><span class="p">.</span><span class="nx">PoolEvent</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">        </span><span class="k">switch</span><span class="w"> </span><span class="nx">evt</span><span class="p">.</span><span class="nx">Type</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="nx">event</span><span class="p">.</span><span class="nx">ConnectionCreated</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Connection created&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="nx">event</span><span class="p">.</span><span class="nx">ConnectionCheckedOut</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Connection checked out&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="nx">event</span><span class="p">.</span><span class="nx">ConnectionCheckoutFailed</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Checkout failed:&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">evt</span><span class="p">.</span><span class="nx">Reason</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">        </span><span class="k">case</span><span class="w"> </span><span class="nx">event</span><span class="p">.</span><span class="nx">ConnectionReturned</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">            </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Connection returned&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="nx">clientOptions</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">options</span><span class="p">.</span><span class="nf">Client</span><span class="p">().</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">    </span><span class="nf">ApplyURI</span><span class="p">(</span><span class="nx">uri</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">    </span><span class="nf">SetPoolMonitor</span><span class="p">(</span><span class="nx">poolMonitor</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">    </span><span class="nf">SetMaxPoolSize</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="nx">client</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">mongo</span><span class="p">.</span><span class="nf">Connect</span><span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nf">TODO</span><span class="p">(),</span><span class="w"> </span><span class="nx">clientOptions</span><span class="p">)</span></span></span></code></pre></div><h2 id="driver-nuances--pitfalls">Driver Nuances &amp; Pitfalls</h2>
<h3 id="nodejs-mongoose--native-driver">Node.js (Mongoose / Native Driver)</h3>
<p>The Node driver is asynchronous, but the pool logic is strict.</p>
<ul>
<li><strong>The Singleton Mistake:</strong> A common pattern in serverless or improper Express setup is calling <code>MongoClient.connect()</code> inside the route handler. This creates a new pool for every request, rapidly hitting the Atlas connection limit (e.g., 1,500 connections on M10). Always initialize the client <em>once</em> at startup.</li>
<li><strong>Unified Topology:</strong> The Node.js driver (v4.0+) uses Unified Topology by default (it&rsquo;s the only option). This keeps a background monitoring thread that can detect step-downs. If your pool churns during a primary election, check if your <code>heartbeatFrequencyMS</code> is tuned correctly.</li>
</ul>
<h3 id="python-pymongo">Python (PyMongo)</h3>
<ul>
<li><strong>Fork Safety:</strong> <code>MongoClient</code> is not fork-safe. If you use <code>gunicorn</code> or <code>uwsgi</code> with forking, you <strong>must</strong> create the client <em>after</em> the fork (e.g., inside the worker process initialization), or you will end up with child processes trying to use the parent&rsquo;s file descriptors, leading to deadlocks and obscure socket errors. This often manifests as <code>AutoReconnect</code> errors or &ldquo;file descriptor bad&rdquo; errors.</li>
<li><strong>Wait Queue:</strong> In PyMongo 4.0+, <code>waitQueueMultiple</code> was removed. Use <code>waitQueueTimeoutMS</code> to control how long threads wait for connections. Limit your application&rsquo;s thread pool size to control concurrency.</li>
</ul>
<h3 id="java-spring-boot--mongodb-java-driver">Java (Spring Boot / MongoDB Java Driver)</h3>
<ul>
<li><strong>Automatic Pooling:</strong> The <code>MongoTemplate</code> handles pooling automatically. Ensure <code>spring.data.mongodb.database</code> settings do not override defaults with aggressive timeouts. The Java driver&rsquo;s default <code>maxPoolSize</code> is 100, which is often too high for production.</li>
</ul>
<h3 id="go-mongo-go-driver">Go (mongo-go-driver)</h3>
<ul>
<li>The Go driver respects <code>context</code>. If your <code>Connect</code> or <code>Find</code> context times out, the connection might be closed or returned to the pool depending on the stage.</li>
<li>Monitor <code>PoolEvent</code> via the <code>SetPoolMonitor</code> option to visualize exactly when connections are created vs. checked out.</li>
</ul>
<h2 id="serverless--atlas-implications">Serverless &amp; Atlas Implications</h2>
<p>In MongoDB Atlas, connection limits are hard tiers.</p>
<ul>
<li><strong>M0 (Free Tier):</strong> 500 connections per node</li>
<li><strong>M10:</strong> 1,500 connections per node</li>
<li><strong>M20:</strong> 3,000 connections per node</li>
<li><strong>Serverless Instances:</strong> These bill by &ldquo;Read Processing Units&rdquo; but still require connection management.</li>
</ul>
<p>If you are running on AWS Lambda or Vercel, you cannot maintain a persistent pool. Every &ldquo;warm&rdquo; container might keep a pool of 10 open. 100 concurrent Lambdas = 1,000 connections.
<strong>Solution:</strong> You typically cannot use standard pooling here. You must aggressively close connections (set <code>maxIdleTimeMS</code> very low) or use the <strong>Atlas Data API</strong> (HTTP based) instead of the TCP driver to avoid socket exhaustion.</p>
<h2 id="monitoring--diagnosis">Monitoring &amp; Diagnosis</h2>
<p>How do you know it&rsquo;s a pool issue and not a slow query?</p>
<ol>
<li>
<p><strong>Check the Server:</strong></p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">db</span><span class="p">.</span><span class="nx">serverStatus</span><span class="p">().</span><span class="nx">connections</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1">// look for { &#34;current&#34;: 1200, &#34;available&#34;: 50 }
</span></span></span></code></pre></div><p>If <code>current</code> is high, your app is leaking connections or scaling too wide.</p>
</li>
<li>
<p><strong>Check the Queue:</strong>
Monitor WiredTiger ticket usage via <code>db.serverStatus().wiredTiger.concurrentTransactions</code> or connection counts. If active connections significantly exceed your pool size or WiredTiger tickets are exhausted, your latency is due to queuing, not network speed.</p>
</li>
<li>
<p><strong>Zombie Connections:</strong>
When a client crashes hard (OOM Kill) without sending a TCP FIN, the server keeps the connection open until the server-side <code>tcp_keepalive</code> times out. In high-churn Kubernetes environments, this can exhaust the server&rsquo;s file descriptors even if the <em>active</em> pool count seems low. This reinforces the need for aggressive <code>maxIdleTimeMS</code> and proper TCP keepalive configuration.</p>
</li>
<li>
<p><strong>Driver Metrics:</strong>
Enable command monitoring in your driver. If <code>connectionCheckoutTime</code> spikes while <code>commandDuration</code> (server execution time) stays low, the problem is client-side exhaustion. The database is fast; the line to get to it is slow.</p>
</li>
<li>
<p><strong>Connection Churn Rate:</strong>
Monitor the rate of new connections (<code>connectionsCreated</code> per second). If this is high (&gt;10/sec per node) while your total pool count is stable, your <code>maxIdleTimeMS</code> is likely too low. You are burning server CPU on SSL handshakes rather than reusing connections. Check <code>db.serverStatus().connections.created</code> and compare it to your connection pool turnover rate.</p>
</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>In MongoDB, &ldquo;Connection Pool Exhaustion&rdquo; is often a misnomer for &ldquo;Transaction Pinning&rdquo; or &ldquo;Overscaling.&rdquo; Because the drivers are complex state machines interacting with Replica Sets, simply bumping <code>maxPoolSize</code> masks the problem until it crashes <code>mongod</code> via memory pressure or ticket exhaustion.</p>
<p>The fix is almost always:</p>
<ol>
<li>Lower the pool size to match hardware (<code>threads &lt;= cores</code>).</li>
<li>Shorten transaction scopes to microseconds.</li>
<li>Treat the <code>MongoClient</code> as a sacred singleton.</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Why MySQL is not CA</title>
      <link>https://KTS-o7.github.io/bear/systems/why_mysql_is_not_ca/</link>
      <pubDate>Fri, 05 Sep 2025 07:07:07 +0100</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/systems/why_mysql_is_not_ca/</guid>
      <description>&lt;hr&gt;&#xA;&lt;h2 id=&#34;motivation&#34;&gt;motivation&lt;/h2&gt;&#xA;&lt;p&gt;I was recently discussing with my CTO about databases. There was one point where we were thinking about the &lt;code&gt;CAP&lt;/code&gt; theorem.&#xA;Then he randomly mentioned that MySQL is not CA. I was surprised because I always thought that MySQL is a CA database at its core.&lt;/p&gt;&#xA;&lt;p&gt;Now I had to search and understand why MySQL is not CA.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;cap-theorem&#34;&gt;CAP theorem&lt;/h2&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://substackcdn.com/image/fetch/$s_!cOsx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F869db2d6-6133-411e-96ad-cc1c52611778_800x472.png&#34; alt=&#34;CAP theorem&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;We have always been taught in Engineering that CAP is one of the most important theorems in distributed systems and databases. We just accepted it. Time to question and learn ┏ʕ •ᴥ•ʔ┛&lt;/p&gt;</description>
      <content:encoded><![CDATA[<hr>
<h2 id="motivation">motivation</h2>
<p>I was recently discussing with my CTO about databases. There was one point where we were thinking about the <code>CAP</code> theorem.
Then he randomly mentioned that MySQL is not CA. I was surprised because I always thought that MySQL is a CA database at its core.</p>
<p>Now I had to search and understand why MySQL is not CA.</p>
<hr>
<h2 id="cap-theorem">CAP theorem</h2>
<p><img src="https://substackcdn.com/image/fetch/$s_!cOsx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F869db2d6-6133-411e-96ad-cc1c52611778_800x472.png" alt="CAP theorem"></p>
<p>We have always been taught in Engineering that CAP is one of the most important theorems in distributed systems and databases. We just accepted it. Time to question and learn ┏ʕ •ᴥ•ʔ┛</p>
<p>Here is what it actually means.</p>
<p>Imagine a bank server, when you check your account balance it should not randomly change it based on the server you are hitting and it should be consistent across all nodes. But in a distributed system, network failures (partitions) can occur. The CAP theorem states that you can only guarantee 2 out of 3 properties:</p>
<ul>
<li><strong>Consistency (C)</strong>: All nodes see the same data at the same time</li>
<li><strong>Availability (A)</strong>: Every request receives a response, even if it&rsquo;s not the most recent data</li>
<li><strong>Partition Tolerance (P)</strong>: The system continues to operate despite network partitions</li>
</ul>
<p>Here are practical examples of each combination:</p>
<h3 id="ca-systems-consistent--available-but-not-partition-tolerant">CA Systems (Consistent + Available, but not Partition Tolerant)</h3>
<p><code>Example: Traditional Relational Databases (like PostgresSQL in single-node setups)</code></p>
<p>Imagine a single PostgresSQL database server in a bank. When you check your balance:</p>
<ul>
<li><strong>Consistency</strong>: Every time you query your balance, you get the exact same amount ($100.00)</li>
<li><strong>Availability</strong>: The system always responds to your requests</li>
<li><strong>Partition Tolerance</strong>: If the network connection to the database fails, you can&rsquo;t access your account</li>
</ul>
<p>This works perfectly in non-distributed environments, but fails when you need to scale across multiple data centers.</p>
<h3 id="cp-systems-consistent--partition-tolerant-but-sacrifices-availability">CP Systems (Consistent + Partition Tolerant, but sacrifices Availability)</h3>
<p><code>Example: Apache ZooKeeper or etcd</code></p>
<p>Imagine a distributed configuration management system like ZooKeeper:</p>
<ul>
<li><strong>Consistency</strong>: All nodes agree on configuration values (like which server is the leader)</li>
<li><strong>Partition Tolerance</strong>: If some nodes lose network connectivity, the remaining nodes continue operating</li>
<li><strong>Availability</strong>: During a network partition, some requests might be blocked to maintain consistency</li>
</ul>
<p>In a ZooKeeper cluster, if a majority of nodes become unreachable, the system stops accepting writes to prevent inconsistent states.</p>
<h3 id="ap-systems-available--partition-tolerant-but-sacrifices-consistency">AP Systems (Available + Partition Tolerant, but sacrifices Consistency)</h3>
<p><code>Example: Amazon DynamoDB or Cassandra</code></p>
<p>Imagine a social media platform&rsquo;s like counter system:</p>
<ul>
<li><strong>Availability</strong>: The system always responds to requests, even during network failures</li>
<li><strong>Partition Tolerance</strong>: The system continues operating across multiple data centers despite network issues</li>
<li><strong>Consistency</strong>: During partitions, you might see different like counts on different servers</li>
</ul>
<p>You might see a post with 100 likes on one page refresh, then 102 likes on the next refresh, as the systems reconcile the differences asynchronously.</p>
<hr>
<h3 id="why-mysql-is-not-ca">Why MySQL is not CA</h3>
<p>While MySQL can achieve CA in single-node deployments, it cannot be truly CA in distributed scenarios. When you add MySQL replicas or clustering solutions like MySQL Group Replication:</p>
<ul>
<li>During network partitions, MySQL must choose between consistency (rejecting writes) or availability (allowing potentially conflicting writes)</li>
<li>MySQL&rsquo;s synchronous replication can become unavailable during partitions if strict consistency is enforced</li>
<li>Traditional asynchronous replication trades consistency for availability (so it becomes AP, not CA).
Even with semi-sync replication, MySQL typically prioritizes availability over strict consistency—clients may see stale reads.</li>
</ul>
<p>It doesn’t enforce strong ACID guarantees across replicas the way PostgreSQL doe</p>
<h3 id="why-postgresql-is-considered-ca">Why PostgreSQL is considered CA</h3>
<p>Postgres in standalone mode: Definitely CA (strong ACID, always responds if node is up).</p>
<p>In replication setups:</p>
<p>PostgreSQL replication (especially with synchronous replication) prioritizes consistency first.
A commit can be acknowledged only after replicas confirm the write (if you configure synchronous replication).</p>
<p>This means if replicas aren’t reachable, Postgres will prefer not to serve stale data (sacrificing availability to preserve consistency).</p>
<p>So Postgres is effectively CA (or CP in distributed setups).</p>
<p>This is why distributed databases like CockroachDB or TiDB are often preferred for truly distributed, partition-tolerant systems.</p>
<hr>
<p>ʕ ● ᴥ ●ʔ</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
