default_pool_size is one of those parameters that, if you set it "by eye", might work… until the day the load increases and it blows up:Postgres saturated(if you overdo it) orqueues in PgBouncer(if you fall short).
The good news: it can be sized with a simple method and then fine-tuned with metrics.
1) What really controls default_pool_size
PgBouncer defines default_pool_size as:
how many "server" connections it allows per user/database pair(user/database pair), and it can be overridden by DB. Postgres Professional
Intransaction pooling(the typical case with Odoo), a "server" connection is assignedonly during the transactionand then returns to the pool. Postgres Professional+1
Translation:default_pool_size is, in practice, your limit ofconcurrent transactionsto PostgreSQLper pool.
2) Before calculating: define how many "pools" you have (this changes everything)
Since the pool is by(db, user), the number of pools depends on how you connect:
Typical Odoo:1 DB user (odoo) + 1 DB ⇒1 pool(the most common).
Multi-tenant:1 DB user + N databases ⇒N pools(one per database).
Multiple DB users:(rare in Odoo) ⇒ you multiply pools (db × users).
3) Step by step to scale (without magic)
Step A — Define your real connection “budget” in PostgreSQL
Do not use max_connections as “all for PgBouncer.” Leave margin for:
superuser / admin
migrations, backups, maintenance
monitoring
replication (if applicable)
direct connections (if you decide to bypass for something specific)
Mental example:leave 10–20% freeas a buffer.
Step B — Distribute the budget among pools
Base formula:
default_pool_size_initial ≈ floor( pg_budget / num_pools )
Example:
max_connections = 300
reserve 50 for “everything else” →pg_budget = 250
you have 1 pool → default_pool_size ≈ 250 (but be careful: it’s not always advisable to go that high)
Step C — Do not give Postgres more concurrency than it can handle
Even if it “fits” in max_connections, Postgres can become slow if you give it too many simultaneous queries (CPU, I/O, locks). Therefore, as an operational rule:
Startconservatively(e.g. 20–80 per pool) and increase with metrics.
Sometimes 40 well-used connections perform better than 200 competing wildly.
default_pool_size is “how many” connections; it does not guarantee “more performance.”
4) Recommended method (the one that works in production)
4.1 Choose an initial default_pool_size per profile
If it is 1 pool (1 DB, 1 user):
Small server / moderate load:20–40
Medium server:40–80
Large server (powerful DB + optimized queries):80–150
If it is multi-DB (N pools):
split the budget by N andcapper pool (don't let one DB "eat" everything).
Remember: default_pool_size is per user/DB. Postgres Professional
4.2 Enable "burst control": reserve_pool_size
PgBouncer allows a temporary "extra" with:
reserve_pool_size (additional connections)
reserve_pool_timeout (when they are enabled if a client waits) Postgres Professional+1
Typical example for Odoo:
reserve_pool_size = 10 (or 10–20% of the pool)
reserve_pool_timeout = 5
This absorbs short spikes without over-provisioning the "normal" pool.
5) Use global limits to avoid surprises
Although default_pool_size is per (db,user), you can impose ceilings with:
max_db_connections (limit of server connections per database, regardless of user) Postgres Professional
max_user_connections (limit per user, regardless of DB) Postgres Professional
This is gold if you have multiple DBs or scenarios where a pool can grow more than expected.
6) The real validation (the only one that matters)
Signal 1: Is there a queue in PgBouncer?
Look at SHOW POOLS; and pay attention to:
cl_waiting (clients waiting)
sv_active (active server connections)
If cl_waiting spikesandPostgres is comfortable → you probably lack pool.
Signal 2: Is Postgres “happy” or saturated?
If when increasing default_pool_size you see:
CPU/IO at 100%
more lock waits
more latency per query
…your DB does not benefit from more concurrency: you need to optimize queries/indexes or lower the pool.
7) Example “by the book” (typical Odoo)
Scenario
1 DB, 1 user (odoo)
Postgres max_connections = 300
Reserves 60 (admin, maintenance, etc.) → budget = 240
You start with something prudent:
[pgbouncer] pool_mode = transaction default_pool_size = 80 reserve_pool_size = 20 reserve_pool_timeout = 5 max_db_connections = 120
Then:
if cl_waiting appears during peak hours and Postgres is not suffering → increase to 100–120
if Postgres is suffering → decrease and optimize
8) Bonus: don't forget max_client_conn (and FDs)
max_client_conn is how many clients (Odoo workers + connections) PgBouncer accepts, and the documentation itself warns that this impacts file descriptors and even provides formulas for the theoretical maximum. Postgres Professional
It is not directly part of the calculation of default_pool_size, but if you keep it low, you will see rejections even if the pool is fine.
Conclusion: the golden rule
default_pool_size is not “guessed”: it ischosen conservatively, itis observed(queues vs saturation) and itis adjusted.