Lab 09: Connection Pooling

Time: 40 minutes | Level: Practitioner | DB: PostgreSQL 15 (PgBouncer) + MySQL 8.0 (ProxySQL)

Each new database connection spawns a process/thread and costs ~2-10ms + ~5MB RAM. Connection pooling reuses connections, enabling thousands of app connections through a handful of database connections.


Step 1 — The Connection Cost Problem

-- PostgreSQL: each connection = a backend process
SELECT count(*) AS active_connections FROM pg_stat_activity;
SELECT setting AS max_connections FROM pg_settings WHERE name = 'max_connections';

-- Check connection overhead
SELECT pid, usename, application_name, state, wait_event_type,
       ROUND(EXTRACT(EPOCH FROM (NOW() - backend_start))) AS age_seconds
FROM pg_stat_activity
WHERE pid != pg_backend_pid()
ORDER BY backend_start;

Why pooling matters:

  • PostgreSQL default: max_connections = 100

  • Each connection: ~5MB RAM + fork overhead

  • 1000 app servers × 10 connections each = 10,000 connections needed

  • Solution: app connects to pooler, pooler maintains small pool to DB


Step 2 — PgBouncer: Install and Configure

pgbouncer.ini — the core configuration file:


Step 3 — PgBouncer Pooling Modes

Mode
Connection reuse
Transaction behavior
Use case

session

Per-session lifetime

Full ACID, SET vars persist

Legacy apps, prepared statements

transaction

Released after each transaction

Cannot use session-level SET

Most web apps, REST APIs

statement

Released after each statement

No multi-statement transactions

Read-only analytics

💡 Transaction mode is the sweet spot for web applications: a pool of 20 DB connections can serve thousands of concurrent app connections because each request only holds a connection during a transaction.


Step 4 — PgBouncer: SHOW Commands

SHOW POOLS output:

Column
Meaning

cl_active

Client connections actively using a server connection

cl_waiting

Client connections waiting for a free server connection

sv_active

Server connections actively serving a client

sv_idle

Server connections idle and ready

maxwait

Longest wait time (seconds) — high = pool too small


Step 5 — ProxySQL for MySQL

ProxySQL key concepts:

  • Hostgroups: groups of MySQL servers (e.g., 10 = writes, 20 = reads)

  • Query Rules: route queries to hostgroups based on regex patterns


Step 6 — ProxySQL: SHOW POOLS and SHOW STATS


Step 7 — Connection Pool Tuning

Connection string changes for PgBouncer:

💡 In transaction mode, avoid session-level features: SET LOCAL variables, PREPARE/EXECUTE statements, advisory locks, and LISTEN/NOTIFY. Use session mode for these.


Step 8 — Capstone: Sizing a Connection Pool

Simulate load to find the right pool size:


Summary

Feature
PgBouncer
ProxySQL

Database

PostgreSQL

MySQL

Pool modes

session / transaction / statement

Connection pooling

Read/write split

No (use HAProxy)

Yes (hostgroups + query rules)

Admin interface

psql on port 6432

mysql on port 6032

Config reload

RELOAD;

LOAD ... TO RUNTIME;

Key metric

maxwait in SHOW POOLS

avg_time_us in query digest

Last updated