Real-Time Backend
Первое real-time приложение
2008 год. Guillermo Rauch, 19 лет, один уикенд - и real-time web перестаёт требовать Flash. Socket.io показывает чат на 100 пользователей прямо в браузере с задержкой 50ms. Индустрия никогда не будет прежней.
- **Slack** использует WebSocket + custom protocol для доставки сообщений 16M+ одновременных пользователей - те же принципы rooms и presence
- **Figma** строит collaborative редактор поверх WebSocket с operational transforms - основа та же: connection state reconciliation при reconnect
- **Discord** обрабатывает 850M+ сообщений в день через Gateway API (WebSocket) с presence для 500M+ пользователей - rooms это guild channels
- **GitHub Copilot** стримит токены через SSE/WebSocket - streaming как частный случай broadcasting одному клиенту
Socket.io: почему не просто WebSocket
2008 год. Guillermo Rauch - 19 лет, живёт в Буэнос-Айресе. За один уикенд пишет Socket.io и показывает живой чат на 100 одновременных пользователей с задержкой меньше 50ms прямо в браузере. Без Flash. Без Java Applets. Просто браузер и JavaScript. Аудитория не верит глазам.
До Socket.io real-time web требовал либо Flash-плагина (Adobe, проприетарный, дырявый), либо Java Applets (Oracle, тяжёлые, ненавидимые пользователями), либо хитрых long-polling трюков которые ломались на каждом втором корпоративном прокси. Socket.io убил всё это разом.
Socket.io - это не просто WebSocket. Это протокол поверх WebSocket (или HTTP long-polling как fallback) с автоматическим переподключением, комнатами, неймспейсами и acknowledgement-механизмом. Engine.io под капотом выбирает лучший транспорт автоматически.
Архитектура Socket.io состоит из двух слоёв. Engine.io отвечает за транспорт - сначала пробует HTTP long-polling, потом апгрейдит до WebSocket если браузер поддерживает. Socket.io Protocol сидит сверху и добавляет мультиплексирование (неймспейсы), rooms и надёжную доставку событий.
Каждое соединение - это объект `socket` с уникальным `socket.id`. Сервер ведёт в памяти Map всех активных сокетов. Это одновременно главная сила и главная засада Socket.io: при рестарте сервера весь этот Map исчезает. Клиенты переподключаются, но сервер о них ничего не знает.
Socket.io v3+ и v2 несовместимы на уровне протокола. Клиент v2 не подключится к серверу v3. Версии должны совпадать. В монорепо - фиксировать точную версию в обоих package.json.
Engine.io начинает соединение с HTTP long-polling и только потом апгрейдит до WebSocket. Зачем такой порядок, а не сразу WebSocket?
Rooms и Namespaces: мультиплексирование на практике
Namespace - это виртуальный сокет-сервер на одном TCP-соединении. Офисный билдинг: одна входная дверь (WebSocket соединение), но внутри разные этажи (неймспейсы) с разными правилами доступа. `/chat`, `/admin`, `/notifications` - три разных неймспейса, три изолированных пространства событий, одно соединение.
Room - это группа внутри неймспейса. Если неймспейсы - этажи, то комнаты - переговорки. Один сокет может быть одновременно в нескольких комнатах. `socket.join('room-42')` добавляет сокет в комнату. `socket.leave('room-42')` убирает. При дисконнекте Socket.io автоматически чистит все комнаты.
Socket.io хранит rooms в памяти сервера. При горизонтальном масштабировании на несколько нод - rooms существуют только на своей ноде. Пользователь на Node A не получит событие из room на Node B. Решение - Socket.io Redis Adapter: rooms синхронизируются через Redis pub/sub.
Это именно то место где большинство чат-приложений ломаются при росте. Один сервер - работает. Два сервера за балансировщиком - половина пользователей не видит сообщения. Redis Adapter решает проблему в 3 строки кода, но его нужно знать заранее, не когда уже горит.
Пользователь подключён к `/chat` namespace и находится в room `general`. Какой вызов отправит сообщение всем в room `general` КРОМЕ самого пользователя?
Broadcasting, presence indicators и connection state reconciliation
Зелёный кружок рядом с аватаром в Slack стоит дорого. Не метафорически - буквально. Slack обрабатывает 16 миллионов одновременных пользователей. Каждый из них периодически шлёт heartbeat со своим online-статусом. Каждый получает обновления о статусе других. Это O(N^2) трафик при наивной реализации. Slack решает его через умный fan-out и topic partitioning.
В Socket.io presence реализуется через комбинацию `connect`/`disconnect` событий и явного управления состоянием. Нельзя просто слушать `connect` - при нестабильном интернете пользователь может переподключиться 10 раз за минуту, генерируя 20 событий. Нужен debounce на уровне бизнес-логики.
Connection state reconciliation - самая важная часть, которую забывают 90% туториалов. Что происходит когда пользователь на 30 секунд потерял интернет и переподключился? Socket.io автоматически reconnect - отлично. Но пока он был offline, в чат пришло 15 сообщений. Они потеряны навсегда если не сделать явную синхронизацию.
Socket.io v4.5+ добавил встроенный `connection state recovery` через `io({ auth: { sessionId } })`. Сервер буферизует события пока клиент переподключается и отдаёт их при reconnect. Но максимальный буфер ограничен - для серьёзных приложений всё равно нужна своя reconciliation логика через БД.
Итог: Socket.io убрал весь cross-browser pain 2008 года. Но добавил свой слой абстракции с которым нужно уметь работать. Rooms и namespaces - мультиплексирование бесплатно. Presence - требует явного debounce. Reconnection reconciliation - требует хранения event ID на клиенте и replay-логики на сервере. Это не сложно - это просто нужно знать.
Socket.io - это просто удобная обёртка над WebSocket, и все данные хранятся в соединении
Socket.io - это отдельный протокол (Engine.io + Socket.io Protocol) поверх WS или HTTP. Данные живут в памяти сервера или в Redis при масштабировании - соединение не хранит state
WebSocket - это просто байтовый канал. Вся логика rooms, namespaces, acknowledgements, reconnection - это Socket.io Protocol, не WebSocket. При рестарте сервера весь in-memory state пропадает. Персистентность - задача приложения.
Пользователь потерял интернет на 20 секунд, Socket.io автоматически переподключился. Какой механизм гарантирует что он увидит сообщения, пришедшие пока он был offline?
Ключевые идеи
- **Socket.io = Engine.io + Socket.io Protocol**: транспорт (WS/polling fallback) отделён от мультиплексирования (namespaces/rooms). Это осознанная архитектура, не случайность
- **Rooms живут в памяти одной ноды**: при горизонтальном масштабировании нужен Redis Adapter - это не опциональная оптимизация, это обязательное требование
- **Presence требует debounce**: mobile сети переключаются между WiFi и 4G, генерируя множество reconnect. 5 секунд grace period на disconnect - минимум
- **Connection reconciliation - ответственность приложения**: lastEventId на клиенте + replay из БД при reconnect. Socket.io v4.5 connection recovery как дополнение, не замена
Связанные темы
Real-time архитектура чата пересекается с несколькими фундаментальными областями:
- Event Loop Node.js — Неблокирующая обработка тысяч сокетов одним потоком
- WebSocket Protocol — Базовый транспорт под Socket.io Engine.io слоем
- Сравнение подходов (SSE/WS/Polling) — Контекст для выбора Socket.io vs других транспортов
- Redis pub/sub — Синхронизация rooms между нодами Socket.io кластера
Вопросы для размышления
- Slack показывает typing indicator когда кто-то набирает текст. Как бы была устроена такая фича на Socket.io - какие events, с какой частотой, и как избежать flood от 50 одновременно печатающих?
- Если Socket.io сервер перезапускается, все in-memory rooms пропадают. Что произойдёт с клиентами - они переподключатся автоматически? Как сервер узнает в какие rooms их вернуть?
- Presence indicator показывает пользователя online если у него открыто несколько вкладок. Что должно произойти когда он закрывает одну вкладку из трёх - статус должен поменяться?
Связанные уроки
- rt-04 — WebSocket RFC 6455 - основа под Socket.io
- rt-05 — Анатомия фреймов нужна чтобы понимать overhead
- node-01-event-loop — Event loop - почему Node.js идеален для Socket.io
- rt-06 — Сравнение подходов: когда выбирать Socket.io vs SSE
- net-06-ip-intro — IP и порты важны для понимания namespace routing
- net-36-websocket