awesome-everything EN
↑ Обратно к восхождению

Сети и протоколы

WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10M

Суть Три потолка, ограничивающих масштаб WebSocket — RAM на idle-соединение, файловые дескрипторы и пропускная способность NIC — и как HTTP/2 extended CONNECT и тюнинг сжатия сдвигают эти потолки.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Phoenix продемонстрировал 2 миллиона одновременных WebSocket-соединений на одном сервере 64 ГБ. MigratoryData сообщает о 10 миллионах на commodity-железе. Для этого нужно понять три независимых потолка: RAM на idle-соединение, файловые дескрипторы и пропускная способность NIC — и эволюцию протокола, сдвигающую эти потолки.

Три потолка по порядку

Потолок 1 — RAM на idle-соединение. Idle WebSocket-соединение потребляет буферы сокета ядра (send + receive) плюс состояние соединения в фреймворке. При стандартных настройках Linux:

  • Буферы сокета ядра: ~4–8 КБ (rmem/wmem по умолчанию).
  • Состояние соединения в фреймворке: зависит от языка/фреймворка (Node.js: ~10–50 КБ из-за накладных расходов V8; Go: ~2–5 КБ; Rust: ~1–2 КБ).

С permessage-deflate при window_bits=15 по умолчанию: +256 КБ компрессор + 44 КБ декомпрессор = +300 КБ на соединение.

При 10 M соединений × 10 КБ базовых = 100 ГБ RAM минимум. Именно поэтому 2 M Phoenix на 64 ГБ впечатляет — они урезали накладные расходы ниже 32 КБ на соединение.

Потолок 2 — Файловые дескрипторы. Лимит Linux по умолчанию: 1 024 на процесс. Production-серверы поднимают через ulimit -n или LimitNOFILE в systemd. Практический максимум на процесс — около 1 M на современном Linux. При 10 M соединений на сервер нужно 10 M дескрипторов — требует тюнинга ядра (fs.nr_open, fs.file-max).

Потолок 3 — Пропускная способность NIC. Keepalive ping каждые 25 с с payload 125 байт: 10 M соединений × 4 ping/минуту × 125 байт = ~83 МБ/с только ping-трафика на 1 Гбит/с NIC. Насыщение NIC устанавливает практический потолок idle-соединений примерно в 500k–2 M в зависимости от интервала ping и частоты сообщений.

WebSocket в масштабе — ключевые цифры
RAM на idle-соединение (зависит от фреймворка)
2–50 КБ
Накладные расходы permessage-deflate по умолчанию
~300 КБ
Phoenix: соединений на 64 ГБ, один сервер
2 миллиона
MigratoryData: соединений на commodity-железе
10 миллионов
Лимит fd Linux по умолчанию на процесс
1 024 (поднять до 1 М в production)
Практический потолок на один сервер (RAM или NIC)
500k–2M idle-соединений

HTTP/2 extended CONNECT (RFC 8441)

Открытие 100 WebSocket-соединений через HTTP/1.1 Upgrade означает 100 TCP 3-way handshake’ов (~30 мс каждый при RTT 10 мс) плюс 100 TLS-сессий (~20 мс на resume). Каждое TCP-соединение потребляет файловый дескриптор и ~8 КБ буферов ядра.

RFC 8441 определяет extended CONNECT для WebSocket через HTTP/2. Вместо GET + Upgrade клиент отправляет:

HEADERS stream=5
  :method = CONNECT
  :protocol = websocket
  :scheme = https
  :path = /chat
  :authority = example.com
  sec-websocket-protocol = chat

Сервер отвечает 200 OK (не 101). HTTP/2-стрим 5 становится WebSocket-туннелем. Другие HTTP/2-стримы на том же TCP-соединении несут другой трафик одновременно.

Преимущества:

  • 100 WebSocket-соединений разделяют одно TCP-соединение — всего один 3-way handshake.
  • Ноль дополнительных файловых дескрипторов для новых соединений.
  • Тюнинг управления перегрузкой на общем соединении выгоден всем стримам.
  • Суммарная задержка handshake для 100 соединений: ~30 мс вместо ~3 000 мс.

Компромисс: требует HTTP/2 end-to-end (клиент, сервер, все прокси). По состоянию на 2026 год крупные облачные load balancer’ы (AWS ALB, GCP Load Balancer) поддерживают это, но большинство корпоративных прокси и файрволов — нет. Adoption ограничен гиперскейл-операторами.

WebSocket через HTTP/3 (RFC 9220)

HTTP/3 работает поверх QUIC (UDP-based). RFC 9220 определяет WebSocket через HTTP/3 с тем же extended CONNECT и :protocol websocket.

Ключевое отличие от HTTP/2: QUIC-стримы независимы на транспортном уровне — потерянный пакет на стриме 0 не блокирует стрим 4 (нет head-of-line blocking). HTTP/2 поверх TCP всё ещё страдает от HoL на уровне TCP: потерянный TCP-сегмент блокирует все HTTP/2-стримы на соединении.

По состоянию на начало 2026 года ни один крупный браузер или сервер не имеет production-поддержки WebSocket-over-HTTP/3. WebTransport (QUIC-нативный протокол) — предпочтительный выбор для приложений, которым нужна QUIC-семантика: он предлагает двунаправленные стримы плюс ненадёжные датаграммы и имеет ~75% поддержки браузеров (Chrome 120+, Firefox 127+, Safari 17+).

Тюнинг permessage-deflate

Расширение permessage-deflate (RFC 7692) сжимает каждое сообщение DEFLATE. Степень сжатия:

  • 50–90% на текстовых payload (JSON, HTML).
  • Плохо на маленьких сообщениях (< 64 байт) — может расширить.
  • ~0% на уже сжатых данных (изображения, видео, зашифрованные payload).

Затраты памяти на соединение при window_bits=15 по умолчанию:

  • Компрессор: ~256 КБ
  • Декомпрессор: ~44 КБ

При window_bits=12: ~50 КБ суммарно на соединение. Компромисс — более низкая степень сжатия (история 4 КБ против 32 КБ при window_bits=15). Степень сжатия падает с ~70% до ~50% на типичных JSON payload.

Production-стратегии:

  • Полностью отключить для idle-соединений и соединений с маленькими сообщениями или бинарными данными.
  • Установить window_bits=10–12 для деплоёв с ограниченной памятью.
  • Включить context_takeover=false для сброса состояния сжатия на каждом сообщении — жертвует степенью ради меньших накладных расходов на сообщение.
Почему это работает

Почему head-of-line blocking важен именно для WebSocket. WebSocket мультиплексирует все каналы приложения на одно TCP-соединение. Если приложение отправляет независимые каналы данных (игровые объекты A, B, C), TCP-ретрансмит пакета объекта A блокирует всё соединение на ~10–30 мс (типичный таймер ретрансмита). Обновления объектов B и C стопорятся, хотя их пакеты были доставлены. Митигация: использовать несколько WebSocket-соединений (по одному на независимый канал), или перейти на WebTransport/QUIC с независимостью на уровне стримов. Большинство production-приложений терпят этот HoL-риск для интерактивной задержки (< 100 мс) без специальной обработки.

Проследи
1/5

Трассируйте экономию памяти и handshake при переводе 1 000 WebSocket-соединений с HTTP/1.1 на HTTP/2 extended CONNECT.

1
Step 1 of 5
HTTP/1.1: 1 000 соединений. Каждое idle-соединение использует 10 КБ буферов сокета ядра. Сколько суммарная память ядра?
2
Locked
HTTP/1.1: открываем 1 000 соединений по очереди при RTT 10 мс. Сколько суммарная задержка handshake?
3
Locked
HTTP/2 extended CONNECT: 1 000 WebSocket-соединений разделяют одно TCP-соединение. Сколько памяти ядра на буферы сокетов?
4
Locked
HTTP/2: сколько TCP handshake'ов?
5
Locked
Какое ограничение деплоя сдерживает adoption HTTP/2 extended CONNECT?
Викторина

Сервер имеет 100k idle WebSocket-соединений с включённым permessage-deflate при window_bits=15 по умолчанию. Примерная память только на состояние сжатия?

Вспомните перед уходом
  1. 01
    Объясните, почему HTTP/2 extended CONNECT эффективнее поддерживает несколько одновременных WebSocket-соединений, чем открытие отдельных TCP-соединений.
  2. 02
    Какой эффект у server_no_context_takeover при согласовании permessage-deflate и когда его использовать?
  3. 03
    Назовите три независимых потолка, ограничивающих WebSocket-соединения на одном сервере, и практический предел каждого.
Итог

WebSocket-соединения в масштабе упираются в три независимых потолка. RAM первый: каждое idle-соединение использует 2–50 КБ состояния фреймворка и буферов ядра; permessage-deflate при настройках по умолчанию добавляет ещё 300 КБ на соединение, делая 100k соединений потребителями 30 ГБ только на состояние сжатия — большинство production-деплоёв отключают сжатие или снижают window_bits. Файловые дескрипторы вторые: Linux по умолчанию 1 024 на процесс; production-серверы поднимают до 100k–1 M через ulimit -n. Пропускная способность NIC третья: один ping-трафик насыщает 1 Гбит/с NIC при ~1–2 M idle-соединений. HTTP/2 extended CONNECT (RFC 8441) решает потолки TCP и файловых дескрипторов, мультиплексируя 100 WebSocket-соединений через одно TCP — 100× сокращение памяти буферов ядра и задержки handshake — но требует HTTP/2-совместимого стека прокси, пока не распространённого за пределами гиперскейл-деплоёв. WebSocket через HTTP/3 (RFC 9220) добавляет QUIC-независимость стримов, но не имеет production-поддержки браузеров по состоянию на 2026; WebTransport — текущая QUIC-нативная альтернатива.

Связанные уроки
встречается в258
Продолжить восхождение ↑WebSocket в production: прокси, безопасность и распределённая архитектура
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.