Connection Pooling

Updated June 3, 2026
M
Magic Magnets Team
6 min read

Imagine a busy restaurant. Every time a new customer walks in, the restaurant decides to hire a brand-new waiter off the street, train them, give them a uniform, and assign them to that customer. When the customer leaves, the waiter is immediately fired.

This sounds insane. The restaurant would spend all its time and money hiring and firing, and the customers would starve waiting.

Yet, this is exactly how naive backend applications interact with databases.

The Cost of a Connection

When your backend application (like a Node.js API) needs to talk to a PostgreSQL database, it has to open a connection.

Opening a database connection is incredibly expensive and slow. Under the hood, it involves:

  1. Network Handshake: The app establishes a TCP connection with the database server over the network.
  2. Security: TLS/SSL encryption is negotiated.
  3. Authentication: The database checks the username and password, looks up permissions, and allocates memory for this specific user session.

This entire setup process can take 50 to 100 milliseconds.

If your API gets a request, opens a connection (100ms), runs a simple query (5ms), and then closes the connection, the database spent 95% of its time just dealing with the paperwork! If 1,000 users hit your API at the same time, the database will completely crash under the weight of trying to open 1,000 concurrent network connections.

We need to stop hiring and firing waiters. We need a staff of permanent waiters. We need Connection Pooling.

Quiz Time

Why is opening a new database connection for every request a performance problem?

What is Connection Pooling?

A Connection Pool is exactly what it sounds like: a cache of database connections kept open and ready to be used.

When you use a connection pooler, the lifecycle of a request completely changes:

  1. Initialization: When your backend server starts up, it immediately opens a set number of database connections (say, 20). These connections are kept alive permanently.
  2. Borrowing: When a user request hits the API and needs data, the application doesn't talk to the database directly. It goes to the Pool and asks, "Can I borrow an open connection?"
  3. Executing: The app uses the borrowed connection to run the 5ms query. No setup time is required.
  4. Returning: Once the query is done, the app does not close the connection. It returns the connection back to the Pool, where it waits to be used by the next user request.

If 100 requests come in simultaneously, and the pool only has 20 connections, the other 80 requests simply wait in a queue for a few milliseconds until a connection is returned to the pool.

Quiz Time

When an application "borrows" a connection from a pool, what happens after the query finishes?

Where Does the Pool Live?

There are two common places to put a connection pool:

1. Application-Level Pooling

Most modern ORMs and database libraries (like Prisma, Hibernate, or SQLAlchemy) have connection pooling built-in. The pool lives in the memory of your API server.

This works great until you scale your API. If you deploy 50 instances of your API server, and each has a pool of 20 connections, your database is suddenly managing 1,000 open connections. Databases hate managing thousands of idle connections. It eats up all their RAM.

Quiz Time

Application-level pooling alone is sufficient for large-scale deployments with dozens of API server instances.

2. Middleware (Proxy) Pooling

To protect the database, massive architectures use a dedicated piece of software sitting between the API servers and the database. A famous example for PostgreSQL is PgBouncer.

All 50 of your API servers connect to PgBouncer. PgBouncer manages hundreds of thousands of lightweight incoming connections, but it only maintains a strict, small pool of heavy, actual connections to the real database. The database is shielded and only sees a calm, steady stream of 50 active connections.

Quiz Time

What is PgBouncer's primary role in a large PostgreSQL architecture?

Sizing the Pool

A common beginner mistake is making the connection pool too large. You might think, "I have high traffic, I should set my pool size to 500!"

Counter-intuitively, setting the pool size too high will destroy your performance. Your database server only has a few CPU cores. If 500 connections are actively trying to read from the disk at the exact same time, the CPU spends all its time context-switching between tasks, and the hard drive gets gridlocked.

A famous formula provided by the PostgreSQL community suggests the optimal pool size is roughly: (Number of CPU Cores * 2) + Effective Spindle Count (Hard Drives)

For a standard 8-core database server, the optimal connection pool size is often shockingly small: around 20 to 30 connections.

Quiz Time

Setting your connection pool size very large (e.g., 500) will always improve performance under high traffic.

Quiz Time

According to the PostgreSQL community formula, what is the primary factor that determines optimal pool size?

Summary

  • Opening a new database connection for every request is incredibly slow due to network handshakes, SSL, and authentication overhead.
  • A Connection Pool maintains a cache of permanently open database connections.
  • Applications "borrow" a connection from the pool, run their fast queries, and return it, bypassing the setup time.
  • Pooling can be handled by the application library (ORMs) or by dedicated proxy middleware like PgBouncer.
  • Keeping pool sizes relatively small (often 20-50 connections) is crucial to prevent database CPU and disk thrashing.

Sharding

How helpful was this content?

Comments

0/2000

Sign in to join the discussion

Saved on this device only

Sign in to sync progress across devices