Serverless platforms are very good at creating concurrency quickly. PostgreSQL is not designed to enjoy thousands of short-lived clients opening direct connections at the same time. That mismatch is one of the most common failure modes in modern app stacks: the application tier scales, and the database spends its time managing connection churn.
That is why PgBouncer matters.
Why PgBouncer Helps
PgBouncer sits between your application and Postgres and reuses a smaller number of real database connections across a larger number of application clients. In practice, that usually improves:
- connection stability under bursts
- memory pressure on the database
- survivability during deploys and traffic spikes
A common setup looks like this:
[databases]
app = host=postgres.internal port=5432 dbname=appdb
[pgbouncer]
pool_mode = transaction
default_pool_size = 50
max_client_conn = 1000
The point is not to let 1,000 requests run 1,000 real Postgres sessions. The point is to smooth the compute layer into something the database can tolerate.
The Important Trade-Off
PgBouncer changes connection semantics, especially in transaction pooling mode. Features that assume session state can break or behave differently:
- session-level prepared statements
- temp tables across transactions
SET state that assumes connection stickiness
That means you need to choose the pooling mode deliberately and verify your ORM or query layer assumptions.
Better Operational Rule
If the compute layer is bursty, treat connection management as architecture, not as a low-level tuning detail. Serverless plus direct Postgres connections is often fine in development and painful in production. PgBouncer exists to absorb that mismatch before the database becomes the bottleneck.
Further Reading