PQ
PQ.Hosting

Валюта

pgBouncer: пул соединений для PostgreSQL — установка и настройка

Автор
PQ
06 апреля 2026
8 мин чтения
61 просмотров

PostgreSQL под каждое соединение поднимает отдельный серверный процесс. Стартует он с 5-10 МБ ОЗУ ещё до первого запроса. Умножьте на 100 клиентов - минимум полгигабайта памяти улетает в никуда. На 200+ соединениях PostgreSQL начинает ощутимо тормозить: latency растёт, планировщик больше занят переключением контекста между процессами, чем самими запросами. pgBouncer прослойкой между приложением и базой собирает все входящие соединения и отдаёт их в небольшой пул реальных коннектов к PostgreSQL.

Как pgBouncer работает

pgBouncer - это легковесный прокси-сервер, который располагается между приложением и PostgreSQL. Приложение подключается к pgBouncer так же, как к обычной базе данных, - по TCP на указанный порт. pgBouncer поддерживает собственный пул соединений к PostgreSQL и выдаёт их клиентам по запросу.

Ключевая идея: реальных соединений к PostgreSQL намного меньше, чем клиентских. Если к pgBouncer подключено 200 воркеров Django, а пул к PostgreSQL настроен на 20 соединений - в базе открыто ровно 20 процессов. Остальные 180 запросов ждут в очереди и получают соединение, как только оно освобождается.

В отличие от connection pooling внутри самого приложения (например, SQLAlchemy pool), pgBouncer работает на уровне сети и не зависит от языка или фреймворка. Один экземпляр pgBouncer обслуживает любое количество приложений и сервисов.

Три режима пулинга

Три режима, и выбор между ними - ключевое решение при настройке.

Session pooling - соединение с PostgreSQL выдаётся клиенту на всю сессию. Пока клиент подключён к pgBouncer - он держит одно реальное соединение в базе. Безопасно, поддерживает всё что умеет PostgreSQL, но если клиент сидит без активных запросов - ресурс тратится впустую. Экономия небольшая.

Transaction pooling - то, что нужно для большинства случаев. Соединение с базой занято только на время транзакции. После COMMIT или ROLLBACK оно идёт обратно в пул. 200 клиентов через 20 реальных соединений - и все довольны, если транзакции достаточно короткие.

Statement pooling - соединение возвращается после каждого отдельного оператора. Агрессивный режим с жёстким ограничением: BEGIN/COMMIT блоки не работают. Подходит разве что для аналитических SELECT без явных транзакций. В продакшне встречается редко.

Установка pgBouncer на Ubuntu/Debian

pgBouncer есть в стандартных репозиториях. Установка без лишних шагов:

sudo apt update
sudo apt install pgbouncer -y

После установки проверяем версию и статус службы:

pgbouncer --version
sudo systemctl status pgbouncer

Конфигурационные файлы:

  • /etc/pgbouncer/pgbouncer.ini - основной конфиг
  • /etc/pgbouncer/userlist.txt - список пользователей и паролей для аутентификации

По умолчанию pgBouncer слушает порт 6432. PostgreSQL продолжает работать на стандартном порту 5432. Приложение переключается на порт 6432 - больше никаких изменений в коде не требуется.

Базовая настройка pgbouncer.ini

Открываем основной конфиг:

sudo nano /etc/pgbouncer/pgbouncer.ini

Минимальная рабочая конфигурация:

[databases]
myapp = host=127.0.0.1 port=5432 dbname=myapp

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 500
default_pool_size = 20
log_file = /var/log/postgresql/pgbouncer.log
pid_file = /var/run/postgresql/pgbouncer.pid

Блок [databases] - маппинг имён баз данных. Клиент подключается к myapp на pgBouncer, pgBouncer проксирует запрос на реальную базу myapp по адресу 127.0.0.1:5432.

Файл userlist.txt содержит учётные данные в формате:

"username" "password"

Пароль можно хранить в открытом виде или в формате MD5-хеша PostgreSQL. Для получения MD5-хеша:

echo -n "passwordusername" | md5sum

Результат вставляется с префиксом md5:

"appuser" "md5a1b2c3d4e5f6..."

После изменения конфига перезапускаем службу:

sudo systemctl restart pgbouncer
sudo systemctl enable pgbouncer

Ключевые параметры производительности

default_pool_size - сколько реальных соединений к PostgreSQL открывается на каждую пару база+пользователь. Главный параметр. Одна база, один пользователь - это число и есть максимум соединений в PostgreSQL от pgBouncer. Начинайте с 20-30 для продакшна.

max_client_conn - лимит клиентских соединений к самому pgBouncer. Это не коннекты к базе, а то, сколько приложений может ломиться в pgBouncer одновременно. pgBouncer лёгкий - 1000 клиентских соединений для него не нагрузка, ставьте с запасом.

reserve_pool_size - резерв соединений на пиковые моменты. Если все слоты в основном пуле заняты и клиенты ждут дольше reserve_pool_timeout секунд - pgBouncer открывает дополнительные соединения из этого резерва. Хватит 5-10% от default_pool_size.

server_idle_timeout - через сколько секунд неактивное соединение к PostgreSQL закрывается. По умолчанию 600 сек. Снижение до 300 помогает освободить ресурсы базы в часы низкой нагрузки.

client_idle_timeout - то же для клиентских соединений. Клиент молчит дольше указанного времени - соединение разрывается. Защита от зависших коннектов.

server_connect_timeout - сколько ждать при открытии нового соединения к PostgreSQL. Если база не ответила за это время - pgBouncer считает попытку неудачной.

Расширенный пример конфига с этими параметрами:

[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
reserve_pool_size = 5
reserve_pool_timeout = 3
server_idle_timeout = 300
client_idle_timeout = 60
server_connect_timeout = 10
log_file = /var/log/postgresql/pgbouncer.log
pid_file = /var/run/postgresql/pgbouncer.pid

Практический пример

Рассмотрим реальный сценарий: Django-приложение развёрнуто в 200 воркерах Gunicorn на одном VPS с 8 ГБ ОЗУ.

До pgBouncer - прямые соединения с PostgreSQL:

  • 200 воркеров × 1 соединение каждый = 200 соединений в PostgreSQL
  • Каждое соединение занимает ~8 МБ
  • Итого: 200 × 8 МБ = 1,6 ГБ ОЗУ только под соединения
  • Реальная нагрузка на базу при этом - пиковые 30-40 одновременных запросов
  • 170 соединений большую часть времени простаивают, но ресурс потребляют

После pgBouncer в режиме transaction pooling:

  • 200 воркеров подключаются к pgBouncer - для pgBouncer это 200 лёгких клиентских соединений
  • pgBouncer держит 20 реальных соединений к PostgreSQL
  • 20 × 8 МБ = 160 МБ ОЗУ под соединения
  • Экономия: 1,44 ГБ оперативной памяти
  • Throughput при равномерной нагрузке не снижается - транзакции Django короткие

Конфиг Django для работы через pgBouncer:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'myapp',
        'USER': 'appuser',
        'PASSWORD': 'yourpassword',
        'HOST': '127.0.0.1',
        'PORT': '6432',  # порт pgBouncer, не PostgreSQL
        'OPTIONS': {
            'connect_timeout': 10,
        },
        'CONN_MAX_AGE': 0,  # важно: отключить persistent connections в Django
    }
}

Параметр CONN_MAX_AGE = 0 критически важен. Django по умолчанию держит соединения открытыми между запросами. При transaction pooling это нарушает логику пула - соединение не возвращается в pgBouncer после транзакции. Устанавливайте CONN_MAX_AGE = 0 при использовании transaction mode.

Частые проблемы и решения

Prepared statements ломаются в transaction mode. PostgreSQL именованные prepared statements привязаны к сессии. В transaction mode одна сессия PostgreSQL может обслуживать разных клиентов, и prepared statements предыдущего клиента недоступны следующему. Решение: отключить prepared statements на уровне ORM. Для SQLAlchemy:

create_engine(url, execution_options={"no_parameters": True})

Для Django с psycopg2 - использовать параметр prepared_statements = False или переключиться на psycopg3, который умеет работать с pgBouncer в transaction mode без отключения prepared statements.

Команды SET и временные таблицы. В transaction mode SET-команды (SET search_path, SET timezone и т.д.) и временные таблицы не переживают окончание транзакции - соединение уходит другому клиенту. Если приложение использует SET для смены схемы - либо включайте эти настройки через ALTER ROLE, либо переходите на session mode.

Ошибка аутентификации: password authentication failed. pgBouncer проверяет пользователя по userlist.txt, а не напрямую через PostgreSQL. Если пользователь есть в базе, но не добавлен в userlist.txt - получите отказ. Добавьте пользователя в оба места. После изменения userlist.txt перезагрузка не нужна - достаточно:

psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "RELOAD;"

pg_hba.conf не разрешает соединения от pgBouncer. Если pgBouncer работает на том же сервере, что и PostgreSQL, в pg_hba.conf должна быть строка для 127.0.0.1:

host    all             all             127.0.0.1/32            md5

После изменения pg_hba.conf:

sudo systemctl reload postgresql

Мониторинг текущего состояния. pgBouncer предоставляет служебную базу pgbouncer для мониторинга:

psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "SHOW POOLS;"
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "SHOW STATS;"
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "SHOW CLIENTS;"

Команда SHOW POOLS показывает, сколько клиентов ждут соединения (cl_waiting). Если это число постоянно больше нуля - увеличивайте default_pool_size.

Часто задаваемые вопросы

В чём разница между pgBouncer и PgPool-II?

pgBouncer - специализированный пулер соединений. Делает одно, но делает хорошо: минимальная задержка, низкое потребление ресурсов. PgPool-II - более тяжёлое решение с дополнительными функциями: репликация, балансировка нагрузки, кеширование запросов. Если нужен только пулинг соединений - pgBouncer быстрее и проще в настройке. PgPool-II оправдан, если нужна ещё и балансировка между несколькими репликами.

Сколько соединений в PostgreSQL можно поставить в max_connections?

Рекомендуемая формула: (RAM в ГБ × 100) / work_mem в МБ. При 8 ГБ ОЗУ и work_mem = 64MB получается около 200. Но с pgBouncer реальных соединений намного меньше, поэтому можно снизить max_connections в PostgreSQL до 100-150 и направить сэкономленную память на shared_buffers и work_mem.

Как мониторить pgBouncer в Prometheus?

Используйте pgbouncer_exporter. Он подключается к служебной базе pgBouncer и экспортирует метрики в формате Prometheus: количество клиентов, размер пулов, время ожидания, статистику запросов. Установка:

docker run -d \
  -e DATA_SOURCE_NAME="postgresql://pgbouncer:password@127.0.0.1:6432/pgbouncer?sslmode=disable" \
  -p 9127:9127 \
  prometheuscommunity/pgbouncer-exporter

Поддерживает ли pgBouncer SSL?

Да. pgBouncer поддерживает SSL как на стороне клиентов, так и на стороне PostgreSQL. Для включения SSL в конфиге:

[pgbouncer]
client_tls_sslmode = require
client_tls_key_file = /etc/pgbouncer/server.key
client_tls_cert_file = /etc/pgbouncer/server.crt
server_tls_sslmode = require

pgBouncer работает в контейнере - нужны ли изменения?

В контейнерной среде pgBouncer обычно разворачивают как отдельный sidecar-контейнер или как отдельный сервис в Docker Compose. Конфигурация идентична, меняется только адрес PostgreSQL. В Kubernetes pgBouncer часто запускают как DaemonSet или как отдельный Deployment с Service - это зависит от того, нужен ли общий пул для всего кластера или локальный на каждом узле.

Поделиться статьей

Похожие статьи