Режимы пула
pg_doorman поддерживает два режима пула: transaction и session. Режим задаётся для пула, при необходимости переопределяется для конкретного пользователя.
Режима statement нет. В statement-пулинге серверное соединение ротируется после каждого оператора — это вынуждает клиентов отказаться от мульти-statement транзакций и полностью ломает протокол prepared statements. Все оптимизации pg_doorman (кеш prepared statements, прямая передача, строгий FIFO-планировщик) рассчитаны на транзакционный режим. PgBouncer оставляет statement для обратной совместимости; Odyssey его не реализует.
Транзакционный режим (рекомендуется)
pools:
mydb:
pool_mode: "transaction"
Backend-соединение удерживается в течение транзакции и возвращается в пул при COMMIT, ROLLBACK или неявном завершении.
Именно этот режим даёт ту самую эффективность соединений, ради которой существует pg_doorman: pool_size равный 40 обслуживает тысячи клиентов, пока транзакции остаются короткими.
Что работает в транзакционном режиме (там, где большинство пулеров проваливаются):
- Prepared statements. pg_doorman кеширует их в рамках пула, переименовывает имена statement между backend-соединениями и прозрачно повторно готовит запрос. Драйверы, привязанные к
unnamed-statement (Go pgx, .NET Npgsql, Python asyncpg), работают без настройки. - Pipelined-батчи и асинхронный поток
Flush. - Cancel-запросы поверх TLS.
LISTEN/NOTIFY— но только внутри транзакции.LISTENпослеCOMMITотпускает backend, и любые уведомления, доставленные ему после, попадут к тому клиенту, который следующим заберёт это соединение, а не к исходномуLISTEN-еру. PgBouncer ведёт себя так же; если вам нуженLISTENчерез несколько транзакций, для этого клиента используйте сессионный режим.
Что в транзакционном режиме не работает:
SETиRESETвне транзакции. Используйте сессионный режим для клиентов, опирающихся на изменение GUC уровня сессии (SET TIME ZONE,SET search_pathодин раз на соединение).- advisory-блокировки, удерживаемые между транзакциями. Используйте сессионный режим.
- курсоры, удерживаемые вне транзакций (
WITH HOLD). Используйте сессионный режим. SET LOCALработает как ожидается — он ограничен транзакцией.
Сессионный режим
pools:
legacy_app:
pool_mode: "session"
Backend-соединение удерживается в течение клиентской сессии. В пул возвращается только при отключении клиента.
Используйте, когда:
- Приложение использует состояние уровня сессии (
SET search_path,SET TIME ZONE). - Приложение использует курсоры WITH HOLD.
- Приложение использует advisory-блокировки между транзакциями.
- Вы переезжаете с немодифицированного PgBouncer-развёртывания, работавшего в сессионном режиме, и хотите подмену один-в-один.
В сессионном режиме pool_size фактически равен максимальному числу одновременных клиентов. Размер пула подбирается так, чтобы соответствовать max_connections PostgreSQL минус резервы.
Переопределение для конкретного пользователя
Режим пула можно переопределить для конкретного пользователя:
pools:
mydb:
pool_mode: "transaction"
users:
- username: "app"
password: "md5..."
pool_size: 40
- username: "admin_tools"
password: "md5..."
pool_size: 4
pool_mode: "session"
Полезно, когда одному пользователю (инструменты эксплуатации, миграции) нужна семантика сессии, а основное приложение остаётся в транзакционном режиме.
Очистка при возврате в пул
Очистка в транзакционном режиме отслеживает мутации, а не выполняется безусловно. pg_doorman следит за каждой транзакцией на предмет SET, PREPARE и DECLARE CURSOR, и только когда backend уходит в пул с одним из этих флагов, отправляет соответственно RESET ALL, DEALLOCATE ALL или CLOSE ALL. Транзакция только на чтение пропускает очистку целиком — это измеримый выигрыш на горячих OLTP-путях.
Что сбрасывается, когда сработал флаг:
- Флаг
SET→RESET ALLсбрасывает session-level GUCs и неявно вызываетpg_advisory_unlock_all. - Флаг
PREPARE→DEALLOCATE ALLудаляет PostgreSQL-side prepared statements, которые драйвер именовал явно. Собственный кеш prepared statements pg_doorman сохраняется после сброса: он индексируется текстом запроса, а не backend-именем. - Флаг
DECLARE CURSOR→CLOSE ALLзакрывает курсоры.
DEALLOCATE ALL и DISCARD ALL со стороны клиента очищают prepared-statement-кеш именно этого клиента (следующий Parse зарегистрируется заново). Pool-level shared cache не затрагивается; у других клиентов их записи сохраняются.
Полностью отключить очистку (ради производительности в жёстко контролируемых развёртываниях):
pools:
mydb:
pool_mode: "transaction"
cleanup_server_connections: false
Делайте так только если уверены, что приложение никогда не оставляет состояние сессии. Очистка по умолчанию уже дёшева на транзакциях без мутаций, поэтому отключение редко стоит риска.
Справочник
- Параметр
pool_mode: Настройки пула. cleanup_server_connections: Настройки пула.- Размер пула: Координатор пулов, Пул под нагрузкой.