PgBouncer's defaults get connection pooling working in minutes, then hand you a set of cliff-edges that most teams only discover during an incident.
The defaults work. Until a burst of slow queries holds every server connection, clients pile into the FIFO wait queue, cl_waiting climbs, and applications start timing out and retrying — a thundering herd that grows the queue faster than it can drain. Until max_client_conn is reached and new connections are refused instantly with no more connections allowed. Until someone switches to transaction pooling for efficiency and the application starts throwing prepared statement "..." does not exist under load. Until a client runs BEGIN, wanders off to do other work, and holds a pool slot idle-in-transaction while everyone else waits. Until a PostgreSQL failover leaves PgBouncer's DNS cache pointing at the dead primary.
These guides are written for engineers who already run PgBouncer, not for people learning what connection pooling is. The goal is the mental model of how the pooler actually behaves under load, the failure patterns that keep recurring, the monitoring story that catches them before they page anyone, and the runbooks you wish someone had handed you before your last incident. PgBouncer is a proxy, not a database — monitor it the way you monitor HAProxy or nginx, and remember it has zero error counters, so its errors live only in the log.