Пули з'єднань і 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'ax.
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 і стійкість до сплесків без перегріву бекендів.