Skip to main content

No Connection Pooling

Opening a connection per request pays repeated handshake cost and exhausts the database's connection limit under load, failing before the database is even busy. Use connection pooling, or an external pooler in serverless setups, and size the pool to the limit.

This anti-pattern opens a new database connection for each request, query, or job and closes it immediately afterward, rather than reusing connections from a pool. Establishing a connection is expensive — TCP handshake, TLS negotiation, authentication, and server-side session setup — and databases cap the number of concurrent connections. Doing it per request wastes that cost repeatedly and risks running into the cap.

Why It Happens

Connect-use-close is the simplest lifecycle and the one tutorials show. Serverless and short-lived function environments make persistent pools awkward, nudging developers toward per-invocation connections. Some frameworks default to creating connections on demand. The cost is invisible at low traffic and only bites under concurrency.

Why It Hurts

Every connection setup adds latency to the request, often more than the query itself, and that overhead is paid on every call. Worse, databases have a hard limit on concurrent connections; under load, opening one per in-flight request quickly exhausts it, producing "too many connections" errors that take down the application even though the database has spare CPU. Connection churn also stresses the database's connection handling and can cause latency spikes and cascading retries. The system becomes fragile precisely when traffic is highest.

Warning Signs

  • Code opens and closes a connection inside each request handler or query.
  • The database reports connection-limit or "too many connections" errors under load.
  • A large share of request latency is spent establishing connections.
  • Connection counts spike with traffic and the app fails before the database is CPU-bound.

Better Alternatives

Use connection pooling: maintain a bounded set of reusable connections that requests borrow and return, amortizing setup cost across many queries and capping concurrency at a safe level. In environments where the application cannot hold a pool well — such as serverless or many small instances — front the database with an external pooler or proxy (for example PgBouncer) that multiplexes many clients onto few database connections. Size the pool deliberately based on database limits and workload rather than leaving defaults.

How to Refactor Out of It

Introduce a pool at application startup and route all queries through it instead of opening connections ad hoc. Set a sensible maximum pool size that, multiplied across all instances, stays within the database's connection limit. For serverless or high-fan-out deployments, deploy an external connection pooler and point clients at it. Add monitoring on active connections, pool wait time, and connection errors. Tune pool size and timeouts against real load until latency stabilizes and connection-limit errors disappear.