PostgreSQL connection pooling: why your app runs out of connections
Every PostgreSQL connection is a process. Ten containers with a pool of twenty is two hundred processes, and your database was configured for one hundred.
Serverless functions scale to zero and bill per invocation, which suits spiky, stateless, short-lived work. Always-on servers or containers are better for sustained traffic, long-running processes, WebSockets and workloads with heavy database connection needs. Most production systems end up using both.
| Workload | Better fit | Why |
|---|---|---|
| Spiky, unpredictable traffic | Serverless | Scales instantly, pays nothing at idle |
| Steady high traffic | Servers | Cheaper per request at sustained load |
| Scheduled jobs | Serverless | No idle cost between runs |
| WebSockets / long connections | Servers | Functions are request-scoped |
| Long processing (> 15 min) | Servers / batch | Execution time limits |
| Latency-critical endpoints | Servers | No cold start variance |
| Event processing | Serverless | Native integration with queues and events |
Tens to hundreds of milliseconds for a small runtime, and worse for large bundles or VPC-attached functions — but only on the first request to a new instance.
This is the constraint that surprises teams in production. Each concurrent function invocation opens its own database connection, and a traffic spike that scales to 500 concurrent functions tries to open 500 connections to a database configured for 100.
Serverless pricing is roughly linear with usage; server pricing is a step function you pay whether or not it is busy. The crossover is where sustained utilisation makes a reserved instance cheaper than paying per request.
A typical production architecture we build: the main API on always-on containers behind a load balancer, image processing and report generation on serverless functions triggered by a queue, scheduled jobs on serverless cron, and a WebSocket service on containers. Each workload runs where its shape fits.
The unifying discipline is that everything is stateless, configured by environment variables and observable through the same pipeline — which is what makes moving a workload between the two a deployment change rather than a rewrite.
Warm invocations perform comparably to a container. Cold starts add latency on the first request to a new instance, which matters for infrequently-called user-facing endpoints and rarely for high-traffic ones.
Yes — many Next.js deployments do exactly that. Watch execution limits, connection handling and response streaming, and confirm your framework features work in the target runtime.
They run closer to users with very fast cold starts, but in a restricted runtime without full Node APIs and usually with tighter CPU limits. Excellent for routing, auth checks and personalisation; not for heavy work.
Somewhat — no persistent process to attach to and distributed execution by default. Good structured logging, tracing and local emulation close most of the gap.
Harshal Patel
Founder & Lead Engineer, ROVQIX
Harshal leads engineering at ROVQIX, where he has shipped production Next.js, Node.js and PostgreSQL systems for startups, SaaS teams and ecommerce brands. He writes about the trade-offs behind architecture decisions rather than the framework of the week.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Every PostgreSQL connection is a process. Ten containers with a pool of twenty is two hundred processes, and your database was configured for one hundred.
Most cloud bills have 30% of obvious waste in them. Finding it takes an afternoon; the hard part is having the conversation about what to turn off.
You cannot debug what you cannot picture. This is the map of a production request, layer by layer.
No spam. Just the occasional case study and craft breakdown.