Пулы соединений и latency
Пулы соединений и latency
1) Зачем нужны пулы
Соединения стоят дорого (TCP/TLS handshakes, аутентификация, warm-up). Пул позволяет:- Переиспользовать готовые коннекты (keep-alive) → ниже TTFB.
- Контролировать параллелизм и давать backpressure вместо лавины ретраев.
- Уменьшать хвосты p95/p99 за счет правильного размера и таймаутов.
Ключевые риски: очереди ожидания в пуле, head-of-line blocking, контеншен за коннекты и шторм ретраев.
2) База математики: как считать размер пула
Используем закон Литтла: `L = λ × W`. Для пула это значит:- `λ` — средний поток запросов (RPS).
- `W` — средняя занятость соединения на запрос (service time, включая сетевую латентность и работу удаленного сервиса).
- Минимальный размер пула: `N_min ≈ λ × W`.
- Добавьте запас под вариации и p99: headroom 20–50%.
- Пример: 300 RPS, средний hold-time 40 ms → `N_min = 300 × 0.04 = 12`. С запасом 50% → 18 коннектов.
Если хвосты велики: учитывайте `W_p95` или `W_p99` для критичных путей — пулы растут.
3) Общие принципы проектирования
1. Короткий путь данных: reuse (keep-alive, HTTP/2/3 мультиплексирование).
2. Ограничение параллелизма: лучше отказать быстро (429/503) чем зажарить бэкенд.
3. Таймауты > ретраи: выставляйте малые таймауты и редкие ретраи с джиттером.
4. Очереди у клиента короче, чем у сервера (быстрый fail-fast).
5. Backpressure: когда пул заполнен — немедленно NACK/ошибка/коллбек «позже».
6. Изоляция пулов по целям: DB, кеш, внешние PSP — свои лимиты.
4) HTTP/1.1 vs HTTP/2/3, keep-alive
HTTP/1.1: один запрос на коннект одновременно (практически); нужен пул с несколькими коннектами на хост.
HTTP/2: мультиплексирование потоков в одном TCP; меньше коннектов, но возможен HOL-blocking на TCP при потере пакетов.
HTTP/3 (QUIC): потоковая независимость поверх UDP — меньше HOL-проблем, быстрее первые байты.
- keep-alive timeout 30–90s (по профилю), лимит запросов на коннект (graceful recycle).
- Предпрогрев (preconnect) при старте воркера.
- Ограничение макс. потоков на HTTP/2 (например, 100–200).
nginx upstream backend {
server app-1:8080;
server app-2:8080;
keepalive 512;
keepalive_requests 1000;
keepalive_timeout 60s;
}
proxy_http_version 1. 1;
proxy_set_header Connection "";
Envoy (HTTP/2 pool):
yaml http2_protocol_options:
max_concurrent_streams: 200 common_http_protocol_options:
idle_timeout: 60s max_connection_duration: 3600s
5) Пулы БД: PgBouncer, HikariCP, драйверы
Цель — ограничить конкурентные транзакции и держать короткие удержания коннекта.
5.1 PgBouncer (PostgreSQL)
Режимы: `session` / `transaction` / `statement`. Для API — чаще transaction.
Важные параметры: `pool_size`, `min_pool_size`, `reserve_pool_size`, `server_idle_timeout`, `query_wait_timeout`.
ini
[databases]
appdb = host=pg-primary port=5432 dbname=appdb
[pgbouncer]
pool_mode = transaction max_client_conn = 5000 default_pool_size = 100 min_pool_size = 20 reserve_pool_size = 20 query_wait_timeout = 500ms server_idle_timeout = 60 server_reset_query = DISCARD ALL
5.2 HikariCP (Java)
Малые, быстрые коннекты, жесткие таймауты.
properties dataSourceClassName=org. postgresql. ds. PGSimpleDataSource maximumPoolSize=30 minimumIdle=5 connectionTimeout=250 validationTimeout=200 idleTimeout=30000 maxLifetime=1800000 leakDetectionThreshold=5000
Правила:
- `maximumPoolSize ≈ RPS × W × headroom`.
- `connectionTimeout` сотни миллисекунд, не секунды.
- Включите leak detection.
5.3 Go / Node / Python — примеры
Go http.Client (reuse + timeouts):go tr:= &http. Transport{
MaxIdleConns: 512,
MaxIdleConnsPerHost: 128,
IdleConnTimeout: 60 time. Second,
TLSHandshakeTimeout: 2 time. Second,
}
c:= &http. Client{
Transport: tr,
Timeout: 2 time. Second ,//general
}
Node.js keep-alive агент:
js const http = require('http');
const agent = new http. Agent({ keepAlive: true, maxSockets: 200, maxFreeSockets: 64, timeout: 60000 });
psycopg / SQLAlchemy (Python):
python engine = create_engine(
url, pool_size=30, max_overflow=10, pool_recycle=1800, pool_pre_ping=True, pool_timeout=0. 25
)
6) Очереди ожидания и tail-latency
Хвосты возникают, когда:- Пул меньше, чем `λ × W` → растет очередь ожидания коннекта.
- Неровность нагрузки (bursts) без буфера и лимитов.
- Долгие запросы занимают коннект и создают HOL.
- Разделяйте пулы по типам запросов (быстрые/медленные).
- Внедрите таймаут ожидания коннекта (client-side). Если истек — быстрый NACK.
- Outlier detection и circuit-breaking на маршрутах (Envoy, HAProxy).
- Квоты на «тяжелые» маршруты, отдельный пул для отчетов/экспортов.
yaml circuit_breakers:
thresholds:
- priority: DEFAULT max_connections: 200 max_pending_requests: 100 max_requests: 1000 max_retries: 2
7) Таймауты и ретраи (правильный порядок)
1. Connect timeout (короткий: 50–250 ms внутри DC).
2. TLS handshake timeout (500–1000 ms вне DC).
3. Request/Read timeout (ближе к SLO маршрута).
4. Retry: максимум 1 раз, только для идемпотентных методов; джиттер + backoff.
5. Budget на ретраи: глобальный лимит в процентах от RPS (например, ≤ 10%).
8) Keep-alive, Nagle, протоколы
Отключите Nagle (TCP_NODELAY) для RPC с малыми сообщениями.
Включайте HTTP keep-alive везде, где возможно.
Следите за TIME_WAIT — tune `reuse`/`recycle` только если понимаете последствия; лучше — reuse коннектов, а не тюнинг ядра.
TLS: используйте session resumption и ALPN.
9) OS/Kernel тюнинг (с осторожностью)
`net.core.somaxconn`, `net.ipv4.ip_local_port_range`, `net.ipv4.tcp_fin_timeout`.
Дескрипторы: `nofile` ≥ 64k на процесс-прокси.
Баланс IRQ, GRO/LRO — по профилю трафика.
Приоритет — профилировать; тюнинг без метрик часто вредит.
10) Наблюдаемость: что мерить
Pool utilization: занято/всего, p50/p95 ожидания коннекта.
In-flight запросы и их hold-time (срезы по маршрутам).
Error budget ретраев: доля повторов.
Connection churn: создание/закрытие в секунду.
TCP/TLS: SYN RTT, handshakes, session reuse.
Для БД: active connections, waiting, long transactions, locks.
Графики: «RPS vs pool wait», «hold-time distribution», «reuse ratio», «circuit trips».
11) Кейс-рецепты
11.1 API-шлюз → backend
HTTP/2 к бэкендам, `max_concurrent_streams = 200`.
Пул 20–40 коннектов на сервис на узел шлюза.
Таймауты: connect 100ms, per-try 300–500ms, общий 1–2s, 1 retry с джиттером.
11.2 Сервис → PostgreSQL через PgBouncer
`pool_mode=transaction`, `default_pool_size` по формуле (RPS×W×1.3).
В приложении `connectionTimeout≤250ms`, короткие транзакции (<100ms).
Тяжелые отчетные запросы — отдельный пул/реплика.
11.3 gRPC внутренний
Один канал (HTTP/2) на целевой хост с лимитом потоков 100–200.
Deadline на RPC по SLO маршрута, ретраи только idempotent.
Сэмплинг трейсов для длинных RPC и метрики hold-time.
12) Чек-лист внедрения (0–30 дней)
0–7 дней
Замерьте `W` (hold-time) на ключевых маршрутах/клиентах.
Рассчитайте `N_min = λ×W` и добавьте 30–50% headroom.
Включите keep-alive и короткие таймауты ожидания коннекта.
8–20 дней
Разделите пулы (быстрые/медленные/внешние).
Введите circuit-breakers и budgets ретраев.
Добавьте дашборды: pool wait p95, reuse ratio, in-flight.
21–30 дней
Прогоны нагрузкой с бурстами, хаос-тест «падение бэкенда».
Оптимизация по хвостам: изоляция тяжелых маршрутов, локальные кэши.
Документируйте формулы и лимиты в runbook’ах.
13) Анти-паттерны
Размер пула «наугад» и отсутствие headroom.
Большие таймауты ожидания коннекта → длинные хвосты вместо быстрых отказов.
Много ретраев без джиттера и идемпотентности → шторм.
Один общий пул на все типы запросов.
Долгие транзакции держат коннект (DB) → starvation остальных.
Отключенный keep-alive или слишком маленькие лимиты idle → churn и рост TTFB.
14) Метрики зрелости
Pool wait p95 в проде < 10% от общего p95 маршрута.
Reuse ratio (>90% для внутренних HTTP; >80% для внешних).
DB txn time p95 < 100–200 ms; доля долгих транзакций < 1%.
Retry rate < 5% (и ≤ budget), ошибки из-за timeouts стабильны и предсказуемы.
Документированный расчет пула для всех критичных клиентов.
15) Заключение
Эффективный connection pooling — это инженерия очередей + дисциплина таймаутов. Измерьте `W`, рассчитайте пул `λ×W` с запасом, включите keep-alive/HTTP2+, отделите медленные пути, держите короткие таймауты и минимальные ретраи с джиттером. Добавьте наблюдаемость «pool wait vs latency» и circuit-breakers — и вы получите низкий TTFB, контролируемый хвост p99 и устойчивость к всплескам без перегрева бэкендов.