Postgres 'Too many connections' at billing peak — practical diagnosis and remediation guide
By Kevin Marcondes · Published on · Updated on · 7 min read
‘Too many connections’ in PostgreSQL during a billing peak usually comes from a mix of connection storms, long transactions and missing pooling. In minutes you can measure the problem, apply safe containment, fix root causes (pooling, timeouts, SQL), and prepare both the database and application for the next closing window without surprises.
What the error means and why it bursts at peak
PostgreSQL caps concurrent sessions with max_connections. When that limit is reached, new connections fail and your application errors. During billing peaks, traffic, background jobs and reports coincide and multiply sessions. Without pooling, every request opens a session. Open transactions hold slots and cause queues.
If you suspect concurrent jobs are competing with checkout, review their execution windows. This guide on locks and cron jobs details impacts and fixes: auditing and optimizing cron jobs.
- Connection storm: multiple instances/threads opening sessions simultaneously.
- Long transactions and locks: queues grow, connections remain active or stuck.
- Jobs and reports running concurrently with checkout traffic.
- Misconfigured health checks saturating the database.
Step 1 — Measure right now: how many connections and where they come from
Map session states and origins. Find who’s consuming slots: application, IP address, or database user. That separates cause from symptom and guides next actions. Run the queries below on the affected database.
-- Sessões por estado
SELECT state, count(*)
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY 1 ORDER BY 2 DESC;
-- Origem por application_name
SELECT coalesce(application_name, 'unknown') AS app, count(*)
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY 1 ORDER BY 2 DESC;
-- Clientes que mais abrem conexões
SELECT client_addr, count(*)
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY 1 ORDER BY 2 DESC;
-- 'idle in transaction' retém slots
SELECT count(*) AS idle_in_tx
FROM pg_stat_activity
WHERE state = 'idle in transaction';
Step 2 — Connection pooling: the missing buffer
Without pooling, every request becomes a PostgreSQL session. A pool limits and reuses connections, protecting the database from storms. Check the app pool and, if you use PgBouncer, review its limits and pooling mode. In peaks, transaction pooling typically reduces pressure.
- Prefer transaction pooling for high-concurrency web apps.
- Size pools per service, avoiding summing theoretical maxima across all services.
- Apply timeouts for idle sessions and network drops.
; Exemplo de PgBouncer (ilustrativo)
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb
[pgbouncer]
max_client_conn = 2000
default_pool_size = 50
reserve_pool_size = 20
pool_mode = transaction
server_idle_timeout = 30
query_timeout = 30
idle_transaction_timeout = 30
Step 3 — Keep the app under control: avoid connection storms
Even with a pool, the application can exhaust sessions by starting many workers or using aggressive retries. Tune connection defaults and backoff. Reduce concurrency during the peak if needed. Health checks should be light and avoid opening a database connection on every probe.
- Initialize the pool once per process; don’t open/close per request.
- Implement retries with exponential backoff and jitter, with clear limits.
- Limit simultaneous workers during peak windows.
- Use connection lifetime recycling, but avoid synchronized recycling across processes.
Step 4 — Long transactions and heavy SQL drain slots
Open transactions tie up connections and locks. Reports without proper indexes and N+1 queries increase time spent holding the database. Monitor the oldest queries and focus on those touching hot tables. To speed billing paths, review SQL tuning and indexing.
- Terminate stuck sessions and refine indexes on billing paths.
- Avoid 'idle in transaction': always COMMIT or ROLLBACK.
- Review job schedules during business hours and reschedule heavy reports.
-- Top transações antigas e o que aguardam
SELECT pid,
now() - xact_start AS xact_age,
state,
wait_event_type,
wait_event,
left(query, 200) AS query
FROM pg_stat_activity
WHERE datname = current_database()
AND xact_start IS NOT NULL
ORDER BY xact_age DESC
LIMIT 15;
Step 5 — Adjust max_connections safely (no guessing)
Raising max_connections without planning can cause OOM and worse outages. Size memory first and prefer pooling. Keep the database at a stable session count and absorb client spikes in an external pool. Use timeouts to avoid forever-open connections.
| Parameter | Impact | Rule of thumb |
|---|---|---|
| max_connections | Limit on sessions in PostgreSQL | Keep moderate and stable; use a pool to serve thousands of clients |
| shared_buffers | Database page cache | Size according to available RAM, avoiding competition with the OS and filesystem cache |
| work_mem | Memory per operation (sort/hash) | Avoid globally high values; tune per session/query when needed |
| statement_timeout / idle_in_transaction_session_timeout | Prevents stuck sessions | Set realistic limits to avoid eternal connections and long queues |
| Pool (app/PgBouncer) | Amortizes peaks | Sum real pools and validate worst-case concurrency scenarios |
-- Checar parâmetros atuais
SHOW max_connections;
SHOW shared_buffers;
SHOW work_mem;
SHOW statement_timeout;
SHOW idle_in_transaction_session_timeout;
Step 6 — Immediate containment during the incident (carefully)
These actions relieve the collapse while you address the root cause. Applying them in production is risky: test first and limit scope to the affected database/role/application. Focus on non-essential traffic and sessions that only occupy slots.
- Reduce non-essential traffic (reports, less critical integrations).
- Cancel slow queries; terminate zombie sessions.
- Apply temporary timeouts to avoid new infinite queues.
-- Definir timeouts no DB afetado (temporário)
ALTER DATABASE mydb SET statement_timeout = '30s';
ALTER DATABASE mydb SET idle_in_transaction_session_timeout = '30s';
-- Encerrar sessões ociosas de uma aplicação específica
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'mydb'
AND application_name = 'checkout'
AND state IN ('idle','idle in transaction');
Step 7 — Prevention for the next peak
Consolidate lessons and build protections. Monitor connections, latency and locks. Run load tests on the billing path. Isolating heavy reports and adopting telemetry and structured logs will help anticipate degradation and correlate causes.
- Dashboards for active connections, queues and locks with alerts — see how to deploy a stack: Prometheus & Grafana with Docker Compose.
- Logs with correlation to trace routes and jobs that saturate connections: structured JSON logs and request correlation.
- Load tests focused on checkout and billing.
- Contingency plans for critical time windows.
Frequently asked questions
Will increasing max_connections permanently fix the error?
Not necessarily. It can worsen memory usage and latency. The sustainable approach is to keep a moderate number of sessions and use a connection pool to absorb spikes. Fix long transactions, slow queries and application storms.
PgBouncer: session or transaction mode for web apps?
For high-concurrency web apps, transaction pooling usually performs better because it releases server connections quickly. Session pooling holds a server connection for the client's entire session, increasing usage during peaks.
How do I identify which service is exhausting connections?
Use pg_stat_activity to group by application_name, user and client_addr. Enable logs that include application identification and request correlation. That maps routes and jobs consuming the most sessions during critical windows.
Do read replicas help reduce 'Too many connections'?
Replicas offload read queries, but each replica also has a connection limit. Without pooling and transaction optimization, the problem can simply move. Direct heavy reads to replicas with appropriate pooling.
Why does the error only show up at month-end closing?
At those times you get simultaneous peaks: billing jobs, reconciliations, reports, integrations and higher traffic. If they all compete and the database lacks pooling, indexes and timeouts, the connection limit is quickly reached.
Need to stabilize PostgreSQL before the next peak?
If you hit 'Too many connections' during billing, we can review connections, pooling, critical queries and timeouts to reduce the risk of another outage.
Discuss a diagnosisSee also
- How to recover corrupted statistics and indexes in PostgreSQL that caused slow queries
- REST API latency spikes from N+1 queries and PHP GC — how to diagnose and fix
- Reduce costs migrating Magento to OpenMage with a rollback plan and acceptance tests
- Practical guide: recover a dropped PostgreSQL table with point-in-time recovery without downtime