Зачем нужен пул соединений к базе и как выбрать его размер?
- Почему нельзя открывать соединение на каждый запрос?
- Что такое PgBouncer?
- Почему пул на 500 соединений работает хуже, чем на 50?
Установка соединения — TCP, TLS, аутентификация, а в PostgreSQL ещё и новый процесс: миллисекунды и память. Пул держит готовые соединения и ограничивает их число.
- Размер пула базы — небольшой: порядка ядер базы × 2, а не «по числу потоков приложения»
- Больше соединений, чем база может обрабатывать параллельно, — только очереди внутри базы и конкуренция за блокировки
- Сумма пулов всех экземпляров приложения не должна превышать лимит базы — отсюда PgBouncer
А ещё HTTP-клиенты тоже: keep-alive и пул соединений к каждому хосту
Подробно
Цена соединения. Новое соединение к PostgreSQL: TCP-рукопожатие, TLS, аутентификация, запуск отдельного серверного процесса (fork) с собственной памятью — единицы миллисекунд и мегабайты памяти на сервере. Делать это на каждый запрос — удвоить задержку и нагрузку. Пул (HikariCP, pgxpool, SQLAlchemy pool) открывает соединения заранее и раздаёт их запросам на время транзакции.
Почему пул должен быть маленьким. Интуиция «больше соединений — больше параллелизма» неверна. База выполняет параллельно столько, сколько у неё ядер и дисковой очереди. Лишние соединения:
- конкурируют за CPU — переключения контекста;
- конкурируют за блокировки и буферы — растёт время каждого запроса;
- тратят память на сервере (work_mem на каждый сортирующий запрос!).
Пропускная способность при 50 соединениях часто выше, чем при 500, а задержка — ниже. Отправная формула (HikariCP): соединений ≈ ядра базы × 2 + число дисков. Дальше — по нагрузочным тестам. Ожидание свободного соединения в пуле лучше, чем очередь внутри базы: его видно метрикой и им можно управлять (таймаут на получение соединения).
Сумма пулов. 40 экземпляров приложения × пул 20 = 800 соединений, а PostgreSQL комфортно держит сотни. Решения:
- уменьшить пулы экземпляров;
- PgBouncer (Odyssey, pgcat) между приложением и базой: тысячи клиентских соединений мультиплексируются на десятки серверных. Режим transaction pooling — серверное соединение выдаётся на время транзакции; ограничения — сессионное состояние (prepared statements исторически,
SET, advisory locks, временные таблицы); - закон Литтла для проверки: RPS к базе × время транзакции = нужное число одновременно занятых соединений.
Практика:
- держать соединение только на время транзакции; не делать внешние HTTP-вызовы, пока соединение взято, — пул кончится;
- таймауты: получение соединения из пула, запрос (
statement_timeout), простаивающая транзакция (idle_in_transaction_session_timeout); - метрики пула: занято, ожидающих, время ожидания;
- проверка живости соединений и пересоздание после переключения мастера.
HTTP-клиенты — та же история: keep-alive и пул соединений на хост экономят TCP- и TLS-рукопожатия; один клиент на приложение, а не новый на каждый запрос (иначе TIME_WAIT и исчерпание портов).
Пример
Приложение: 12 экземпляров × HikariCP maximumPoolSize=10 → 120 соединений.
База: 16 ядер, SSD → разумно ~32–40 активных.
Сделали: PgBouncer в transaction-режиме, default_pool_size=40;
приложение может держать хоть 1000 клиентских соединений, база видит 40.
Пропускная способность выросла, p99 упал: запросы ждут в пуле, а не дерутся в базе.Что хотят услышать
- Из чего складывается цена соединения.
- Почему пул должен быть маленьким, связь с ядрами базы.
- Сумма пулов экземпляров и PgBouncer, его ограничения.
- Практика: не держать соединение во время внешних вызовов, таймауты, метрики.
Частые ошибки
- Ставить пул по числу потоков приложения (200).
- Брать соединение в начале обработки HTTP-запроса и держать до конца.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Размер пула должен в точности равняться числу потоков приложения
- ✗Чем больше соединений к базе, тем быстрее она обрабатывает запросы
- ✗Новое соединение к PostgreSQL почти ничего не стоит по ресурсам
- ✓Готовые соединения; размер — около ядер базы × 2
Связанные вопросы
- Почему при загрузке сервера 90% время ответа растёт в разы, хотя запас ещё есть? Middle
- Как устанавливается и закрывается TCP-соединение? Middle
- Зачем нужен пул потоков и как выбрать его размер? Middle
- Сервер приложений загружен на 30% по CPU, но задержки растут, а пул соединений к базе занят полностью. Что даст добавление ещё нескольких серверов приложений? Middle
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.