Транспорт бэкенда

Connection Pooling и Multiplexing

Instagram в 2012 году переехал с Django ORM на прямые SQL запросы через pgBouncer. Результат: 17x уменьшение числа PostgreSQL соединений при том же трафике. База перестала быть bottleneck. Connection pooling - одна из немногих оптимизаций с гарантированным результатом: установка соединения стоит миллисекунды, запрос - десятки микросекунд.

  • **Supabase** использует PgBouncer в transaction mode для всех managed PostgreSQL инстансов. Без него каждый serverless запрос открывал бы новое соединение - PostgreSQL рухнул бы при 100 concurrent пользователях.
  • **Stripe** использует gRPC для inter-service коммуникации. Один Channel на upstream сервис с встроенным retry и load balancing заменяет сложный connection pool код.
  • **RabbitMQ** в Zalando: prefetch=20 для order processing consumers. При аварии и перезапуске consumer только 20 сообщений могут быть недоставлены - остальные вернутся в очередь.

Стоимость установки соединения

Каждое TCP соединение стоит: 3-way handshake (~RTT), TLS handshake (2 RTT для TLS 1.2, 1 RTT для TLS 1.3), OS ресурсы (socket, буферы ~4KB+4KB). Для PostgreSQL: handshake + auth + startup messages = 3-5 RTT. При 1000 req/s это 1000 соединений в секунду - неприемлемо.

PgBouncer используется большинством крупных PostgreSQL установок: Heroku, Supabase, Neon, Railway. Transaction mode самый эффективный: соединение к БД возвращается в пул после каждой транзакции, не после полного HTTP запроса.

Почему PgBouncer в transaction mode эффективнее, чем session mode?

Connection Pool паттерны

Connection Pool - набор предустановленных соединений, которые переиспользуются. При запросе клиент берёт соединение из пула, использует, возвращает. Ключевые параметры: min idle, max connections, timeout на получение соединения.

Оптимальный размер пула PostgreSQL: (CPU ядер * 2) + effective_spindle_count. Для 8-core машины: 17 соединений. Это не интуитивно, но PostgreSQL не масштабируется линейно с числом соединений - контекстные переключения убивают throughput.

Что произойдёт если не вызвать client.release() после использования соединения из пула?

HTTP/2 Multiplexing

HTTP/1.1: одно соединение = один запрос в момент времени (pipeline сломан на практике). Браузеры открывают 6 соединений на домен. HTTP/2: одно соединение = много параллельных streams. Connection pool для HTTP/2 обычно = 1 соединение.

HTTP/2 multiplexing меняет стратегию connection pool: для HTTP/1.1 нужно много соединений на хост, для HTTP/2 - одно. fetch() в браузере автоматически использует HTTP/2 если сервер поддерживает. Node.js: http2 модуль или undici (HTTP/1.1+HTTP/2).

Почему HTTP/2 не полностью устранил Head-of-Line blocking?

gRPC Channels и AMQP Channels

gRPC Channel - абстракция над HTTP/2 соединением с встроенным пулингом, балансировкой и retry. Один Channel может мультиплексировать тысячи RPC вызовов. AMQP Channel - логический канал внутри одного TCP соединения к RabbitMQ: несколько channels на одном connection.

gRPC channel переиспользует HTTP/2 соединение, мультиплексируя вызовы. Stripe использует gRPC для inter-service коммуникации: один channel на upstream сервис, без connection pool логики в коде.

Нужно ли создавать gRPC channel pool (аналог DB connection pool)?

AMQP Channels и prefetch

AMQP channel.prefetch(N) - механизм backpressure для RabbitMQ consumer: брокер отправляет не более N unacknowledged сообщений. Без prefetch consumer получает все доступные сообщения сразу - OOM при большой очереди.

prefetch=0 (default в старых версиях) означает нет ограничения - при 1M сообщений в очереди consumer получит все сразу и умрёт от OOM. Всегда устанавливать prefetch в production consumer.

Для HTTP/2 нужен большой connection pool как для HTTP/1.1

HTTP/2 мультиплексирует все запросы через одно соединение. Connection pool для HTTP/2 обычно содержит 1 соединение на хост.

HTTP/1.1 пул нужен потому что одно соединение = один запрос. HTTP/2 stream multiplexing устраняет это ограничение. Для gRPC достаточно одного Channel на сервис.

RabbitMQ consumer падает с OOM. В логах: 'Received 500,000 messages'. Что нужно исправить?

Итоги

  • **Connection pool** amortizes стоимость TCP+TLS handshake. Для PostgreSQL оптимальный размер: CPU*2+spindles, не 100+. PgBouncer в transaction mode для максимальной эффективности.
  • **HTTP/2 multiplexing** заменяет connection pool: один Channel на upstream сервис для gRPC, одно соединение для HTTP/2 client.
  • **AMQP prefetch** - обязательный backpressure механизм: без него consumer падает от OOM при большой очереди.

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

Connection pooling связан с backpressure и производительностью транспортного слоя:

  • HTTP/2 и HTTP/3 — HTTP/2 multiplexing фундаментально меняет подход к connection pooling - одно соединение заменяет пул
  • Backpressure и Flow Control — AMQP prefetch - это backpressure механизм на уровне message broker; connection pool bounded size - тоже форма backpressure

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

  • Как определить оптимальный размер connection pool для конкретной БД и workload?
  • Почему создание нового gRPC Channel для каждого запроса - антипаттерн?
  • Как prefetch влияет на порядок обработки сообщений в RabbitMQ?

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

  • net-15-tcp-basics
Connection Pooling и Multiplexing

0

1

Войти