Базы данных

Connection Pooling

GitHub в 2019 году столкнулся с "too many clients" ошибкой во время инцидента: автоматическое масштабирование приложения создало 3000 соединений при лимите в 500. Решение заняло час. После: ProxySQL как обязательный слой перед каждой MySQL БД. Connection pooling - не оптимизация, а обязательный элемент архитектуры.

  • **Supabase**: PgBouncer встроен для всех проектов - serverless функции создают тысячи соединений, пул ограничивает реальное количество к PostgreSQL
  • **GitLab**: PgBouncer перед каждым PostgreSQL кластером - 10,000+ соединений от CI runners к 50 реальным backend соединениям
  • **Shopify**: ProxySQL для MySQL - 10,000 app серверов к 200 реальным MySQL соединениям

Стоимость создания соединения

Создание нового соединения с PostgreSQL - дорогостоящая операция: fork нового процесса (postgres backend), TLS handshake, аутентификация, загрузка pg_hba.conf, инициализация session state. В среднем 5-15ms на соединение. При 1000 req/sec это 5-15ms overhead на каждый запрос - катастрофа.

PostgreSQL максимум по умолчанию: 100 соединений (max_connections). У Shopify 10,000+ application серверов. Без пула: 10,000 прямых соединений = невозможно. PgBouncer решает: 10,000 клиентов -> 50 реальных соединений к PostgreSQL.

PostgreSQL ограничен 200 соединениями. 500 application серверов x 5 минимальных соединений = 2500 нужно. Как решить без увеличения max_connections?

Режимы PgBouncer

PgBouncer - легковесный connection pooler для PostgreSQL. Три режима: Session pooling - соединение с PostgreSQL удерживается всё время сессии клиента. Transaction pooling - соединение с PostgreSQL возвращается в пул после каждой транзакции (наиболее эффективный). Statement pooling - после каждого запроса (не работает с транзакциями).

Transaction pooling несовместим с: session-level settings (SET search_path), prepared statements (per-session), advisory locks, LISTEN/NOTIFY. Supabase (hosted PostgreSQL) предоставляет PgBouncer по умолчанию для всех проектов.

Приложение использует SET search_path = myschema после подключения. Какой режим PgBouncer подходит?

ProxySQL для MySQL

ProxySQL - умный proxy для MySQL с connection pooling, query routing, query rewriting, and failover. В отличие от PgBouncer, ProxySQL может маршрутизировать SELECT на replicas, INSERT/UPDATE на master - автоматически, без изменений в приложении.

ProxySQL поддерживает query multiplexing: несколько легковесных connections от приложения мультиплексируются в меньшее количество соединений к MySQL. При этом видны session-level переменные.

ProxySQL обнаруживает что текущий master MySQL недоступен. Что происходит с pending транзакциями?

Лимиты соединений и мониторинг

Превышение max_connections в PostgreSQL - критическая ошибка: "FATAL: sorry, too many clients already". Приложение не может подключиться. Мониторинг активных соединений и их состояния - обязательная метрика для продакшн PostgreSQL.

pg_stat_activity показывает 50 соединений в состоянии "idle in transaction" по 5 минут каждое. Что это означает?

Trade-offs и выбор стратегии

Нет универсального решения для connection pooling. PgBouncer transaction mode + маленький pool_size - наиболее эффективно, но несовместимо с session-level features. Supabase предоставляет два endpoints: direct (полный PostgreSQL протокол) и pooler (PgBouncer transaction mode).

СценарийРекомендацияПричина
Serverless функции (AWS Lambda, Vercel)PgBouncer transaction mode или Supabase PoolerТысячи холодных соединений без pooler убьют PostgreSQL
Web приложение с постоянными серверамиВстроенный pool (SQLAlchemy, pgx) + PgBouncerДвухуровневый пул: app + pgbouncer
MySQL с read replicasProxySQLАвтоматический routing reads/writes без изменений в коде
Prepared statements критичныPgBouncer session mode или pgx built-in poolTransaction mode несовместим с server-side prepared statements

PgBouncer vs pgpool-II: pgpool-II дополнительно поддерживает load balancing, connection caching, query cache. Но он тяжелее и сложнее в настройке. Большинство продакшн систем используют PgBouncer (simpler, faster) + HAProxy для load balancing.

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

Избыточные соединения к PostgreSQL снижают производительность: каждый backend - отдельный процесс с RAM overhead, context switching между процессами дорог. Оптимальный pool_size ≈ (num_cores * 2) + num_disks.

Пул из 10-50 соединений часто даёт лучшую throughput чем 500. Соединения конкурируют за CPU, а не параллельно работают. PgBouncer с маленьким pool_size + очередь - правильная модель.

Vercel serverless функция подключается к PostgreSQL напрямую. Каждый invocation - новое соединение. 10,000 req/sec. Что произойдёт?

Ключевые идеи

  • **Стоимость соединения**: 5-15ms fork + TLS + auth; pooler создаёт их один раз
  • **PgBouncer transaction mode**: самый эффективный для PostgreSQL, несовместим с SET/session vars/server-side prepared statements
  • **Мониторинг**: idle in transaction > 30s = проблема; connections > 80% max = нужен pooler

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

Connection pooling - часть оптимизации всего стека:

  • Кеширование — Redis как cache снижает количество запросов к БД - меньше нагрузка на connections
  • Мониторинг БД — pg_stat_activity - основной инструмент мониторинга соединений
  • Репликация — ProxySQL маршрутизирует reads на replicas - connection pooling + load balancing вместе

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

  • Serverless архитектура (AWS Lambda) с PostgreSQL без pooler: какую проблему ожидать при 1000 concurrent invocations?
  • Когда pool_size=10 эффективнее чем pool_size=100 для PostgreSQL?
  • Как connection pooler влияет на транзакции и нужно ли что-то менять в коде приложения при добавлении PgBouncer?

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

  • os-03-threads
Connection Pooling

0

1

Войти