Before you start
Two different limits produce one symptom, a request that waits or fails for a connection. One is the app's own pool being full. The other is the database refusing a new connection because it has reached max_connections. The fix differs, so tell them apart first. Examples below are made up.
Which limit did you hit?
| You see | Limit |
|---|---|
| A timeout waiting for a connection from the pool, with the database healthy | The app's pool is full: every connection is checked out, often held by a slow query or an open transaction. |
PostgreSQL too many clients already (SQLSTATE 53300) | The database is full. All app instances together opened more connections than max_connections allows. |
Check the arithmetic
Each running instance of the app has its own pool. The database sees the sum. Example: 20 serverless instances with a pool of 5 each can open 100 connections. If max_connections is 100, there is nothing left for migrations, admin tools or monitoring.
The default pool size depends on the Prisma version. In version 6 it was derived from the CPU count (physical CPUs times 2, plus 1), set by the connection_limit URL parameter. From version 7, the pool belongs to the database driver, and for the PostgreSQL driver the setting is max. Read your version's page before you copy a number.
Find what holds connections
An interactive $transaction keeps one connection until its callback ends. A slow call to another service inside it holds that connection the whole time. Count sessions by state to see it:
SELECT application_name, state, count(*)
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY application_name, state
ORDER BY count(*) DESC;Many idle in transaction sessions mean transactions are open longer than they need to be. See Find idle-in-transaction sessions in PostgreSQL.
The write loop inside a transaction
await prisma.$transaction(async (tx) => {
for (const item of items) {
await tx.stock.update({ where: { id: item.id }, data: { qty: { decrement: item.qty } } });
}
});One write per item, one after the other, on one held connection. With 500 items the connection and every row lock it took stay held for 500 round trips. Other requests wait for the pool, or for those row locks. Batching the writes into one statement (updateMany, or a single raw statement) shortens the hold. Whether it is safe depends on your logic, so test it.
Next step
Connect your database and repository, and dbtempo flags pool settings that look risky against the server's limit. See Connect your source code.