Build1 distinct publisher3 min readUpdated
Postgres refusing connections is a counting problem. Install a pooler before you find the multiplier and you buy the same outage next month, plus one more hop in the path.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
`FATAL: sorry, too many clients already` almost never means the database ran out of capacity; it means something in the stack opened more connections than `max_connections` allows [1]. That distinction decides what you do next, because the reflex fix, installing PgBouncer, addresses the symptom and leaves the cause in place: put a pooler in without finding the multiplier first and, according to a dev.to write-up on diagnosing the error, you get the same failure a month later with an extra moving part in the path [3].
The mechanics are unglamorous. Postgres allocates one backend process per connection, `max_connections` is a hard ceiling fixed at server start, and attempts past it are rejected outright [5]. There is a second message worth recognising separately: `FATAL: remaining connection slots are reserved for non-replication superuser connections` means you have hit `max_connections` minus `superuser_reserved_connections`, so ordinary users are locked out while the reserved slots keep a superuser login available for exactly this moment [6]. Both are refusals at the door rather than evidence of load, and CPU and IO can sit near idle while it happens [7].
So count before you build. The author's first query groups `pg_stat_activity` by user, `application_name` and state, with a count and `max(now() - state_change)`, filtered to `backend_type = 'client backend'` [8]. Three shapes recur. A wall of `idle` connections under a single `application_name` is a pool sized correctly per process and then replicated across more processes than you remembered running [9]. `idle in transaction` with a longest-in-state measured in minutes is a code path that opened a transaction, did something slow or fallible outside the database, usually an HTTP call, and never committed; those connections hold locks and are useless to everyone else [10]. A scatter of `active` connections above your intended pool size means something bypasses the pool entirely: a migration runner, a cron job, an admin tool, a metrics exporter [11].
If `idle in transaction` appears at all, fix it before touching pool sizes, because a pooler will leak those just as happily [13]. Setting `idle_in_transaction_session_timeout` to 60 seconds and reloading the config kills sessions that hold a transaction open past a minute, converting a silent connection leak into a loud, attributable application error [12].
The number itself comes from multiplication: pool size times processes per instance times instances, doubled again if you run a separate replica or read pool [14]. Eight Gunicorn or Puma workers with a pool of 10 across three pods is 240 connections before anything goes wrong [15], which is 2.4 times a default `max_connections` of 100 [1] and, once you add a background worker deployment on the same defaults, several times over it [16]. Multiply the ceiling, not the steady state, because most application pools have a burst setting above nominal size, such as SQLAlchemy's `max_overflow` or HikariCP's `maximumPoolSize` against minimum idle [18]. The sizing rule the author trusts is HikariCP's: connections should be a small multiple of core count, not a function of request concurrency, since the extras just queue inside Postgres instead of inside your app [19].
Shrinking pools is sufficient when the set of long-lived processes is fixed; a pooler earns its place when the client count is genuinely unbounded, as with serverless or autoscaled workers [20]. Serverless is the extreme case, where every warm function instance holds its own connection and concurrency spikes create instances faster than any pool config can constrain [17].
Worth watching in your own estate: whether your pool ceiling times process count times replica count already exceeds `max_connections` on a normal day, and whether anything outside the pool is dialling the database directly [14][11]. Run the count first, then the arithmetic, then decide about the extra hop [4].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The Postgres error "FATAL: sorry, too many clients already" almost never means the database is out of capacity; it means something in the stack opened more connections than max_connections allows.
The author's working order is: count the connections and their states, find the multiplier, shrink the app-side pool, and only then decide whether a pooler is warranted.
Postgres allocates a backend process per connection, max_connections is a hard ceiling set at server start, and when clients exceed it the connection attempt is rejected outright.
The message "FATAL: remaining connection slots are reserved for non-replication superuser connections" means max_connections minus superuser_reserved_connections has been reached, so ordinary users are locked out while the reserved slots keep a superuser login available for exactly that situation.
Both errors are refusals at the door rather than signs of load; CPU and IO can be near idle while they occur.
The recommended diagnostic query selects usename, application_name, state, count(*) and max(now() - state_change) as longest_in_state from pg_stat_activity where backend_type = 'client backend', grouped and ordered by count.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Mechanism well described, outcomes unmeasured
The mechanical core is specific and checkable from the single source: per-connection backend processes, max_connections as a start-time hard ceiling, the superuser_reserved_connections message, an exact pg_stat_activity query, an ALTER SYSTEM timeout guard, and arithmetic that closes (8 x 10 x 3 = 240 = 2.4x a default 100). What is missing is any independent corroboration, primary documentation, or measured outcome: the prevalence claims, the HikariCP sizing attribution, and the prediction about pooler-first fixes are single-practitioner assertions with no data in the cluster.
No adoption signal in cluster
The cluster contains no release, deployment, benchmark, usage disclosure, pricing or licensing event. Tools named (PgBouncer, Supavisor, RDS Proxy, HikariCP, SQLAlchemy, Gunicorn, Puma) appear only as illustrative options in a how-to, with no dated evidence of who runs them or at what scale, so no adoption level can be measured without inventing facts.
Slightly understated
The framing runs against its own promotional grain: it reframes an outage-class error as arithmetic, tells readers to defer buying or deploying a pooler until the multiplier is found, and offers a free server-side timeout as the immediate guard. There is no claim of novelty, no product being sold, and no extrapolation beyond the described mechanism. The small negative reflects that the practically useful, low-drama core is packaged as routine debugging advice while a couple of the broader generalizations (prevalence, the month-later recurrence) are asserted more confidently than the supplied evidence warrants, which offsets part of the understatement.
Low commercial pressure, platform engagement pressure remains
Observable incentive pressure is low: the post is self-published on a developer community platform, its central recommendation is to spend nothing and shrink an existing pool before adopting any pooler, and the three commercial or hosted options named (PgBouncer, Supavisor, RDS Proxy) are presented as context-dependent alternatives rather than a single promoted product. No affiliation or sponsorship is disclosed in the supplied source, so a residual incentive is assigned for the audience-and-visibility motive typical of self-published platform tutorials.
Moderate — verifiable mechanics, single unreplicated source
Confidence is held mid-range by structure rather than content quality: the mechanics and arithmetic are specific, internally consistent, and independently checkable by any reader with a Postgres instance, which supports the operational core. But the cluster has exactly one publisher and one article, several load-bearing generalizations are uncorroborated, and there is no adoption or outcome data, so conclusions about how often this failure mode arises or how well the prescribed order works in practice cannot be held with high confidence.
build
Two mechanisms, one vCPU floor: why db.t3.micro cannot meet a 1-second RPO on RDS1 distinct publisher
build
Prisma v7 stops seeding for you, and the pooled URL will not finish the job1 distinct publisher
build
Your ORM never puts a WHERE clause in an index, and that is where the seq scans live1 distinct publisher
security
ToxicPanda 2.0 Widens From 16 Apps to 140, and From Overlays to ADB Shell3 distinct publishers
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 15, 2026