Compression with permessage-deflateСжатие через permessage-deflate
What permessage-deflate isЧто такое permessage-deflate
permessage-deflate (RFC 7692) is the standard WebSocket extension that compresses each message with DEFLATE — the same algorithm behind gzip. For repetitive text payloads like JSON it can cut bytes on the wire by 70–90%, which matters on mobile networks and high-fan-out broadcasts. It is negotiated once during the handshake and is then transparent: your send() and onmessage code does not change.
permessage-deflate (RFC 7692) — стандартное расширение WebSocket, сжимающее каждое сообщение алгоритмом DEFLATE — тем же, что и за gzip. Для повторяющихся текстовых данных вроде JSON оно сокращает объём «на проводе» на 70–90%, что важно в мобильных сетях и при широковещании на много клиентов. Оно согласуется один раз при рукопожатии и далее прозрачно: ваш код send() и onmessage не меняется.
Negotiation in the handshakeСогласование при рукопожатии
The client offers the extension via a header; the server accepts it (optionally with parameters) or stays silent to decline. Browsers offer it automatically, so most of the control lives on the server.
// client → server (offer) Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits // server → client (accept, here without context takeover) Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover
Клиент предлагает расширение заголовком; сервер принимает его (по желанию с параметрами) или молчит, чтобы отклонить. Браузеры предлагают его автоматически, поэтому почти всё управление — на сервере.
// клиент → сервер (предложение) Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits // сервер → клиент (принятие, здесь без context takeover) Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover
Enabling it in Node.jsВключение в Node.js
The ws library exposes the extension through the perMessageDeflate option. It is off by default in recent versions because the defaults can be memory-hungry — so configure it deliberately:
import { WebSocketServer } from "ws"; const wss = new WebSocketServer({ port: 8080, perMessageDeflate: { threshold: 1024, // skip compressing messages < 1 KiB serverNoContextTakeover: true, // reset dictionary per message clientNoContextTakeover: true, concurrencyLimit: 10, // limit parallel zlib jobs zlibDeflateOptions: { level: 6, memLevel: 7 } } });
The threshold is the single most useful knob: tiny frames cost more in CPU and framing overhead than they save, so don't compress them at all.
Библиотека ws предоставляет расширение через опцию perMessageDeflate. В свежих версиях оно выключено по умолчанию, потому что настройки по умолчанию могут быть прожорливы к памяти — поэтому конфигурируйте осознанно:
import { WebSocketServer } from "ws"; const wss = new WebSocketServer({ port: 8080, perMessageDeflate: { threshold: 1024, // не сжимать сообщения < 1 КиБ serverNoContextTakeover: true, // сбрасывать словарь на каждое сообщение clientNoContextTakeover: true, concurrencyLimit: 10, // предел параллельных задач zlib zlibDeflateOptions: { level: 6, memLevel: 7 } } });
Параметр threshold — самая полезная «ручка»: крошечные кадры стоят дороже в CPU и накладных, чем экономят, поэтому их не сжимают вовсе.
Context takeover: the memory trade-offContext takeover: компромисс по памяти
“Context takeover” means the DEFLATE dictionary is kept between messages, so repeated patterns compress better over time. The catch: each connection then holds a zlib context — easily tens of kilobytes — and with tens of thousands of sockets that adds up to real RAM.
- Context takeover on — best compression ratio, highest memory. Good for a few long-lived, chatty connections.
- no_context_takeover — the dictionary resets each message; slightly worse ratio, dramatically less memory. Usually the right call at scale.
«Context takeover» означает, что словарь DEFLATE сохраняется между сообщениями, поэтому повторяющиеся паттерны со временем сжимаются лучше. Подвох: каждое соединение тогда держит контекст zlib — легко десятки килобайт — и при десятках тысяч сокетов это превращается в ощутимую RAM.
- Context takeover включён — лучший коэффициент сжатия, максимум памяти. Хорошо для немногих долгоживущих «болтливых» соединений.
- no_context_takeover — словарь сбрасывается на каждое сообщение; чуть хуже коэффициент, кардинально меньше памяти. Обычно правильный выбор при масштабе.
When to enable itКогда включать
- Do enable for large, repetitive text (JSON, logs, markup) over constrained networks.
- Don't bother for already-compressed payloads — images, video, gzip blobs or tightly packed binary frames. You'll burn CPU for almost no gain.
- Measure both bytes saved and CPU/memory before and after. Compression is a trade, not a free win.
If you're broadcasting the same payload to many clients, also see scaling WebSockets — compressing once per unique message rather than per recipient is a bigger lever than the deflate level.
- Включайте для крупного повторяющегося текста (JSON, логи, разметка) в ограниченных сетях.
- Не стоит для уже сжатых данных — изображения, видео, gzip-блобы или плотно упакованные бинарные кадры. Сожжёте CPU почти без выгоды.
- Измеряйте и сэкономленные байты, и CPU/память до и после. Сжатие — это компромисс, а не бесплатный выигрыш.
Если вы рассылаете одни и те же данные многим клиентам, см. также масштабирование WebSocket — сжать один раз на уникальное сообщение, а не на каждого получателя, важнее, чем уровень deflate.