PgDoorman

Многопоточный пулер соединений для PostgreSQL, написанный на Rust. Совместимая замена для PgBouncer и Odyssey, альтернатива PgCat. Три года в промышленной эксплуатации у Ozon под нагрузками Go (pgx), .NET (Npgsql), Python (asyncpg, SQLAlchemy) и Node.js.

Скачать PgDoorman 3.11.0 · Сравнение · Бенчмарки

Ключевые возможности

Кеш анонимного Parse

В 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 прямого аналога нет.

Подробнее →

Fallback через Patroni

Если 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 доступен без отдельного экспортёра.

Сравнение

ФункцияPgDoormanPgBouncerOdyssey
Общий пул соединений для всех рабочих потоковДаНет, один рабочий потокРабочие процессы с отдельными пулами
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 или TOMLINIСобственный формат
Структурированные 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 PgBouncervs 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. В конфиге нет паролей в открытом виде.

Руководство по установке → · Справочник по конфигурации →

Куда дальше