Суть Читай реальные байты WebSocket-frame, broadcast-цикл отправки, процедуру переподключения и блок Nginx — предскажи поведение и выбери исправление, которое senior-инженер делает первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Real-time-баги диагностируются в дампах frame, циклах отправки, процедурах переподключения и конфиге proxy — не в спецификации. Прочитай каждый артефакт, предскажи поведение под нагрузкой и выбери исправление, к которому senior-инженер тянется первым.
Цель
Отрепетируй цикл, который ты гоняешь в каждом WebSocket-инциденте: прочитай байты на проводе или горячий путь, предскажи, где он ломается, и примени самое результативное исправление до добавления серверов или ручек.
Сниппет 1 — frame на проводе
Frame bytes (hex): 89 04 70 69 6E 67
Викторина
Completed
Сервер получил эти байты от клиента. Что это за frame и что сервер обязан сделать?
Heads-up Opcode 0x1 — это text; 0x9 — ping. Младший nibble 0x89 равен 0x9, control-frame, на который слой протокола отвечает pong — он никогда не доходит до приложения как данные.
Heads-up MASK=0 был бы нарушением для client DATA-frame, но главный сигнал здесь — что это ping; учебный момент в обязательном ответе pong. Строгий сервер также отметил бы отсутствие mask.
Heads-up Close — это opcode 0x8; 0x9 — ping. Правильный ответ — pong, а не разрыв: закрытие на ping уронило бы здоровые соединения во время keepalive.
Сниппет 2 — broadcast-цикл
function broadcast(clients, message) { for (const ws of clients) { ws.send(message); // enqueue, returns immediately }}// called every 10 ms with a 100 KB payload, 10k clients
Викторина
Completed
Несколько сотен клиентов на медленных каналах. С этим циклом что происходит под устойчивой нагрузкой и каково самое результативное исправление?
Heads-up send() в этом стиле не блокируется; он буферизует и возвращается. Именно это позволяет очереди одного медленного клиента расти без границ — ограниченная память требует явной проверки high-water mark.
Heads-up Быстрые клиенты опустошаются за миллисекунды и держат почти ничего. Безграничный рост целиком на медленных клиентах, чьи очереди никогда не пустеют.
Heads-up Больший kernel-буфер отодвигает обрыв на пару KB на сокет; мегабайты копятся в application queue. Исправление — ограничить application queue, а не увеличивать kernel-буфер.
Деплой роняет 5 миллионов клиентов разом, и все запускают эту процедуру. Что идёт не так и каково однострочное исправление?
Heads-up Потолок защищает сервер, ограничивая частоту retry; его понижение или повышение не лечит ключевой дефект — что все 5 М клиентов срабатывают в одни и те же моменты.
Heads-up Math.min с 60000 ограничивает значение задолго до любого переполнения; задержки — корректные числа. Дефект в том, что они одинаковы у всех клиентов — нет jitter.
Heads-up Плотный цикл долбил бы сервер куда сильнее. setTimeout с backoff верен; ему просто нужен jitter, чтобы клиенты десинхронизировались.
Upgrade WebSocket проходит успешно, но соединения тихо умирают примерно через минуту бездействия в production. Чего не хватает в этом блоке?
Heads-up Именно эти заголовки и заставляют upgrade работать — их удаление ломает handshake. Недостающая часть — read/send timeout, который управляет idle long-lived соединениями.
Heads-up Схема backend не связана со сбоем по idle-timeout; ws работает через http:// upstream за TLS-терминацией. Смерть на 60 с — это дефолтный proxy_read_timeout.
Heads-up Симптом (смерть через ~60 с тишины) — это хрестоматийный дефолтный proxy_read_timeout Nginx, закрывающий тихие соединения; это пробел в конфиге proxy, а не баг клиента.
Итог
Каждый WebSocket-инцидент читается в артефактах: nibble opcode у frame говорит ping (отвечай pong) против data; безграничный цикл отправки уходит в OOM на медленных клиентах, если high-water mark не ограничит очередь; процедура переподключения без jitter превращает деплой в thundering herd; а блок Nginx без поднятого proxy_read_timeout тихо убивает idle-соединения на 60 с. Диагностируй по артефакту, примени исправление с верха лестницы, потом подтверди под той же нагрузкой.