PostgreSQL

Настройка соединений и connection pooling

Heroku PostgreSQL: каждый клиент получает PostgreSQL с max_connections = 25. Кажется мало? Heroku предоставляет PgBouncer из коробки. 1000 Rails workers → PgBouncer → 25 PostgreSQL соединений. Средний Rails app с 100K RPM прекрасно работает на 25 соединениях. Понимание connection overhead и pooling - разница между '$50/месяц plan' и '$500/месяц plan'.

  • **Heroku** - max_connections=25 на free tier + PgBouncer: 95% клиентов не замечают ограничение, потому что connection pooling решает проблему масштабирования
  • **Yandex Cloud** - Odyssey в production для всех managed PostgreSQL: многопоточный pooler обрабатывает 500K+ соединений/сек на флагманских инстансах
  • **Supabase** - PgBouncer transaction mode для всех проектов: 5000 клиентских соединений → 20 PostgreSQL connections per project, auto-scaling пула

max_connections: сколько соединений выдержит PostgreSQL

**max_connections** ограничивает количество одновременных backend процессов. Дефолт: 100. Кажется мало, но каждое соединение - отдельный процесс ОС с выделенной памятью. При 1000 соединениях PostgreSQL тратит огромные ресурсы на scheduling и IPC, производительность падает быстрее чем растёт с числом соединений.

**max_connections > 500 без connection pooler - антипаттерн.** 500 backend процессов = ~3-5 ГБ RAM только на процессы + overhead планировщика ОС. Lock table размер = O(max_connections^2). Выше 500 производительность падает. Правильный подход: max_connections = 100-200 + PgBouncer.

Приложение жалуется 'too many connections'. max_connections = 100. Что правильнее сделать?

Connection overhead: цена каждого соединения

**Каждое PostgreSQL соединение - полноценный процесс ОС** с fork(), выделением памяти, инициализацией структур. Установка нового соединения: 50-100 мс (TCP handshake + auth + fork + init). При 10K запросов/сек с новым соединением на каждый - latency хуже чем сам запрос.

**pg_sleep и pgbench:** benchmark с 1000 соединениями против 50 (через pooler) обычно показывает 2-4x разницу в TPS в пользу pooler, даже при той же нагрузке. OLTP workload с маленькими транзакциями особенно чувствителен к connection overhead.

Benchmark: 100 соединений × 1 транзакция/сек = 100 TPS. Ожидаемый результат при 1000 соединениях × 0.1 транзакции/сек?

PgBouncer: три режима пула

**PgBouncer** мультиплексирует клиентские соединения через небольшой пул реальных PostgreSQL соединений. Три режима пула: session (1 соединение PG на сессию клиента), transaction (1 соединение PG на транзакцию), statement (1 соединение PG на запрос). Каждый режим - компромисс между совместимостью и эффективностью.

РежимПул эффективностьСовместимостьДля чего
sessionНизкаяПолнаяLegacy apps, LISTEN/NOTIFY, advisory locks
transactionВысокаяБольшинство случаевREST APIs, microservices, ORM
statementМаксимальнаяТолько autocommitSimple queries без транзакций

Приложение использует PgBouncer transaction mode. Разработчик добавляет `LISTEN events` для real-time уведомлений. Что произойдёт?

Odyssey: продвинутый pooler от Яндекса

**Odyssey** - connection pooler, разработанный Яндексом для Yandex Cloud. Многопоточный (в отличие от однопоточного PgBouncer), лучше использует многоядерные CPU. Поддерживает routing по правилам, SSL offloading, детальные метрики. Использует тот же wire protocol что и PgBouncer - совместим с любым PostgreSQL клиентом.

**PgBouncer vs Odyssey:** PgBouncer - battle-tested, проще в настройке, широко поддерживается (AWS RDS, Heroku). Odyssey - лучше на многоядерных серверах (>4 cores), более гибкий routing, активно развивается Яндексом. Для большинства команд PgBouncer достаточен.

PgBouncer обрабатывает 100K запросов/сек. CPU: 1 ядро на 100%. Переключение на Odyssey поможет?

Prepared Statements через PgBouncer

**Prepared statements** в PostgreSQL кешируют план запроса на уровне сессии. В PgBouncer transaction mode соединение переключается между клиентами - prepared statement, созданный одним клиентом, недоступен другому. Это ломает PREPARE/EXECUTE workflow. Три решения: session mode, application-level caching, или новый Prepared Statement protocol.

**PgBouncer 1.23+** (2024) добавил поддержку prepared statements в transaction mode через statement-level caching. Pooler перехватывает PREPARE/EXECUTE и маппирует их между соединениями. Это устраняет главное ограничение transaction mode.

Больше max_connections = лучше производительность PostgreSQL

Оптимальный max_connections для большинства серверов: 100-300. Больше - накладные расходы на scheduling и lock table превышают пользу. Для масштабирования числа клиентов нужен connection pooler

Lock table PostgreSQL хранит информацию о блокировках для каждой пары (процесс, объект). Размер растёт как O(max_connections). При 1000 соединениях - 1M записей. Scheduling ОС переключает контекст между сотнями процессов. Pgbench показывает пик TPS при 50-200 соединениях, затем деградацию

TypeORM использует prepared statements (named statements) и PgBouncer transaction mode. Разработчик видит 'prepared statement X does not exist'. Быстрый workaround?

Итоги

  • **max_connections = 100-300** - практический максимум без pooler. Больше - overhead scheduling и lock table превышает пользу
  • **PgBouncer transaction mode** - оптимально для REST APIs и ORMs. Ломает: PREPARE, SET вне транзакции, advisory locks, LISTEN
  • **Odyssey** - многопоточный, лучше на многоядерных серверах, routing rules, но PgBouncer проще и battle-tested
  • **Prepared statements через pooler**: server_reset_query=DISCARD ALL + клиент без named statements, или PgBouncer 1.23+ с встроенным statement cache
  • Мониторинг: idle in transaction > 0 = проблема. Много idle соединений = лишние backend процессы, освободить через disconnect

Связанные темы

Connection pooling тесно связан с HA и архитектурой приложения:

  • High Availability — PgBouncer в HA-стеке: буферизует соединения при failover, клиенты видят задержку вместо ошибки
  • Архитектура PostgreSQL — Модель process-per-connection - причина почему нужен pooler. Потоки были бы эффективнее, но Postgres использует процессы
  • Мониторинг — pg_stat_activity - главный инструмент мониторинга соединений: state, wait_event, idle_in_transaction

Вопросы для размышления

  • 10 микросервисов, каждый с пулом max=20. Без PgBouncer: 200 PostgreSQL соединений. С PgBouncer (pool_size=10 per service): 10 соединений. Как PgBouncer мультиплексирует 200 клиентских соединений через 10 реальных? Что происходит если все 200 хотят запрос одновременно?
  • Приложение использует Django с psycopg2. Кто-то добавил `connection.cursor(); cursor.execute('SET search_path=...')`. PgBouncer transaction mode. Через час - беспорядочные ошибки 'relation not found'. Почему?
  • PgBouncer показывает wait_time растёт. pool_size = 20, client_conn = 500. 80% соединений idle. Что происходит и как исправить?

Связанные уроки

  • bt-24-connection-pooling
Настройка соединений и connection pooling

0

1

Войти