PgBouncer простыми словами: зачем нужен и как настроить
Представь: приложение работает, трафик растёт. Внезапно PostgreSQL начинает отдавать ошибку FATAL: sorry, too many clients. Сервер не сломан - он просто захлебнулся соединениями. PgBouncer решает эту проблему без переписывания кода и без апгрейда железа.
В статье разбираем, как устроен пулер соединений, чем отличаются три режима работы и как за час поднять PgBouncer в рабочей среде.
Почему база ломается от соединений
Как PostgreSQL обрабатывает соединения
PostgreSQL устроен просто и прямолинейно: на каждое входящее подключение сервер делает fork() и запускает отдельный процесс - backend process. Пока соединение живо, процесс живёт вместе с ним, занимая память и процессорное время.
Это работает хорошо при десятках соединений. Но современные веб-приложения и микросервисы открывают сотни и тысячи подключений одновременно.
Чем опасен рост max_connections
Первый инстинкт - увеличить max_connections в postgresql.conf. Но это ловушка:
- Память. Каждое соединение потребляет 5–10 МБ RAM ⚠️ (оценка, зависит от рабочих буферов и нагрузки). 500 соединений → 2,5–5 ГБ только на обслуживание подключений.
- CPU. Чем больше процессов, тем больше контекстных переключений. Ядра заняты переключением, а не выполнением запросов.
- Нестабильность. Система сначала тормозит, потом падает - даже при умеренной нагрузке.
Симптомы перегрузки
Понять, что база задыхается от соединений, помогают эти признаки:
- ошибка
FATAL: sorry, too many clientsв логах приложения - время отклика растёт даже при простых запросах
pg_stat_activityпоказывает десятки «idle» соединений, которые ничего не делают- CPU сервера загружен, а запросы медленные
Авторская ремарка: на одном проекте мы столкнулись с этой проблемой при 120 одновременных пользователях. Поднять
max_connectionsдо 500 не помогло - только ускорило деградацию. PgBouncer убрал проблему за 40 минут настройки.
Что такое PgBouncer и как он работает
Схема работы: клиент → PgBouncer → PostgreSQL
PgBouncer - лёгкий прокси-пулер, который встаёт между приложением и базой данных. Приложение думает, что подключается к PostgreSQL, но на самом деле соединяется с PgBouncer (порт 6432 по умолчанию).
Приложение (500 клиентов)
↓
PgBouncer :6432
[пул из 30 соединений]
↓
PostgreSQL :5432
Принцип работы:
- Клиент подключается к PgBouncer, а не к PostgreSQL напрямую.
- PgBouncer проверяет пул - есть ли свободное серверное соединение.
- Если есть - отдаёт его клиенту. Если нет - ставит клиента в очередь.
- После завершения работы физическое соединение возвращается в пул, а не закрывается.
Результат: PostgreSQL видит 30 соединений вместо 500. Память и CPU под контролем.
Что PgBouncer ускоряет (а что нет)
Важно понять сразу: PgBouncer не ускоряет сами SQL-запросы. Он оптимизирует фазу установления соединений - убирает накладные расходы на fork() и аутентификацию при каждом обращении к базе.
Что реально улучшается:
| Что | До PgBouncer | После |
|---|---|---|
| Число процессов PostgreSQL | = числу клиентов | = размеру пула |
| Время на connect/disconnect | 1–5 мс каждый раз | минимальное (переиспользование) |
| Пиковое потребление RAM | пропорционально клиентам | контролируемо |
| Устойчивость при всплесках | ошибки при превышении лимита | очередь в PgBouncer |
PgBouncer vs pgpool-II
⚠️ Предположительно, детальное сравнение производительности требует актуального бенчмарка. Ниже - ключевые структурные отличия по публичной документации:
| Критерий | PgBouncer | pgpool-II |
|---|---|---|
| Назначение | Только пулинг соединений | Пулинг + балансировка нагрузки + репликация |
| Сложность | Низкая | Высокая |
| Потребление ресурсов | Минимальное | Выше |
| Поддержка read-replicas | Нет | Да |
| Применение | OLTP, веб-приложения | HA-кластеры, read-heavy нагрузка |
Если нужен только пулинг - берите PgBouncer. Если нужна балансировка между мастером и репликами - смотрите в сторону pgpool-II.
Три режима пула: session, transaction, statement
Ключевой параметр PgBouncer - pool_mode. Он определяет, когда серверное соединение возвращается обратно в пул.
session pooling: максимум совместимости
pool_mode = session
Серверное соединение закреплено за клиентом на всю сессию - пока клиент не отключится. Поведение почти идентично прямому подключению к PostgreSQL:
- работают сессионные prepared statements
- работают временные таблицы
- работают
SET-параметры на уровне сессии
Минус: пул почти не работает. Если у тебя 200 постоянных соединений от приложения - в PostgreSQL будет ровно 200 соединений. Экономии нет.
Когда использовать: приложения, активно использующие сессионное состояние (долгоживущие prepared statements, временные таблицы).
transaction pooling: лучший баланс
pool_mode = transaction
Серверное соединение выдаётся только на время транзакции (BEGIN ... COMMIT), затем возвращается в пул. Это главный режим для OLTP-нагрузки:
- 500 клиентов могут работать через 30 серверных соединений
- пул максимально эффективен
Важный нюанс: сессионное состояние не сохраняется между транзакциями. Это означает:
- сессионные prepared statements не работают (они могут уйти в другое соединение)
SET variableбезLOCALможет потеряться- временные таблицы ведут себя непредсказуемо
Обязательно: установите server_reset_query = DISCARD ALL - тогда PgBouncer будет очищать состояние соединения перед возвратом в пул.
Когда использовать: большинство современных веб-приложений, микросервисы, Django, FastAPI с ORM.
statement pooling: осторожно
pool_mode = statement
Соединение возвращается в пул после каждого отдельного SQL-запроса. Многозапросные транзакции (BEGIN ... COMMIT) фактически не работают - каждый запрос может попасть в разное соединение.
Используйте только если точно понимаете последствия и все операции - атомарные одиночные запросы без транзакций.
Установка и базовая настройка
Установка на Ubuntu/Debian
sudo apt update
sudo apt install pgbouncer -y
Для получения актуальных версий рекомендуется подключить официальный репозиторий PGDG:
sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \
--fail https://www.postgresql.org/media/keys/ACCC4CF8.asc
sudo sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] \
https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list'
sudo apt update && sudo apt install pgbouncer -y
Установка на RHEL/CentOS/AlmaLinux
sudo dnf install pgbouncer
Конфиг pgbouncer.ini: ключевые параметры
Основной файл - /etc/pgbouncer/pgbouncer.ini:
[databases]
; * = маршрут для всех баз, или конкретное имя
myapp = host=127.0.0.1 port=5432 dbname=myapp_db
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
; Аутентификация
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
; Режим пула
pool_mode = transaction
; Лимиты
max_client_conn = 500
default_pool_size = 50
min_pool_size = 10
reserve_pool_size = 20
reserve_pool_timeout = 5
; Таймауты
server_idle_timeout = 600
server_lifetime = 3600
query_wait_timeout = 120
client_login_timeout = 60
; Сброс состояния между транзакциями (обязателен для transaction-пула)
server_reset_query = DISCARD ALL
; Диагностика
application_name_add_host = 1
log_connections = 1
log_disconnections = 1
stats_period = 60
; Консоль управления
admin_users = pgbouncer
После настройки запустите и добавьте в автозагрузку:
sudo systemctl enable pgbouncer
sudo systemctl start pgbouncer
Проверьте подключение (PgBouncer слушает порт 6432):
psql -h 127.0.0.1 -U myuser -d myapp -p 6432
Аутентификация: scram-sha-256 и md5
По умолчанию PgBouncer работает в режиме trust - без проверки пароля. Для продакшена используйте scram-sha-256 или хотя бы md5.
Файл /etc/pgbouncer/userlist.txt:
"myuser" "SCRAM-SHA-256$<хеш из pg_shadow>"
Хеш пароля берётся прямо из PostgreSQL:
SELECT usename, passwd FROM pg_shadow WHERE usename = 'myuser';
Не забудьте разрешить PgBouncer подключаться в pg_hba.conf PostgreSQL:
host all all PGBOUNCER_IP/32 scram-sha-256
После изменений - перезагрузите оба сервиса.
Расчёт размера пула и таймауты
max_client_conn и default_pool_size
Это два самых важных параметра:
- max_client_conn - максимум клиентов, которых PgBouncer удерживает одновременно (часть из них может стоять в очереди). По умолчанию 100. Ставьте в 2–3× от числа воркеров приложения.
- default_pool_size - сколько реальных соединений к PostgreSQL на пару «база + пользователь». Именно этот параметр экономит память на стороне Postgres.
Практические ориентиры:
| Размер VDS | Воркеры приложения | max_client_conn | default_pool_size |
|---|---|---|---|
| 2 CPU / 4–8 ГБ | ~100 | 300–400 | 30–50 |
| 4–8 CPU / 16–32 ГБ | ~300–500 | 800–1200 | 60–120 |
Главное правило: суммарный пул по всем базам и пользователям не должен превышать max_connections в PostgreSQL - иначе Postgres начнёт отказывать в логине.
reserve_pool_size и min_pool_size
- min_pool_size - «тёплые» соединения, которые PgBouncer держит открытыми заранее. Снижает задержку на первые запросы под нагрузкой.
- reserve_pool_size - резервные соединения для кратковременных всплесков, когда основной пул исчерпан. Рекомендуется 10–30% от
default_pool_size.
Если PgBouncer задействовал резервный пул - это сигнал в логах, что основной default_pool_size стоит увеличить.
Таймауты: query_wait_timeout, server_idle_timeout, server_lifetime
| Параметр | Зачем | Типичное значение |
|---|---|---|
| query_wait_timeout | Как долго клиент ждёт свободное соединение | 60–120 сек |
| server_idle_timeout | Через сколько секунд закрыть простаивающее соединение | 600 сек |
| server_lifetime | Мягкая ротация соединений против утечек памяти | 3600–7200 сек |
| client_login_timeout | Лимит фазы логина клиента | 60 сек |
| client_idle_timeout | Отключать ли пассивных клиентов (0 = нет) | 0 |
Мониторинг: метрики и консоль
Команды SHOW POOLS / SHOW STATS / SHOW CLIENTS
PgBouncer предоставляет административную консоль через псевдобазу pgbouncer:
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer
Основные команды:
| Команда | Что показывает |
|---|---|
| SHOW POOLS; | Активные клиенты, очередь, свободные серверные соединения |
| SHOW STATS; | Число запросов, время выполнения, объём трафика |
| SHOW CLIENTS; | Каждый клиент с его статусом и ожиданием |
| SHOW SERVERS; | Реальные соединения к PostgreSQL |
| SHOW CONFIG; | Текущий конфиг с учётом переопределений |
| RELOAD; | Перечитать конфиг без остановки |
Сигналы тревоги: cl_waiting и sv_idle
Два ключевых числа из SHOW POOLS:
- cl_waiting > 0 длительное время - пул слишком мал или запросы слишком долгие. Увеличивайте
default_pool_sizeили оптимизируйте запросы. - sv_idle = 0 постоянно - вы на пределе. Сервер PostgreSQL не справляется с текущим пулом.
Авторская ремарка: достаточно добавить алерт в мониторинг на
cl_waiting > 5в течение 30 секунд - это даст время заметить проблему до деградации сервиса.
Reload без даунтайма
PgBouncer поддерживает перезагрузку конфига и онлайн-перезапуск без разрыва клиентских соединений:
# Перезагрузить конфиг
sudo systemctl reload pgbouncer
# Или через консоль
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "RELOAD;"
# Онлайн-перезапуск процесса (новый процесс принимает соединения от старого)
sudo -u postgres pgbouncer -R /etc/pgbouncer/pgbouncer.ini -d
Перед обслуживанием используйте PAUSE [dbname] - PgBouncer дождётся завершения текущих запросов и приостановит новые подключения.
Типичные ошибки и как их чинить
too many clients
Ошибка: FATAL: too many clients already в логах PgBouncer.
Причина: достигнут max_client_conn - PgBouncer сам задыхается от клиентов.
Решение:
- Увеличить
max_client_conn. - Найти источник лишних соединений: зависшие воркеры, ORM с агрессивным пулингом, утечки соединений в коде.
Другой вариант: sorry, too many clients уже от самого PostgreSQL - суммарный пул превысил max_connections. Уменьшить default_pool_size или увеличить max_connections с учётом доступной RAM.
prepared statements в transaction-режиме
Ошибка: prepared statement "X" already exists или prepared statement "X" does not exist.
Причина: в transaction-режиме каждая транзакция может получить другое серверное соединение. Сессионные prepared statements теряются или конфликтуют.
Решения:
- Переключить конкретную базу/пользователя на
pool_mode = session. - Отключить сессионные prepared statements в приложении (ORM-настройки) или использовать протокольные prepared statements с параметром
max_prepared_statements(поддерживается в новых версиях PgBouncer). - Убедиться, что
server_reset_query = DISCARD ALLвыставлен.
Ошибки аутентификации
Ошибка: no pg_hba.conf entry for host "X", user "Y".
Решение: проверить, что IP-адрес PgBouncer добавлен в pg_hba.conf PostgreSQL с правильным методом аутентификации. После изменений перезагрузить PostgreSQL (SELECT pg_reload_conf();).
Альтернативное мнение
Часть разработчиков считает, что PgBouncer - это костыль, а правильное решение - переписать приложение под async-пулинг (например, asyncpg в Python или pgx в Go). У этого подхода есть логика: современные async-фреймворки сами управляют соединениями и не создают idle-сессии. Однако на практике большинство production-систем используют уже работающий синхронный код, Django ORM, Celery-воркеры - и здесь PgBouncer даёт результат за минимум усилий, не требуя переписывания ни строчки кода приложения.
Нетривиальный факт
PgBouncer поддерживает онлайн-перезапуск через передачу файловых дескрипторов от старого процесса к новому (-R флаг). Новый процесс «подхватывает» открытые соединения, а старый завершается без потерь. Эту же механику использует Nginx в своём graceful restart - один из элегантных паттернов Unix-демонов.
Часто задаваемые вопросы (FAQ)
Что такое PgBouncer простыми словами?
PgBouncer - прокси между приложением и PostgreSQL. Он принимает сотни клиентских подключений, но держит к базе только небольшой фиксированный пул соединений. Приложение думает, что подключается к PostgreSQL напрямую - на деле работает через посредника.
Какой pool_mode выбрать?
Начинайте с transaction. Он даёт лучшую утилизацию пула при OLTP-нагрузке. Переходите на session, если приложение активно использует сессионные prepared statements или временные таблицы между транзакциями.
На каком порту работает PgBouncer?
По умолчанию на порту 6432 (PostgreSQL работает на 5432). В конфиге приложения замените порт подключения с 5432 на 6432 - и всё.
Почему в transaction-режиме ломаются prepared statements?
Сессионные prepared statements привязаны к конкретному соединению. В transaction pooling каждая транзакция может получить разное серверное соединение - и statement теряется или конфликтует. Решение: отключить их в приложении или переключить на session pooling для конкретной базы.
Нужно ли менять код приложения?
Нет. Достаточно изменить строку подключения - хост и порт указать PgBouncer вместо PostgreSQL. Приложение продолжает работать без изменений.
Зачем нужен server_reset_query = DISCARD ALL?
В transaction pooling одно соединение обслуживает разных клиентов. Перед возвратом в пул PgBouncer выполняет DISCARD ALL - очищает сессионные параметры, временные объекты, снимает блокировки. Без этого состояние одного клиента может «перетечь» к другому.
Как перезапустить PgBouncer без разрыва соединений?
Используйте sudo systemctl reload pgbouncer для перезагрузки конфига или pgbouncer -R для онлайн-перезапуска процесса - он передаёт открытые соединения новому процессу без разрыва.
Источники
- Рег.облако - «Как установить и запустить PgBouncer для PostgreSQL»: https://reg.cloud/blog/pgbouncer-dlya-postgresql/
- fastfox.pro - «Пулы соединений PostgreSQL, конфиг, лимиты и метрики»: https://fastfox.pro/blog/tutorials/pgbouncer-postgresql-pooling-guide/
- Habr Q&A - «Как pgbouncer обрабатывает idle сессии»: https://qna.habr.com/q/1340768
- Cybertec PostgreSQL - «pgbouncer: Types of PostgreSQL connection pooling»: https://www.cybertec-postgresql.com/en/pgbouncer-types-of-postgresql-connection-pooling/
- Postgres Professional - Документация pgbouncer: https://postgrespro.ru/docs/postgrespro/current/pgbouncer
- Microsoft Azure PostgreSQL - «PgBouncer в гибком сервере»: https://learn.microsoft.com/ru-ru/azure/postgresql/flexible-server/concepts-pgbouncer
- Arenadata Docs - «Как работать с PgBouncer»: https://docs.arenadata.io/ru/ADPG/current/concept/architecture/pgbouncer.html



.svg.webp)





