The database rejects connections. How should you size application pools?
Why adding backend processes can exhaust database connections. Pool budgets, queued requests and a careful approach to PostgreSQL.

Scaling a backend can introduce a problem that a CPU graph does not explain: the application waits for a database connection or PostgreSQL rejects new ones. Each process may maintain its own pool, a set of connections available for reuse. Assess its limit together with every other database client. The following approach uses PostgreSQL 18; examples of client metrics use node-postgres.
Find out where the request is waiting
Distinguish waiting in the application pool from a failure to establish a new connection to the server. In node-postgres, track waitingCount alongside totalCount and idleCount. A growing queue is a reason to investigate who holds connections and for how long. Measure query duration separately from the time needed to acquire a connection.
In PostgreSQL, check the setting with SHOW max_connections; and inspect reserved connection slots. Raising this limit requires a server restart and increases some resource allocations, including shared memory. During an incident, first identify the actual source of pressure and the options for a controlled intervention.
Count the pools across every process
For direct database connections, add the maximum pool sizes of all processes. Include workers, scheduled jobs and other services. An illustrative example: four processes with a limit of 20 connections each can open up to 80 connections in total. This is capacity arithmetic, not a measurement of a particular system or a recommended pool size.
Account for old and new versions running together during a deployment, as well as the upper limit of automatic scaling. Leave room for administration and diagnostics. Choose each process limit from that budget and load measurements; a web service and a batch processor may need different settings.
Investigate connections held for a long time
In pg_stat_activity, compare application_name, state and transaction start time. Idle means the session is waiting for a new command. Idle in transaction means an open transaction with no query currently running and warrants investigation. Some diagnostic details about other sessions require appropriate privileges; a missing value does not prove that no problem exists.
When you acquire a client manually in node-postgres, ensure it returns to the pool after errors too, for example through finally. Also configure a finite wait for acquiring a connection. If you need to terminate a specific database session, identify its owner and impact first; pg_terminate_backend ends the entire session. Terminating connections indiscriminately is a poor first intervention.
Test the change with realistic concurrency
If you use PgBouncer, distinguish client connections from connections to PostgreSQL. The default_pool_size parameter sets the default limit for each user and database pair; with multiple pairs and instances, assess their combined limits. Transaction pooling returns the server connection after a transaction, but requires application compatibility checks, for example when using LISTEN or session-level locks.
In an isolated environment, test peak load and overlapping versions during a deployment. Compare connection wait time, error counts and the latency of the user operation. After a production change, monitor the same measures and prepare a configuration rollback. A lower connection count alone does not show whether clients receive results within an acceptable time.
- Distinguish pool waiting from database rejection.
- Include every process and deployment overlap.
- Check client release and transactions left open for a long time.
- Measure errors and latency after the configuration change.
What to take away
Size application pools as a shared database connection budget. When capacity runs out, identify the processes and operations holding connections, then test the change under the expected concurrency.


