PostgreSQL Connection Pool Size Calculator

Give this PostgreSQL connection pool size calculator a server and the shape of the application in front of it, and four numbers come back. It works out max_connections, the pool each worker process should keep, default_pool_size for PgBouncer and the ceiling on clients, with presets for what Supabase, Neon and AWS publish.

Storage

The formula doubles this. Between 1 and 512.

Read by the memory estimate only. Between 1 and 4,096.

The formula's effective_spindle_count, which is zero once the working set is cached. Between 0 and 64.

Containers, pods or machines running your application. Between 1 and 512.

Processes or threads inside one instance, each keeping a pool of its own. Between 1 and 128.

superuser_reserved_connections, which PostgreSQL ships at 3. Between 0 and 100.

(4 × 2) + 0 = 8 connections busy at once. Your 2 instances of a single worker split that into 2 pools of 4.

max_connections 16, pool size 4 per worker, default_pool_size 8, 20 pooler clients.

max_connections

16

your 8 plus 3 reserved and 5 spare

Pool size

4

per worker process, 4 per instance

default_pool_size

8

the whole of what the formula wants busy

max_client_conn

20

clients PgBouncer will admit, 10 of them spare

An estimate, not a measurement: 16 connections each running 2 sorts at the 4MB work_mem default would ask for 128 MB, 1% of the 16GB on this server. PostgreSQL ships with 100, which is a ceiling rather than a target.

postgresql.conf
# 4 cores, 0 spindles: the formula wants (4 * 2) + 0 = 8 connections busy at once.# 2 instances x 1 worker x 4 connections = 8, plus 3 reserved and 5 for psql and migrations.max_connections = 16superuser_reserved_connections = 3## Both take effect at server start, so this is a restart rather than a reload.# A rolling deploy runs old and new instances at once, so double the app total or drain the old ones first.
pgbouncer.ini
[databases]* = host=127.0.0.1 port=5432 [pgbouncer]pool_mode = transactiondefault_pool_size = 8max_client_conn = 20 # default_pool_size is the whole of what the formula wants busy.# PgBouncer ships with 20 and 100 in those two lines.# session mode instead of transaction keeps SET, LISTEN and temporary tables, and most of the reason you wanted a pooler.
Your driver
# node-postgres, one pool per worker processnew Pool({ max: 4 }) # PrismaDATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DB?connection_limit=4" # SQLAlchemycreate_engine(URL, pool_size=4, max_overflow=0) # max_overflow=0 holds SQLAlchemy at 4. Its default opens ten more under load, unasked.

Nothing you type is sent anywhere: the arithmetic and the preset table both ship with this page. Ctrl+Enter, Cmd+Enter on a Mac, copies the postgresql.conf block.

What it does and how to use it

Start with the server. The self-hosted tab asks for the core count, the memory and the number of spinning disks under the data directory, while a provider tab replaces the first two with a compute size and adds the connection ceiling that provider publishes for it.

The other half of the form is what connects. An app instance is a container or a machine, a worker is a process or a thread inside one, and since every worker keeps a pool of its own, a box running four Gunicorn workers arrives at the database looking like four clients. The three blocks underneath follow what you type, so the postgresql.conf lines, the pooler settings and the driver call never drift apart from each other, and none of it leaves the tab.

How many connections a PostgreSQL server should really take

The project wiki has carried the same answer for years, ((core_count * 2) + effective_spindle_count) active connections, with the spindle term falling to zero once the working set is cached. Four cores on an SSD come out at eight, which looks absurd beside the 100 that max_connections ships with, until you notice the two figures answer different questions. Eight is how many backends should be doing work at any instant. Attached and idle is a different state, and a backend sitting in it still holds a process and whatever work_mem its last sort claimed, which is what the ceiling is really rationing. Past the point where the cores are saturated, one more connection buys a context switch and a share of the lock contention rather than throughput, and that is the argument for a pooler sizing two numbers instead of one: pgbouncer default_pool_size is what the formula produced, max_client_conn is what your fleet can open.

Transaction pooling makes that ratio possible and also catches people out. The server connection goes back to the pool at COMMIT, so everything living on the session leaves with it, a SET, a prepared statement, a temporary table, a LISTEN. Supabase draws the line by port, 5432 reaching Postgres directly or Supavisor in session mode and 6543 reaching the transaction pooler, and Neon draws it by hostname with -pooler in the pooled one. Both splits are worked through where they bite hardest, in seeding a Supabase project and in seeding a Neon database, since a seed run is the long transaction with prepared statements in it that transaction pooling suits least.

FAQ

Frequently asked questions

A connection is a backend process with memory of its own, and once the cores are busy the extra processes queue rather than work. Raising the ceiling moves that queue out of your pooler and into the database, where holding it costs more.

On a Supabase project the choice is already made, and the number to change sits in the dashboard under connection pooling rather than in a file. PgBouncer is the usual answer everywhere else, and the two settings this page writes mean the same thing in both.

It is per process, because a pool belongs to whichever process created it. Ten containers of four workers hold forty pools rather than one, so the figure labelled Pool size is what a single one of those forty gets.

The instance count stops being something you choose, since a function can run in hundreds of copies during a spike and none a second later. Neon lists exactly that as a common way to exhaust a compute size, so enter the concurrency ceiling your platform allows and point the functions at the pooled hostname.

Every one of them stays in the tab. The arithmetic is a few dozen lines of JavaScript and the preset table is a static file, both already loaded with the page, so there is nothing to send and no connection string to ask you for.

From the providers themselves, read on 11 September 2026, with the source linked under the panel on each tab. Supabase and Neon publish a column per compute size, while AWS publishes a formula instead of a table.

DBInstanceClassMemory, the variable in the AWS formula, is smaller than the gibibytes an instance class advertises. AWS takes out what the operating system and its own management processes hold before dividing, and it does not publish that remainder per class.

It is free and there is no account. Once the page has loaded, every keystroke is handled here, and nothing you enter is stored.