Настройка PgBouncer (connection pooling) для веб-приложения

При пиковых нагрузках приложение теряет соединения с базой и падают запросы. Мы настраиваем пул соединений под ключ: подбираем режим, конфигурируем и проверяем на нагрузке. Сокращаем время отклика и убираем отказы при пиках.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Настройка PgBouncer (connection pooling) для веб-приложения
Средний
от 1 дня до 3 дней
Часто задаваемые вопросы

PgBouncer настройка: когда пул соединений спасает приложение

Приложение открывает соединение к Postgres на каждый запрос и держит его до конца обработки. Пока пользователей десятки, это незаметно. На пиковой нагрузке число одновременных соединений упирается в max_connections, и дальше начинается каскад: новые запросы получают ошибку подключения, очередь на освободившийся слот растёт, время ответа уходит в секунды. Хуже всего то, что сбой приходит именно тогда, когда нагрузка максимальна, — в момент распродажи, отчётной кампании или утреннего всплеска.

Каждое соединение в Postgres — это отдельный процесс с собственной памятью. Держать сотни таких процессов дорого: база тратит ресурсы на переключение между ними вместо обработки запросов, а часть соединений простаивает в ожидании. Увеличение лимита — тупиковый путь: следующий пик снова упрётся в потолок, только теперь отказ будет глубже.

PgBouncer решает это иначе. Приложение подключается к пулу, а не к базе напрямую, и пул переиспользует небольшой набор реальных соединений между сотнями клиентов. В режиме transaction соединение возвращается в пул сразу после завершения транзакции, поэтому несколько десятков бэкендов обслуживают тысячи одновременных запросов. Настройка сводится к трём вещам: выбрать режим пула, задать default_pool_size и max_client_conn, настроить таймауты простоя и корректно развести служебные подключения, которым нужны сессионные состояния.

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

Что даёт правильно настроенный пул соединений

Пул соединений — это не «опция для оптимизации», а разделитель между приложением, которое держит нагрузку, и приложением, которое падает на пике. Когда каждый запрос открывает новое соединение к базе, время отклика растёт нелинейно: на 50 одновременных пользователях ещё терпимо, на 300 — очередь на подключение и таймауты. Ниже — что именно меняется после настройки PgBouncer, с привязкой к метрикам, которые видно в мониторинге.

  • Время отклика перестаёт зависеть от числа пользователей. Соединения переиспользуются из готового пула, и приложение не тратит время на установку нового подключения и аутентификацию на каждый запрос. На графике отклик держится ровной линией там, где раньше шёл ступенчатый рост.
  • Отказы подключения уходят как класс. Ограничение на число соединений со стороны базы больше не упирается в потолок: лишние запросы ждут в очереди пула доли секунды вместо ошибки. Ошибки вида «too many connections» пропадают из логов приложения.
  • Пиковая нагрузка гасится без роста ресурсов. Всплеск трафика разбирается очередью пула, а не лавиной новых подключений к базе. Сервер базы перестаёт уходить в перегрузку по памяти и процессору на каждом пике.
  • Стабильность под нагрузкой становится предсказуемой. Поведение приложения при росте трафика описывается заранее: известно, сколько соединений держим, сколько ждём, что происходит при переполнении. Это убирает «плавающие» отказы, которые тяжело воспроизвести.
  • Производительность приложения растёт без переписывания кода. Пул настраивается на уровне инфраструктуры, логика приложения не меняется. Эффект виден сразу после переключения трафика на пул.
  • Ресурсы сервера расходуются ровнее. База держит фиксированное число рабочих процессов вместо сотен простаивающих соединений — освободившаяся память уходит на кеш и обработку запросов.

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

Режимы работы пула: сравнение подходов

Режим пулинга определяет, на каком уровне PgBouncer возвращает серверное соединение обратно в пул. От этого зависит и пропускная способность под нагрузкой, и совместимость с функциями PostgreSQL: подготовленными запросами, SET, advisory-локами, курсорами. Ниже — рабочие варианты, между которыми реально выбирают при настройке.

Вариант Когда подходит Ограничения
Сессионный режим Долгие сессии, аналитика, приложения с курсорами и временными таблицами Соединение закреплено за клиентом до отключения — пул почти не экономит ресурсы БД
Транзакционный режим Транзакционные веб-приложения, короткие запросы, высокая конкурентность Нельзя держать состояние между транзакциями: SET, advisory-локи, подготовленные запросы требуют явной поддержки
Режим оператора Простые запросы без транзакций, отдельные утилиты Не поддерживает многооператорные транзакции — под веб-нагрузкой почти не применяется
Пул на стороне приложения Смешанные сценарии, когда нужен контроль над жизненным циклом соединения Пул живёт в каждом процессе приложения — при масштабировании число соединений к БД растёт линейно

Для транзакционного режима критично выставить server_reset_query в DISCARD ALL и отключить кэш подготовленных запросов на стороне драйвера либо включить их поддержку в самом PgBouncer. Иначе приложение получит ошибки «prepared statement already exists» под нагрузкой — классический сбой при переходе с сессионного режима.

Смешанные сценарии решают разделением пулов: отдельный инстанс PgBouncer для транзакционного трафика и отдельный — для аналитики и фоновых задач. Это снимает конфликт ограничений и позволяет независимо настраивать pool_mode, max_client_conn и default_pool_size под каждый профиль нагрузки.

Как мы внедряем пул соединений: этапы работ

Внедрение пула соединений начинается не с установки PgBouncer, а с аудита того, как приложение работает с базой сейчас. Смотрим активные соединения под нагрузкой, поведение драйвера и ORM, длительность транзакций, наличие «спящих» сессий, которые держат соединение открытым часами. Часто уже на этом этапе видно, что часть соединений расходуется впустую — например, фоновые задачи держат отдельный пул, не пересекаясь с веб-трафиком.

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

  1. Аудит соединений. Снимаем профиль нагрузки: пиковое число одновременных сессий, распределение по времени, доля простаивающих соединений, самые долгие транзакции.
  2. Проектирование пула. Определяем число групп, лимиты на каждую, режим работы и правила маршрутизации приложения к нужной группе.
  3. Настройка PgBouncer. Задаём размеры пулов, таймауты простоя и удержания, лимиты на клиента, параметры переподключения к базе.
  4. Правка конфигурации приложения. Переводим драйвер на порт пула, убираем собственный пул там, где он дублирует внешний, иначе смысл настройки теряется.
  5. Проверка под нагрузкой. Прогоняем профиль, близкий к боевому: смотрим, что число соединений к базе держится в заданных границах, а время ответа не растёт на пиках.
  6. Передача документации. Отдаём конфигурацию, схему групп, регламент изменения лимитов и инструкцию по диагностике, чтобы команда могла сопровождать пул без нас.

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

Что заказчик получает на руки после настройки

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

  • Конфигурация пула — рабочие pgbouncer.ini и userlist.txt (пароли в хешированном виде) под режим transaction или session, с обоснованием выбора. Параметры max_client_conn, default_pool_size, reserve_pool_size и таймауты выставлены под профиль нагрузки приложения, а не «по умолчанию».
  • Схема подключений — диаграмма: приложение → пул → PostgreSQL, с указанием, какие сервисы идут через PgBouncer, а какие (миграции, аналитика, LISTEN/NOTIFY) — напрямую. Отдельно отмечены точки отказа и поведение при недоступности пула.
  • Политики доступа — какие роли и базы видны через пул, ограничения по адресам, отдельный пользователь для мониторинга. Правила согласованы с текущей моделью прав в базе, чтобы пул не стал обходным путём.
  • Мониторинг и метрики — экспорт метрик пула (занятость, ожидание, отказы) в вашу систему наблюдения, дашборд с ключевыми показателями и пороги для оповещений.
  • Регламенты эксплуатации — что делать при исчерпании пула, как безопасно перезагрузить конфигурацию без разрыва соединений, как откатиться на прямое подключение, если пул деградировал.
  • Документация — краткое описание архитектуры, инструкция по изменению параметров и чек-лист проверки после релиза. Рассчитана на дежурного инженера, а не на того, кто внедрял.
  • План проверки — сценарии нагрузки, которыми подтверждали результат, и зафиксированные значения до и после: число соединений к базе, время отклика под пиком.

Всё это передаём в ваш репозиторий и систему документации, с разбором на созвоне. Опишите задачу — вернёмся с оценкой и планом.

Кейс: как пул соединений убрал отказы при пиковой нагрузке

Клиент — сервис с личным кабинетом и оплатами — приходил с классической картиной: днём приложение держало отклик в норме, но в часы пик, когда совпадали рассылка и наплыв пользователей, часть запросов падала с ошибкой подключения к базе. Логи показывали, что процесс приложения открывал соединение на каждый запрос и держал его до конца транзакции, а база упиралась в лимит одновременных подключений. Каждое новое соединение — это отдельный процесс на стороне базы, и при скачке их число росло быстрее, чем база успевала их обслуживать.

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

Метрики до и после

Показатель До После
Отказы подключения в пик регулярные, до нескольких процентов запросов единичные, в пределах погрешности
Время отклика в пик росло в разы, часть запросов не отвечала стабильно, без провалов
Одновременные подключения к базе упирались в лимит фиксированный потолок с запасом
Поведение при скачке трафика деградация и ошибки плавный рост, без отказов

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

Чем транзакционный режим отличается от сессионного

Сессионный режим закрепляет серверное соединение за клиентом на всё время сессии: пока приложение держит подключение, оно монопольно занимает один бэкенд PostgreSQL. Это единственный режим, где работают все возможности сервера — подготовленные выражения на уровне сессии, временные таблицы, SET и LISTEN/NOTIFY, advisory-блокировки, курсоры вне транзакции. Плата — масштабирование: каждое соединение приложения требует отдельного процесса в базе, и при сотнях одновременных пользователей PostgreSQL упирается в память и переключения контекста задолго до исчерпания процессора.

Транзакционный режим отдаёт серверное соединение клиенту только на время транзакции и возвращает его в пул сразу после COMMIT или ROLLBACK. Один бэкенд обслуживает десятки клиентов последовательно, поэтому пул в сотни приложений обходится парой десятков реальных соединений к базе. Это режим по умолчанию для веб-приложений: типовой запрос живёт миллисекунды, а пауза между запросами одного пользователя может длиться секунды — держать под неё соединение расточительно.

Ограничения транзакционного режима вытекают из того, что клиент не владеет бэкендом между транзакциями. Состояние, привязанное к сессии, теряется: подготовленные выражения, временные таблицы, изменения search_path и параметров через SET, сессионные advisory-блокировки. Курсоры с WITH HOLD и LISTEN/NOTIFY тоже не переносятся. Обходные пути — SET LOCAL внутри транзакции вместо SET, именованные подготовленные выражения с явным управлением, вынос фоновых задач на отдельный пул в сессионном режиме.

Выбор режима — это выбор между совместимостью и плотностью. Если приложение использует сессионное состояние или работает через драйвер с неявными подготовленными выражениями, начинают с сессионного режима и постепенно переводят горячие пути на транзакционный. Смешанная схема нормальна: два пула на разные порты, транзакционный для веб-трафика, сессионный для миграций, аналитики и фоновых обработчиков. Ограничение по числу соединений задаётся на стороне пула, а не приложения, — иначе смысл пулинга теряется.

Технические сомнения перед внедрением пула

Перед включением пула почти каждая команда проходит один и тот же список сомнений: «сломает ли это ORM», «не поедут ли транзакции», «пройдут ли миграции», «что будет с подготовленными запросами». Ниже — разбор по пунктам, без общих слов.

ORM и подготовленные запросы — не конфликтуют?

Режим transaction pooling возвращает соединение в пул после завершения транзакции, а не сессии. Подготовленный запрос, созданный в одном соединении, может выполниться в другом — и сервер вернёт ошибку «prepared statement does not exist». Решается либо переводом драйвера в режим без серверной подготовки (prepareThreshold=0 для JDBC, prepare: false для node-postgres), либо включением max_prepared_statements в самом пуле. Выбор зависит от профиля нагрузки: если подготовка даёт заметный выигрыш на горячих запросах — оставляем её на стороне пула.

Транзакции и блокировки не потеряют целостность?

В transaction pooling все операторы одной транзакции обязаны идти через одно соединение — пул это гарантирует и не отдаёт его другим клиентам до COMMIT или ROLLBACK. Проблема возникает с SET, временными таблицами и advisory-блокировками уровня сессии: они не переживают возврат соединения. Такие места выносим в session pooling точечно или переписываем на транзакционный уровень блокировки.

Миграции и фоновые задачи — отдельный пул?

Миграции, pg_dump, аналитические выгрузки и длительные фоновые задачи держат соединение минутами. Если пустить их через общий пул, они выберут весь лимит и положат веб-трафик. Поэтому под них поднимаем отдельный пул с малым числом соединений и отдельным пользователем — веб-приложение и служебные процессы перестают конкурировать за один и тот же набор.

Как откатиться, если что-то пойдёт не так?

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

Все четыре опасения закрываются на этапе проектирования — до того, как пул попадёт в рабочий контур.

Обсудим вашу задачу и вернёмся с оценкой

Чтобы вернуться с оценкой и планом работ, нам нужен минимум вводных: текущая конфигурация PostgreSQL и PgBouncer (режим пула, лимиты соединений, таймауты), характер нагрузки — сколько одновременных пользователей, есть ли длинные транзакции и тяжёлые отчёты, как приложение держит соединения (пул на стороне приложения или новое соединение на запрос). Этого достаточно, чтобы понять узкое место и предложить режим пула без гадания.

Дальше работаем по шагам:

  • разбираем текущие метрики: число активных и простаивающих соединений, пиковые очереди, отказы по таймауту;
  • определяем целевой режим пула и лимиты под профиль нагрузки, а не «по умолчанию»;
  • проверяем совместимость с драйвером и ORM: prepared statements, транзакции, сессии;
  • готовим конфигурацию и схему переключения без простоя — через второй экземпляр и перевод трафика;
  • прогоняем нагрузочный тест на копии боевой базы и снимаем метрики до/после;
  • выкатываем на прод с планом отката и наблюдаемостью по соединениям и задержкам;
  • фиксируем итоговые параметры и передаём документацию вашей команде.

Что нужно от вас для старта: доступ к конфигурации и метрикам, окно на тестовый прогон и контакт инженера, который знает, как приложение открывает соединения. Остальное — наша зона: конфигурация, схема переключения, тесты и откат.

Опишите задачу — вернёмся с оценкой, планом работ и понятными следующими шагами.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060