Connection pool
A small set of database connections kept open and shared across requests, so you stop paying to reconnect and stop hitting connection limits.
See it
What it is
Opening a database connection is slow (TCP handshake, auth, session setup) and Postgres spawns a whole process per connection, so a server that opens one per request wastes most of its time on setup and then falls over at 'sorry, too many clients already'. A connection pool keeps a small set of connections open and lends them out: your code borrows one, runs a query, and returns it. Requests beyond the pool size wait in line instead of crashing the database.
Sizing is counterintuitive. Bigger is not better. A pool of 10 to 20 per app instance usually beats 100, because Postgres throughput peaks well below its connection limit and past that everything just contends. But size it as a budget, not a magic number: multiply by the maximum instance count you can autoscale to, add the workers and cron boxes, and stay under the server's max_connections with headroom left for migrations and your own psql session. A per-instance 20 that was fine at three instances is a page at thirty, and on a tiny single-instance app it just throttles you.
Serverless is where this bites hardest: every cold Lambda or edge invocation wants its own pool, and 500 concurrent invocations mean 500 connections to a database that allows 100. The fix is an external pooler in front (PgBouncer in transaction mode, Supabase's pooler, Prisma Accelerate, Neon's pooled endpoint) or an HTTP-based driver. Transaction-mode pooling gives up session state, so LISTEN/NOTIFY, session-scoped SET, temp tables, and advisory locks held across statements all need session mode instead. Prepared statements are the nuanced one: recent PgBouncer can track protocol-level prepared statements when max_prepared_statements is set, while SQL-level PREPARE still breaks, and each driver handles this differently. Check the exact limits for your pooler version, driver, and config rather than trusting a blog post from 2019.
Ask AI for it
Configure connection pooling for this app's Postgres access. Create exactly one pool at module scope (never inside a request handler). Derive max from a budget rather than a fixed number: start from the server's max_connections, subtract connections reserved for superusers, migrations, and my own psql session, subtract the worker and cron pools, then divide the remainder by the maximum web instance count at full autoscale, and show me that arithmetic. Set idle and connection timeouts so stuck clients get recycled, and release every borrowed client in a finally block. If this deploys to serverless or edge, point the app at a transaction-mode pooler such as PgBouncer or the provider's pooled connection string, and list the session-level features that stop working for that specific pooler version, driver, and config (LISTEN/NOTIFY and session state certainly, prepared statements depending on settings). Log pool queue wait time and saturation so I can tune from measurements instead of guesses.