«remaining connection slots are reserved» при работающем pgbouncer: пулы считаются на каждую пару пользователь и база

Что было. PostgreSQL с max_connections = 250, перед ним pgbouncer в режиме transaction с default_pool_size = 10 и max_client_conn = 10000. Приложение периодически получало FATAL: remaining connection slots are reserved for non-replication superuser connections, хотя автор был уверен, что pgbouncer держит соединения под контролем и их «никогда не больше 130».

В чём ошибка в рассуждении. pgbouncer создаёт отдельный пул на каждую пару «пользователь + база данных». У автора было около 150 баз. Даже при одном пользователе на базу это 150 пулов по 10 серверных соединений в пределе, то есть до 1500 потенциальных подключений к PostgreSQL при лимите 250. В обычное время пулы почти пусты (поэтому 130), но при всплеске нагрузки на десятки баз одновременно они разрастаются, и последние 3 слота (superuser_reserved_connections) остаются только для суперпользователя. Отсюда ошибка.

Что предложили.

  1. Уменьшить default_pool_size (например, до 3–5) и min_pool_size до 0–1: при 150 базах и так набирается много.
  2. Задать max_db_connections в pgbouncer: жёсткий потолок на базу независимо от числа пользователей.
  3. Поднять max_connections в PostgreSQL в разумных пределах (каждое соединение это отдельный процесс и память) и, если нужно, superuser_reserved_connections.
  4. Посмотреть, кто реально держит соединения: SELECT usename, datname, state, count(*) FROM pg_stat_activity GROUP BY 1,2,3 ORDER BY 4 DESC; и в консоли pgbouncer SHOW POOLS;.
  5. Убить долгие простаивающие транзакции: idle_in_transaction_session_timeout в PostgreSQL (например, 30s). Соединение в состоянии idle in transaction занимает слот и не отдаёт его пулу.

Практическое правило. Сумма default_pool_size × число активных пар пользователь/база (плюс reserve_pool_size) должна быть меньше max_connections − superuser_reserved_connections − соединения, идущие мимо pgbouncer (миграции, cron, мониторинг). Если баз много, проще одна общая база со схемами, чем сотни маленьких.

Если pgbouncer нет. Та же ошибка означает, что приложение открывает соединений больше, чем разрешено: обычно каждый воркер держит свой пул. Ставить pgbouncer в режиме transaction и уменьшать пулы в приложении.

Параметры соединений описаны в документации PostgreSQL.