<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp" -->

---
title: Connection Pool Sizing, Measured: Why 48 Connections...
description: A benchmark sweep of a PostgreSQL instance from 1 to 400 connections shows throughput peaking at 48 connections (18,039 tps) and dropping 37% by 400...
canonical: https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Connection Pool Sizing, Measured: Why 48 Connections Beat 400 | daily.dev
og:description: A benchmark sweep of a PostgreSQL instance from 1 to 400 connections shows throughput peaking at 48 connections (18,039 tps) and dropping 37% by 400...
og:url: https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp
og:image: https://api.daily.dev/og/posts/F0OqiZyNP.png
og:image:alt: Connection Pool Sizing, Measured: Why 48 Connections Beat 400
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Connection Pool Sizing, Measured: Why 48 Connections Beat 400

**[The Infinity](https://daily.dev/sources/theinfinity-dev)** · [@serdarbuyukdereli](https://daily.dev/serdarbuyukdereli) · 11 min read · 62 upvotes · 5 comments

## Summary

A benchmark sweep of a PostgreSQL instance from 1 to 400 connections shows throughput peaking at 48 connections (18,039 tps) and dropping 37% by 400 connections, while average latency multiplied 13x. The piece explains why connections are OS processes competing for cores rather than free capacity, tests the Universal Scalability Law fit (R²=0.94, predicted optimum 45.1), evaluates the (cores×2)+spindles formula (predicts 9, far off the measured 48), and finds that synchronous_commit=off raises throughput but does not shift the peak. It closes with practical guidance: watch queue depth (cl_waiting in PgBouncer, pending-threads in HikariCP) rather than connection count, account for instances × pool size multiplication, and use transaction pooling or RDS Proxy for Lambda to avoid connection exhaustion.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://theinfinity.dev/articles/connection-pool-sizing-measured>

## Questions this post answers

### How many connections should my PostgreSQL connection pool have?

Fewer than intuition suggests, and the reliable way to find out is to sweep your own workload. On a 4-core PostgreSQL 17 instance, measured throughput peaked at 48 connections (18,039 tps), while the widely quoted (cores × 2) + spindles formula predicted only 9. Growing the pool to 400 connections cost 37% of throughput and multiplied latency by 13.3x, so size to the measured peak, not the maximum.

_Engineers debating pool sizing trade-offs can find measured benchmarks like this one on daily.dev._

### Why does adding more connections make PostgreSQL slower instead of faster?

Each PostgreSQL connection is an operating system process, and once more of them are runnable than there are CPU cores, they stop running in parallel and start taking turns, incurring context-switch overhead, lock contention, and cache pressure. In a measured sweep, going from 48 to 400 connections on a 4-core instance cost 37% of throughput and multiplied average latency from 2.7ms to 36.4ms, a 13.3x increase.

_Debugging a database that feels slow under load is easier with concrete data like this on daily.dev._

### What metric should I monitor to know if my PgBouncer or HikariCP connection pool is too small?

Queue depth, not connection count or CPU usage, indicates whether a pool is undersized. In PgBouncer, SHOW POOLS reports cl_waiting; consistently above zero means clients are queuing for a server connection and the pool has room to grow. In HikariCP, the equivalent signal is the pending-threads gauge; if it stays at zero while the database isn't saturated, the pool is already big enough and growing it only adds latency.

_Teams tuning pool configs under production load track signals like this via daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@agustinbarrientos** · 4 upvotes

> Pool-size changes should include queue-depth alerts and load sweeps at the same pod count production can reach. Autoscaling can quietly multiply a safe local setting into 400 connections

**@rizzdev** · 1 upvotes

> The contention-region USL at R² 0.94 is what you actually size against, but λ=1952 against a measured 3645 tps at one connection is the fit inventing a slower serial box and a 45.1 optimum that lands on the measured 48. Anchoring λ to that 3645 dropped R² to 0.75 because the peak is being read back out of the same crowded points that defined it

**@mikhailmakeev** · 1 upvotes

> We saw the same shape on Postgres, just from pooling rather than the app layer. Before PgBouncer our 1-vCPU instance was fielding about 75 connections, most of them idle Django workers holding a long CONN_MAX_AGE. Transaction pooling dropped that to 16 live backends overnight, and we deliberately kept the pool small afterward, pool_size 20, even though max_client_conn allowed 400. The vCPU was the real ceiling, not the connection count, and more backends just thrashed against it.

## Similar posts on daily.dev

- [All Your GUCs in a Row: max\_connections](https://daily.dev/posts/all-your-gucs-in-a-row-max-connections-32g7lgls2) · Planet PostgreSQL · 0 upvotes · 0 comments
- [Postgres Postmaster does not scale](https://daily.dev/posts/postgres-postmaster-does-not-scale-fee3cv3ni) · Hacker News · 45 upvotes · 1 comments
- [Connection pooling strategies in Amazon Aurora DSQL](https://daily.dev/posts/connection-pooling-strategies-in-amazon-aurora-dsql-g4hpbgqfy) · AWS Database Blog · 0 upvotes · 0 comments

---

Tags: [#database](https://daily.dev/tags/database), [#postgresql](https://daily.dev/tags/postgresql)

[View this post on daily.dev](https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Connection Pool Sizing, Measured: Why 48 Connections Beat 400","url":"https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp"},"datePublished":"2026-08-21T11:37:41.468Z","dateModified":"2026-09-14T08:00:22.366Z","description":"A benchmark sweep of a PostgreSQL instance from 1 to 400 connections shows throughput peaking at 48 connections (18,039 tps) and dropping 37% by 400...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/d4fbfed9e9ddff9b0f571be714e1fdc3?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/d4fbfed9e9ddff9b0f571be714e1fdc3?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"The Infinity","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Person","name":"Serdarcan Buyukdereli","url":"https://daily.dev/serdarbuyukdereli","image":"https://media.daily.dev/image/upload/s--tTV8hAPq--/f_auto/v1778701721/avatars/avatar_Su5HqluAE4wLRb1naHjtv?_a=BAMAMiWQ0","description":"Senior Devops and Cloud Engineer\n","interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"EndorseAction"},"userInteractionCount":21430}},"commentCount":5,"discussionUrl":"https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":62},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":5}],"keywords":"database,postgresql","timeRequired":"PT11M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"The Infinity","item":"https://daily.dev/sources/theinfinity-dev"},{"@type":"ListItem","position":3,"name":"Connection Pool Sizing, Measured: Why 48 Connections Beat 400"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp","comment":[{"@type":"Comment","text":"Pool-size changes should include queue-depth alerts and load sweeps at the same pod count production can reach. Autoscaling can quietly multiply a safe local setting into 400 connections","datePublished":"2026-08-25T04:06:48.454Z","url":"https://daily.dev/posts/F0OqiZyNP#c-9BGtOwPV7","author":{"@type":"Person","name":"Agustin Barrientos","url":"https://daily.dev/agustinbarrientos","image":"https://media.daily.dev/image/upload/s--5ayxQnqn--/f_auto/v1788281802/avatars/avatar_wQYYVe5Tbj0NJ7C7qPoa8?_a=BAMAMicg0"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":4}},{"@type":"Comment","text":"The contention-region USL at R² 0.94 is what you actually size against, but λ=1952 against a measured 3645 tps at one connection is the fit inventing a slower serial box and a 45.1 optimum that lands on the measured 48. Anchoring λ to that 3645 dropped R² to 0.75 because the peak is being read back out of the same crowded points that defined it","datePublished":"2026-08-25T07:52:13.881Z","url":"https://daily.dev/posts/F0OqiZyNP#c-krd3faBYj","author":{"@type":"Person","name":"Andrew","url":"https://daily.dev/rizzdev","image":"https://media.daily.dev/image/upload/s--35vRfXAA--/f_auto/v1785413727/avatars/avatar_fQ7ttCKtPuu6tDvcgQXv5?_a=BAMAMicg0"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1}},{"@type":"Comment","text":"We saw the same shape on Postgres, just from pooling rather than the app layer. Before PgBouncer our 1-vCPU instance was fielding about 75 connections, most of them idle Django workers holding a long CONN_MAX_AGE. Transaction pooling dropped that to 16 live backends overnight, and we deliberately kept the pool small afterward, pool_size 20, even though max_client_conn allowed 400. The vCPU was the real ceiling, not the connection count, and more backends just thrashed against it.","datePublished":"2026-08-28T09:38:12.865Z","url":"https://daily.dev/posts/F0OqiZyNP#c-JOH22xyQG","author":{"@type":"Person","name":"Mikhail Makeev","url":"https://daily.dev/mikhailmakeev","image":"https://lh3.googleusercontent.com/a/ACg8ocIjcvDU1gmJ9-KJDeTYqY2rMdl5QxcWYtLTGSXrj8ubF4nwar_w=s96-c"},"interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/connection-pool-sizing-measured-why-48-connections-beat-400-f0oqizynp#faq","mainEntity":[{"@type":"Question","name":"How many connections should my PostgreSQL connection pool have?","acceptedAnswer":{"@type":"Answer","text":"Fewer than intuition suggests, and the reliable way to find out is to sweep your own workload. On a 4-core PostgreSQL 17 instance, measured throughput peaked at 48 connections (18,039 tps), while the widely quoted (cores × 2) + spindles formula predicted only 9. Growing the pool to 400 connections cost 37% of throughput and multiplied latency by 13.3x, so size to the measured peak, not the maximum. Engineers debating pool sizing trade-offs can find measured benchmarks like this one on daily.dev."}},{"@type":"Question","name":"Why does adding more connections make PostgreSQL slower instead of faster?","acceptedAnswer":{"@type":"Answer","text":"Each PostgreSQL connection is an operating system process, and once more of them are runnable than there are CPU cores, they stop running in parallel and start taking turns, incurring context-switch overhead, lock contention, and cache pressure. In a measured sweep, going from 48 to 400 connections on a 4-core instance cost 37% of throughput and multiplied average latency from 2.7ms to 36.4ms, a 13.3x increase. Debugging a database that feels slow under load is easier with concrete data like this on daily.dev."}},{"@type":"Question","name":"What metric should I monitor to know if my PgBouncer or HikariCP connection pool is too small?","acceptedAnswer":{"@type":"Answer","text":"Queue depth, not connection count or CPU usage, indicates whether a pool is undersized. In PgBouncer, SHOW POOLS reports cl_waiting; consistently above zero means clients are queuing for a server connection and the pool has room to grow. In HikariCP, the equivalent signal is the pending-threads gauge; if it stays at zero while the database isn't saturated, the pool is already big enough and growing it only adds latency. Teams tuning pool configs under production load track signals like this via daily.dev."}}]}
```

