When I first deployed a web service, I didn't think about connection pools. Then load testing showed my app grinding to a halt at ~50 concurrent users. The fix was three lines of config — but understanding why took much longer.
What is a connection?
Every time your app talks to PostgreSQL, it opens a TCP connection, authenticates, and establishes a session. This takes ~50–100ms. If every HTTP request opens and closes a connection, you're spending more time on handshakes than on actual queries.
What is a pool?
A connection pool keeps a set of connections open and reuses them. When a request needs the database, it borrows a connection from the pool, uses it, then returns it. If all connections are busy, the request waits.
# SQLAlchemy pool config (Python)
engine = create_engine(
DATABASE_URL,
pool_size=10, # Connections kept open
max_overflow=20, # Extra connections under load
pool_timeout=30, # Seconds to wait before error
pool_pre_ping=True, # Verify connection is alive
)
The math: how big should your pool be?
A common rule from HikariCP's docs: pool_size = (core_count * 2) + effective_spindle_count. For a 2-core server with SSD (spindle count = 1): pool size of 5. Most developers over-provision pools, which creates resource contention at the database level.
What I changed
After tuning the pool, my app handled 500 concurrent users on the same hardware. The bottleneck shifted from connection overhead to actual query performance — which is a much better problem to have.