Сайт использует сookies для хранения данных. Продолжая использовать сайт, вы даёте согласие на работу с этими файлами.

ОК
💻
Технологии
Опубликовано:
14.08.2026
Обновлено:
14.08.2026

Session pooling и transaction pooling в PgBouncer: разбираемся, что выбрать

Данил Мануйлов

PostgreSQL создаёт отдельный процесс для каждого клиентского подключения. При высокой нагрузке сотни и тысячи прямых соединений быстро исчерпывают ресурсы сервера, приводят к деградации производительности и даже отказам. PgBouncer решает эту проблему, становясь управляемым «шлюзом» между клиентами и базой данных. Однако выбор режима работы этого пула напрямую определяет, насколько корректно и эффективно будет работать приложение.

Введение: зачем PgBouncer и почему важен выбор режима

Проблема управления соединениями в PostgreSQL

Прямое клиентское подключение к PostgreSQL обходится дорого: на каждое создаётся отдельный процесс ОС, выделяется память, устанавливаются сессионные параметры. Если сервис открывает много короткоживущих соединений или держит большое число idle-подключений, сервер базы упирается в лимиты памяти и CPU. Даже мощные инстансы PostgreSQL редко выдерживают больше нескольких сотен активных одновременных сессий без существенного падения пропускной способности.

Добавьте сюда микросервисную архитектуру, serverless-функции или пиковые нагрузки — и проблема становится острой. Решением выступает пул соединений, который принимает клиентские подключения и мультиплексирует их на ограниченный пул серверных соединений с базой.

Что такое пул соединений и какую роль играет PgBouncer

PgBouncer — легковесный connection pooler для PostgreSQL, работающий как прокси. Он открывает заранее заданное количество соединений с сервером и выдаёт их клиентам по требованию. За счёт этого накладные расходы на аутентификацию и установку соединения для клиента минимальны: PgBouncer просто переиспользует готовые серверные подключения.

Ключевая особенность PgBouncer — разные режимы пулинга. Они меняют логику того, как долго серверное соединение удерживается за клиентом, и тем самым определяют компромисс между совместимостью с PostgreSQL и эффективностью утилизации ресурсов.

Два основных режима — session и transaction — и их влияние на архитектуру

PgBouncer поддерживает три режима: session, transaction и statement. На практике решающий выбор почти всегда происходит между session и transaction, так как statement-режим накладывает ещё более жёсткие ограничения и подходит лишь для простейших запросов.

  • Session pooling закрепляет серверное соединение за клиентом на всё время его подключения к PgBouncer. Это самый консервативный и совместимый вариант: любая функциональность PostgreSQL работает без изменений. Цена — худшее масштабирование по числу одновременных клиентов.
  • Transaction pooling выделяет серверное соединение только на время активной транзакции, возвращая его в пул сразу после COMMIT или ROLLBACK. Такой подход позволяет обслужить множество клиентов небольшим числом серверных соединений, но исключает возможности, опирающиеся на сессионное состояние.

От выбора режима зависит, потребуется ли перерабатывать код приложения и как система поведёт себя под нагрузкой.

Session pooling: самый бережный режим

Как работает session pooling: от клиента до сервера

При pool_mode = session клиентское подключение к PgBouncer и серверное соединение с PostgreSQL связываются на весь жизненный цикл. Как только клиент отсоединяется, PgBouncer возвращает серверное соединение в пул, где оно может быть использовано следующим клиентом. Никакой дополнительной логики расцепления посередине работы не происходит.

Это поведение очень близко к прямому подключению к PostgreSQL, за исключением того, что клиенты не конкурируют за установление TCP-соединений и аутентификацию с базой при каждом входе. По сути session pooling — это «вежливый» режим, который не вмешивается в логику работы приложения с базой.

Полная поддержка всех фич PostgreSQL

Session pooling полностью прозрачен для прикладного кода. Можно без ограничений использовать любые средства, которые зависят от сессионного контекста:

  • подготовленные выражения (PREPARE, EXECUTE);
  • временные таблицы;
  • сессионные переменные и настройки через SET;
  • механизмы LISTEN/NOTIFY;
  • консультативные блокировки (advisory locks);
  • любые долгоживущие объекты сессии.

Именно поэтому session pooling часто называют «наиболее вежливым» режимом — он не нарушает никаких ожиданий приложения относительно состояния соединения.

Преимущества и ограничения: когда это лучший выбор

Основное преимущество session pooling — предсказуемость и отсутствие необходимости что-либо менять в коде или конфигурации ORM. Он позволяет уменьшить накладные расходы на установку соединений (особенно если приложение часто переподключается), но не решает фундаментальную проблему масштабирования: количество одновременно обслуживаемых клиентов всё равно не может превышать число серверных соединений в пуле.

Если приложение использует исключительно сессионно-зависимую логику, перейти на transaction pooling без серьёзной переработки невозможно, и session pooling остаётся единственной разумной опцией.

Типичный сценарий: приложения с сессионным состоянием

Session pooling актуален для:

  • корпоративных систем, активно использующих временные таблицы или подготовленные выражения;
  • приложений, завязанных на сессионные переменные для маршрутизации или мультитенантности;
  • legacy-кода, модификация которого нецелесообразна;
  • случаев, когда надёжность и полная совместимость важнее максимальной утилизации ресурсов.

Transaction pooling: максимальная эффективность

Как работает transaction pooling: удержание соединения на транзакцию

При pool_mode = transaction серверное соединение предоставляется клиенту ровно на время выполнения транзакции. Как только транзакция завершается COMMIT или ROLLBACK, PgBouncer немедленно забирает соединение и может передать его другому ожидающему клиенту.

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

Главное преимущество: масштабируемость и утилизация ресурсов

Transaction pooling кардинально улучшает использование ресурсов PostgreSQL. Даже скромный пул серверных соединений способен обслуживать тысячи клиентов, при условии, что их транзакции короткие. База данных не перегружается множеством idle-процессов, а накладные расходы на переключение контекста между транзакциями минимальны.

Именно этот режим чаще всего выбирают для высоконагруженных систем, где критична каждая единица пропускной способности.

Ограничения и несовместимые фичи

Плата за эффективность — потеря всего, что привязано к долговременной сессии. После каждой транзакции PgBouncer очищает сессионное состояние с помощью запроса сброса (обычно DISCARD ALL), чтобы следующая транзакция началась с чистого контекста. В результате становятся недоступны:

  • подготовленные выражения (уничтожаются сбросом);
  • временные таблицы (удаляются);
  • ранее установленные SET-параметры (если нужно, их приходится выставлять заново в начале каждой транзакции);
  • LISTEN/NOTIFY (теряется состояние слушателя между транзакциями);
  • advisory locks (освобождаются при сбросе).

Кроме того, нельзя использовать многостадийные операции, растянутые на несколько последовательных транзакций в рамках одной «сессии» в понимании клиента, например длительные курсоры или интерактивные рабочие процессы, если они опираются на сессионное состояние между запросами.

Типичный сценарий: микросервисы, serverless, высоконагруженные API

Transaction pooling идеален для систем, где:

  • преобладают короткие, атомарные транзакции (REST API, serverless-функции);
  • нет зависимости от сессионных объектов;
  • количество одновременных клиентов велико и динамично меняется;
  • важна максимальная плотность утилизации серверных соединений.

Многие облачные управляемые сервисы PostgreSQL по умолчанию предлагают или советуют именно transaction pooling для современных веб-приложений.

Сравнительная таблица: session vs transaction pooling

Критерий Session pooling Transaction pooling
Совместимость с возможностями PostgreSQL Полная: prepared statements, временные таблицы, LISTEN/NOTIFY, advisory locks, session-переменные. Ограниченная: нельзя использовать сессионно-зависимые фичи без переработки кода.
Масштабируемость и число одновременно обслуживаемых клиентов Ограничена количеством серверных соединений, примерно один к одному. Высокая: множество клиентов обслуживаются небольшим пулом серверных соединений.
Поведение при долгих транзакциях Серверное соединение удерживается на всё время клиентской сессии, включая простой между транзакциями. Соединение удерживается только на время активной транзакции; idle-состояние не блокирует других клиентов.
Поведение при idle-соединениях Каждый idle-клиент резервирует серверное соединение, что может быстро исчерпать пул. Idle-клиенты не занимают серверных соединений вообще.
Простота внедрения Требует только настройку PgBouncer, код приложения менять не нужно. Почти всегда требует аудита кода на использование несовместимых фич, изменения конфигурации ORM.

Как выбрать подходящий режим для вашего проекта

Чек-лист: анализируем потребности приложения

Прежде чем принимать решение, последовательно ответьте на вопросы:

  1. Использует ли приложение подготовленные выражения, временные таблицы, LISTEN/NOTIFY или advisory locks?
  2. Устанавливаются ли в коде сессионные переменные, которые должны сохраняться между запросами?
  3. Применяет ли приложение длительные транзакции с интерактивным ожиданием или многостадийной логикой в рамках одного соединения?
  4. Какое количество одновременных клиентов вы ожидаете, и насколько они перегружают PostgreSQL без пула?
  5. Готовы ли вы перерабатывать код или конфигурацию ORM, чтобы избавиться от сессионных зависимостей?

Если на первые три вопроса ответ «да» — безопасный путь это session pooling. Если ключевое требование — масштабирование тысяч одновременных клиентов при коротких транзакциях, и сессионных зависимостей нет — выбирайте transaction pooling.

Если вы используете ORM (Hibernate, SQLAlchemy, Django ORM)

Многие ORM по умолчанию используют подготовленные выражения, кешируют планы выполнения или оперируют временными таблицами. При переходе на transaction pooling нужно явно отключать эту функциональность или настраивать ORM на режим без использования сессионного состояния. Например, в некоторых ORM можно запретить подготовленные выражения или настроить их удаление после каждой транзакции. Конкретные параметры зависят от выбранного инструмента и должны проверяться в его документации.

Если у вас микросервисная архитектура или serverless

Для микросервисов и serverless-функций, где каждый вызов API инициирует короткую транзакцию без сохранения состояния на сервере, transaction pooling — естественный выбор. Он позволяет держать стабильно низкое число соединений с базой даже при резких скачках трафика.

Особые случаи: служебные запросы, long-polling, отложенные задачи

Long-polling и отложенные задачи (background jobs) могут требовать удержания сессионного контекста. Если воркер периодически считывает очередь и работает с сессионными переменными, безопаснее оставить session pooling. Служебные скрипты, работающие с временными таблицами или слушающие каналы NOTIFY, также потребуют режима, сохраняющего сессию.

Практические рекомендации без магии: когда можно переключиться на transaction pooling

Переход на transaction pooling реален, когда:

  • вы провели аудит кода и убедились, что все операции укладываются в одну транзакцию без зависимости от предыдущего сессионного состояния;
  • вы готовы выставлять требуемые SET-параметры в начале каждой транзакции (или использовать PgBouncer-настройки для их проброса);
  • ORM сконфигурирована так, чтобы не использовать сессионные prepared statements;
  • бизнес-логика не опирается на LISTEN/NOTIFY или advisory locks, либо вы вынесли эти задачи в отдельное подключение с session pooling.

Если все пункты выполнимы, transaction pooling даст серьёзный прирост в эффективности использования ресурсов.

Конфигурация и best practices

Параметр pool_mode и как его изменить

Режим задаётся в файле конфигурации PgBouncer. Можно установить его глобально в секции [pgbouncer]:

  • pool_mode = session (по умолчанию)
  • pool_mode = transaction

Для гибкости параметр переопределяется на уровне конкретной базы данных в секции [databases], что позволяет одновременно использовать разные режимы для разных баз или ролей. Это удобно, когда часть приложений требует полной сессионной совместимости, а другая — максимальной масштабируемости.

Настройка server_reset_query — зачем и как

Когда соединение возвращается в пул, PgBouncer выполняет запрос сброса состояния. Для transaction pooling это особенно критично, потому что серверное соединение могло накопить сессионный мусор от предыдущей транзакции. Обычно устанавливают server_reset_query = DISCARD ALL. Этот запрос очищает все временные таблицы, сбрасывает подготовленные выражения, освобождает блокировки и восстанавливает значения сессионных параметров по умолчанию.

При необходимости можно расширить логику очистки дополнительными запросами, например, DEALLOCATE ALL, но при корректном DISCARD ALL этого, как правило, достаточно. Без правильного сброса состояния одна транзакция может увидеть «мусор» от предыдущей, что ведёт к трудноуловимым ошибкам и нарушению изоляции.

Примеры настройки в боевом окружении

В production-конфигурации обычно оставляют сравнительно небольшой default_pool_size, ограничивающий число серверных соединений на одну базу. Для transaction pooling это число может быть значительно ниже пикового количества клиентов. Для session pooling размер пула, как правило, должен быть близок к ожидаемому максимуму одновременных подключений.

Точные цифры всегда подбираются экспериментально с учётом характеристик железа, настроек самого PostgreSQL и профиля нагрузки. Мониторинг количества ожидающих клиентов и утилизации серверных соединений помогает вовремя скорректировать лимиты. Ориентируйтесь на метрики, а не на универсальные рекомендации.

Чего нельзя делать при переходе на transaction pooling

  • Не переключайте режим без аудита приложения — это гарантированный путь к ошибкам в продакшене.
  • Не забывайте про server_reset_query = DISCARD ALL в конфигурации.
  • Не используйте transaction pooling для обслуживания консольных сессий администраторов или инструментов, требующих долгоживущего интерактивного соединения — лучше выделить отдельный endpoint с session pooling.
  • Не полагайтесь, что подготовленные выражения продолжат работать — они будут уничтожаться после каждой транзакции.

FAQ по выбору режима

Какой режим используется по умолчанию в PgBouncer?

По умолчанию установлен session pooling. Это наиболее безопасный вариант, гарантирующий полную совместимость со всеми возможностями PostgreSQL.

Можно ли одновременно использовать оба режима?

Да, PgBouncer позволяет задать pool_mode отдельно для каждой записи в секции [databases]. Таким образом, разные базы или даже разные роли могут работать в разных режимах. Это особенно полезно, когда часть сервисов требует полной сессионной совместимости, а другая — высокой масштабируемости.

Правда ли, что transaction pooling всегда быстрее?

Не всегда. Если приложение постоянно переустанавливает одно и то же сессионное состояние (например, создаёт временную таблицу для каждого запроса), накладные расходы на очистку и повторное создание могут свести преимущества к минимуму. Эффективность transaction pooling проявляется именно на коротких автономных транзакциях без необходимости хранить состояние между ними.

Что делать, если нужно использовать LISTEN/NOTIFY, но хочется transaction pooling для остальных запросов?

Можно вынести обработку уведомлений в отдельное подключение с session pooling, а основную массу запросов обслуживать через transaction pooling. Такой гибридный подход часто применяется на практике и позволяет совместить преимущества обоих режимов.

Чем опасен неправильный server_reset_query?

Без корректного сброса состояния одна транзакция может увидеть «мусор» от предыдущей — остатки временных таблиц, чужие настройки сессионных переменных, незакрытые блокировки. Это ведёт к трудноуловимым ошибкам, нарушению изоляции и некорректному поведению приложения.

Влияет ли выбор режима на таймауты ожидания?

Настройки query_timeout, client_idle_timeout и другие действуют независимо от режима, но при transaction pooling короткий query_timeout более важен, чтобы долгие транзакции не блокировали серверное соединение для других клиентов.

Как протестировать, выдержит ли приложение переход на transaction pooling?

В тестовом окружении включите transaction pooling с агрессивным DISCARD ALL и прогоните полный набор автоматических и ручных тестов. Обращайте особое внимание на ошибки о несуществующих подготовленных выражениях, пропавших временных таблицах, сброшенных блокировках и неожиданных значениях сессионных переменных. Лучше обнаружить эти проблемы на стенде, чем в продакшене.

Заключение

Выбор между session и transaction pooling — это поиск баланса между совместимостью и масштабируемостью. Если приложение глубоко интегрировано в сессионную модель PostgreSQL и доработка кода невозможна или нецелесообразна, остановитесь на session pooling. Если же главная цель — обслужить множество клиентов небольшим числом серверных соединений и весь код укладывается в рамки атомарных транзакций без сессионных зависимостей, смело выбирайте transaction pooling.

Практический алгоритм прост: проведите аудит зависимостей приложения от сессионного состояния PostgreSQL, проверьте настройки ORM, убедитесь в корректной конфигурации server_reset_query и протестируйте поведение под нагрузкой в окружении, приближенном к боевому. Правильный выбор режима позволит получить максимум от PgBouncer — стабильную работу базы данных и эффективное использование ресурсов без неприятных сюрпризов.

Источники

Это авторская статья, основанная на личном опыте и субъективном взгляде автора. Заметили ошибку или битую ссылку? Сообщите нам: info@codesrc.ru - мы оперативно исправим. Спасибо, что помогаете делать блог лучше.
Следите за нами в соцсетях:

Читайте также