Why Lambda Concurrency Quietly Exhausts Your Database Connections
Each concurrent Lambda opens its own database connection. Here's why that exhausts your pool, how cold starts make it worse, and how to fix it.

Every concurrent Lambda invocation opens its own database connection, and nothing in Lambda shares connections across those invocations. So when your function fans out to 200 concurrent executions, you can suddenly be asking your database for 200 connections at once — and a small Postgres instance will refuse most of them. This is the single most common way serverless apps hit remaining connection slots are reserved errors in production. The fix is not more Lambda; it’s a connection proxy or pooler in front of the database. This post explains the mechanics plainly, with real numbers, so you can size things before they page you.
The core mismatch: Lambda scales wide, databases scale narrow #
A traditional long-running server holds a connection pool — say 10 connections — and multiplexes thousands of requests through it. Lambda breaks that model. Each execution environment handles exactly one request at a time, so if AWS spins up 50 environments to absorb a traffic spike, you now have 50 processes each wanting at least one connection. There is no shared pool across environments because they’re isolated micro-VMs that don’t know about each other.
The database side is the hard limit. A db.t3.micro running Postgres defaults to roughly 85–112 max_connections depending on version and parameter group — the value is derived from instance memory (LEAST({DBInstanceClassMemory/9531392}, 5000)). AWS also reserves a few for superuser and replication, so in practice a micro instance gives you something like 80 usable connections. Line that up against Lambda’s default account concurrency of 1,000 and the problem is obvious: your compute can outscale your database’s connection ceiling by more than 10x without you writing a single extra line of code.
Cold starts make the connection storm worse #
Cold starts amplify the pain because a freshly initialized environment has no warm connection to reuse — it has to open one during init, which adds latency to exactly the requests that are already slow. If you naively open the connection inside the handler instead of outside it, you can even leak connections across invocations. We dug into the latency side of this in AWS Lambda cold starts in 2026; the connection cost rides on top of those numbers. A TLS handshake plus Postgres auth can add 20–80ms per fresh connection, and under a burst you pay that many times over while your free connection slots evaporate.
The usual first instinct — open the connection outside the handler so it survives warm reuse — helps throughput but does nothing for the breadth problem. Warm reuse means one environment keeps one connection alive. It does not mean 200 environments share one connection. You still get one connection per concurrent environment, and idle warm environments can hold connections open for minutes after the burst, starving the next wave.
The actual fix: put a pooler in front of the database #
The durable answer is a connection pooler that sits between your functions and your database and multiplexes many short-lived client connections onto a small set of persistent backend connections. On AWS the managed option is RDS Proxy, which maintains the warm backend pool and lets hundreds of Lambda clients share a few dozen database connections. We compared the trade-offs directly in RDS Proxy vs direct connections for a small app — the short version is that direct connections are fine until concurrency gets spiky, and a proxy earns its keep the moment you see connection-limit errors under load.
RDS Proxy isn’t free: it’s billed per vCPU-hour of the underlying instance, which lands around $11–$15/month for a small database and climbs with instance size. If that cost matters at your stage, capping Lambda reserved concurrency below your usable connection count is a legitimate interim move — set reserved concurrency to, say, 60 against an 80-connection database so Lambda throttles itself before the database does. Throttling is a cleaner failure than connection refusal, since throttled invocations retry rather than erroring mid-query. The cost mechanics of pooling under serverless are worth reading in RDS connection pooling and Lambda before you commit to either path.
How Vylara handles this when it provisions your database #
When you connect the Vylara GitHub App and the agent analyzes your repo, it detects your database driver — pg, prisma, psycopg2, and similar — from your dependencies and surfaces a plain "Your app needs a PostgreSQL database (~$15/mo)" card rather than asking you to reason about connection ceilings. The connection URL is persisted after the first deploy under the project’s service config, and the same values power the dashboard and the in-app infrastructure chat, so when you later ask "why am I seeing connection timeouts," the agent can read your actual service logs and metrics and point at the ceiling rather than guessing.
Be honest about the limit here: the agent’s initial sizing is a reasonable default, not a load test. A right-sized database for 10 concurrent users is undersized for 500, and no analysis of your code can predict your traffic. That’s why Vylara keeps a human in the loop — you review the generated configs in a diff before anything touches your repo, and you approve the deploy that creates the environment in your own AWS account. For the broader picture of how these pieces fit together, the AWS pillar guide walks through databases, caches, and the ownership model end to end.
The practical takeaway: treat your database’s max_connections as a budget and your Lambda concurrency as spending power, and make sure the spending power can’t exceed the budget. Either cap concurrency, add a pooler, or both. Discovering this relationship during a traffic spike at 2am is a worse way to learn it than reading one blog post — which is the whole point of this one.
Review your cloud plan in Vylara, merge delivery changes as Git PRs, and deploy into your own AWS or Azure account when you’re ready.
Start freeFrequently asked questions
- How many connections does a single Lambda function use?
- A single Lambda execution environment uses one database connection at a time, but AWS runs many environments in parallel under load — one per concurrent invocation. So 200 concurrent invocations can mean roughly 200 connections, which exceeds the ~80 usable connections on a db.t3.micro Postgres instance.
- Does RDS Proxy fix Lambda connection exhaustion?
- Yes. RDS Proxy maintains a warm pool of backend database connections and lets hundreds of Lambda clients share a few dozen of them, so bursts of concurrency no longer translate directly into database connections. It costs roughly $11–$15/month for a small instance and scales with instance size.
- Can I just limit Lambda concurrency instead of adding a proxy?
- Yes, as an interim fix. Set Lambda reserved concurrency below your database's usable connection count — for example 60 against an 80-connection database — so Lambda throttles and retries rather than the database refusing connections. It's cheaper than a proxy but caps your throughput at that ceiling.



