PgDoorman
Многопоточный пулер соединений для PostgreSQL, написанный на Rust. Совместимая замена для PgBouncer и Odyssey, альтернатива PgCat. Три года в промышленной эксплуатации у Ozon под нагрузками Go (pgx), .NET (Npgsql), Python (asyncpg, SQLAlchemy) и Node.js.
Скачать PgDoorman 3.11.0 · Сравнение · Бенчмарки
Ключевые возможности
В extended protocol многие драйверы отправляют короткие параметризованные запросы как Parse без имени statement. PgDoorman переименовывает такой Parse на стороне PostgreSQL в служебный DOORMAN_<N> и хранит соответствие в пуле. Следующие Bind для той же формы запроса используют уже подготовленное состояние.
Это снижает повторную работу планировщика PostgreSQL на горячих OLTP-запросах без изменений в приложении. PgBouncer 1.21+ и Odyssey работают с именованными prepared statements, но анонимный Parse передают как есть; PgDoorman закрывает этот частый сценарий у драйверов по умолчанию.
Кеш не растёт бесконтрольно: анонимные записи истекают по таймауту бездействия, именованные освобождаются после последней ссылки. SHOW INTERNER показывает память текстов запросов, а метрики Prometheus — попадания, промахи и вытеснения.
Когда несколько пользовательских пулов работают с одной базой, общий лимит должен защищать PostgreSQL, а не просто ставить клиентов в очередь. PgDoorman держит потолок через max_db_connections: если лимит выбран, координатор закрывает свободное соединение у пула с запасом и отдаёт слот клиенту, который ждёт подключение к PostgreSQL.
Доноры ранжируются по излишку свободных соединений. При равном излишке первым уступает пул с более высоким p95 времени транзакции: быстрые пулы сохраняют больше шансов на переиспользование, а у медленных вытеснение свободного соединения меньше мешает работе. Резервный пул сглаживает короткие всплески, а min_guaranteed_pool_size защищает важные нагрузки от вытеснения.
В PgBouncer max_db_connections задаёт общий лимит, но не перераспределяет уже открытые свободные соединения между пулами. В Odyssey прямого аналога нет.
Если PgDoorman запущен рядом с PostgreSQL и локальный сервер пропадает во время Patroni switchover, новые серверные соединения временно уходят на другой живой узел кластера. PgDoorman выбирает узел через Patroni REST API (GET /cluster): сначала sync_standby, затем replica.
Локальный сервер уходит в период охлаждения, а fallback-соединения получают короткий lifetime. Когда локальный узел снова доступен, пул возвращается на него без отдельного HAProxy или consul-template перед пулером. Настройки patroni_api_urls и fallback_cooldown задаются в [general].
Перед SIGUSR2 или UPGRADE можно заменить бинарь и конфиг на диске. PgDoorman проверяет эту комбинацию через -t, запускает дочерний процесс уже с ней, передаёт ему слушающий сокет, а старый процесс продолжает обслуживать существующих клиентов и переносит мигрируемые сессии.
В новый процесс уходят connection_id, ключ отмены запроса, параметры PostgreSQL-сессии, состояние аутентификации к PostgreSQL и клиентский кеш prepared statements. Новый процесс восстанавливает клиента в том же пуле. Для приложения соединение не рвётся: без переподключения, повторного auth/SCRAM и потери prepared statements. Если на новом серверном соединении statement ещё не подготовлен, PgDoorman отправит нужный Parse при первом Bind.
В foreground-режиме TCP-сессии без TLS передаются через SCM_RIGHTS. TLS-сессии мигрируют только в Linux-сборке с фичей tls-migration и теми же tls_certificate/tls_private_key; в обычных пакетах и Docker-образе она выключена, поэтому TLS-клиенты дренируются. Клиенты внутри транзакции остаются на старом процессе и переезжают после COMMIT или ROLLBACK. У PgBouncer (-R, устарел с 1.20, или rolling restart через so_reuseport) и Odyssey (SIGUSR2 + bindwith_reuseport) старые сессии остаются в старом процессе до отключения клиентов.
PgDoorman отдаёт веб-консоль на том же адресе и порту, что и /metrics. Это локальная панель для разбора инцидента, а не замена долгосрочному мониторингу в Prometheus/Grafana.
На одном экране видны насыщение пулов, p95/p99 задержки запросов, транзакций и ожидания соединения, ошибки по SQLSTATE, долгие запросы, состояние prepared statements и query interner, хвост лога, CPU по потокам tokio-worker и память процесса (jemalloc, /proc/self/status, код/библиотеки, стеки, swap).
Из консоли можно выполнить Pause, Resume, Reconnect и Reload для одного пула или всего инстанса. Остальные экраны доступны только для чтения. Веб-интерфейс включается только при [web].ui = true и непустом general.admin_password, отличном от admin; иначе PgDoorman оставляет только /metrics и пишет WARN в лог.
Почему PgDoorman
- Кеш
Parseна горячих запросах. PgDoorman переиспользует подготовленное состояние в пределах пула, включая безымянныйParse, который многие драйверы отправляют для коротких параметризованных запросов. Это снижает CPU планировщика PostgreSQL на повторяющихся OLTP-запросах;SHOW INTERNERпоказывает память текстов запросов, а метрики Prometheus — попадания, промахи и вытеснения. - Один пул для всех рабочих потоков. Рабочие потоки используют общий набор серверных соединений. При масштабировании PgBouncer несколькими процессами за
so_reuseportкаждый процесс держит свой пул, поэтому свободные соединения могут распределяться неравномерно. - Контроль всплесков при создании соединений. Если много клиентов одновременно ждут несколько свободных серверных соединений, PgDoorman ограничивает параллельное создание новых соединений (
scaling_max_parallel_creates) и передаёт вернувшееся соединение самому старому ожидающему клиенту. - Предсказуемая задержка выдачи соединения. Ожидающие клиенты обслуживаются по FIFO. PgDoorman заранее заменяет серверные соединения перед истечением
server_lifetime, чтобы ротация поколения не превращалась во всплеск задержки при выдаче соединения. - Быстрое обнаружение обрыва PostgreSQL. Если серверное соединение пропадает во время транзакции, PgDoorman отслеживает это параллельно чтению клиента и возвращает SQLSTATE
08006, не дожидаясь системного TCP keepalive. - Операционные инструменты в бинаре. Конфиг пишется в YAML или TOML, длительности задаются как
30sили5m,pg_doorman generate --host ...собирает стартовый конфиг из существующего PostgreSQL,pg_doorman -tпроверяет конфиг без запуска сервера, а/metricsдоступен без отдельного экспортёра.
Сравнение
| Функция | PgDoorman | PgBouncer | Odyssey |
|---|---|---|---|
| Общий пул соединений для всех рабочих потоков | Да | Нет, один рабочий поток | Рабочие процессы с отдельными пулами |
| Prepared statements в transaction pooling | Да | Да, с версии 1.21 | Да, через pool_reserve_prepared_statement |
Кеш анонимного Parse для горячих параметризованных запросов | Да, переиспользуется между клиентами пула | Нет, только именованные | Нет, только именованные |
| Общий лимит серверных соединений к базе | Да, с вытеснением свободных соединений | Нет | Нет |
| Переключение на резервный узел через Patroni | Да, встроено | Нет | Нет |
| Опережающая замена стареющих серверных соединений | Да | Нет | Нет |
| Обрыв серверного соединения во время транзакции | Да, возвращает 08006 без ожидания TCP keepalive | Нет, ждёт TCP keepalive | Нет, ждёт TCP keepalive |
| Горячая замена процесса с переносом свободных сессий | Да, через SCM_RIGHTS; TLS-состояние при сборке tls-migration | Нет, старые сессии остаются в старом процессе | Нет, старые сессии остаются в старом процессе |
| TLS-соединение от пулера к PostgreSQL | Да, 5 режимов и reload по SIGHUP | Да, server_tls_* и reload по RELOAD | Нет |
| SCRAM passthrough без открытого пароля в конфиге | Да, извлекает ClientKey из SCRAM proof | Да, зашифрованный SCRAM secret через auth_query/userlist.txt, с 1.14 | Да |
| JWT-аутентификация (RSA-SHA256) | Да | Нет | Нет |
PAM, pg_hba.conf и auth_query | Да | Да | Да |
| LDAP-аутентификация | Нет | Да, с версии 1.25 | Да |
| Формат конфигурации | YAML или TOML | INI | Собственный формат |
| Структурированные JSON-логи | Да | Нет | Да, log_format "json" |
| Перцентили задержек p50/p90/p95/p99 | Да, через встроенный /metrics | Нет, только средние значения | Да, через отдельный экспортёр на Go |
| Диагностическая web-консоль | Да, встроенная | Нет, только admin-консоль через psql | Нет, только admin-консоль через psql |
| Проверка конфига без запуска сервера | Да, -t | Нет | Нет |
| Генерация начального конфига из PostgreSQL | Да, generate --host | Нет | Нет |
| HTTP-эндпоинт Prometheus | Встроенный /metrics | Отдельный экспортёр | Отдельный экспортёр на Go |
Бенчмарки
AWS Fargate (16 vCPU), pool size 40, pgbench 30 с на тест:
| Сценарий | vs PgBouncer | vs Odyssey |
|---|---|---|
| Extended protocol, 500 клиентов + SSL | ×3.5 | +61% |
| Prepared statements, 500 клиентов + SSL | ×4.0 | +5% |
| Simple protocol, 10 000 клиентов | ×2.8 | +20% |
| Extended + SSL + reconnect, 500 клиентов | +96% | ~0% |
Быстрый старт
Установите пакет через ваш дистрибутив:
# Ubuntu / Debian
sudo add-apt-repository ppa:vadv/pg-doorman
sudo apt update
sudo apt install pg-doorman
# Fedora / RHEL family
sudo dnf copr enable @pg-doorman/pg-doorman
sudo dnf install pg_doorman
Дистрибутивные пакеты и Docker-образ собраны без фич tls-migration и pam. Матрица TLS-фич и инструкции по сборке — в Установке.
Либо запустите через Docker:
docker run -p 6432:6432 \
-v $(pwd)/pg_doorman.yaml:/etc/pg_doorman/pg_doorman.yaml \
ghcr.io/ozontech/pg_doorman \
pg_doorman /etc/pg_doorman/pg_doorman.yaml
Минимальный конфиг (pg_doorman.yaml):
general:
host: "0.0.0.0"
port: 6432
admin_username: "admin"
admin_password: "change_me"
pools:
mydb:
server_host: "127.0.0.1"
server_port: 5432
pool_mode: "transaction"
users:
- username: "app"
password: "md5..." # хеш из pg_shadow / pg_authid
pool_size: 40
server_username и server_password намеренно не указаны: PgDoorman использует MD5-хеш клиента или SCRAM ClientKey для аутентификации к PostgreSQL. В конфиге нет паролей в открытом виде.
Руководство по установке → · Справочник по конфигурации →
Куда дальше
- Впервые видите PgDoorman? Начните с Обзора, затем Установка и Базовое использование.
- Мигрируете с PgBouncer или Odyssey? Прочитайте Сравнение и Аутентификацию.
- Используете Patroni? См. Fallback через Patroni и
patroni_proxy. - Готовитесь к промышленной эксплуатации? Прочитайте Пул под нагрузкой и Координатор пулов.
- Эксплуатируете PgDoorman? См. Горячую замену процесса, Сигналы, Диагностику проблем.