Разработка систем реального времени: WebRTC, SSE, WebSocket
На одном проекте — платформа для онлайн-аукционов — использовали поллинг каждые 2 секунды. Под нагрузкой в 400 одновременных участников аукциона сервер получал 12 000 HTTP-запросов в минуту только ради того, чтобы узнать текущую ставку. 90% ответов возвращали одни и те же данные. Переход на WebSocket снизил нагрузку в 15 раз без изменения UX.
Выбор транспорта под задачу
Три основных механизма реального времени различаются не только технически, но и операционно.
Server-Sent Events работают поверх обычного HTTP/1.1 или HTTP/2. Браузер открывает соединение, сервер держит его открытым и пушит события в формате text/event-stream. Автоматическое переподключение встроено в браузер — не надо писать логику reconnect. Ограничение: только сервер → клиент. Идеально для нотификаций, прогресса долгих задач, live-фидов.
WebSocket — полнодуплексный канал после HTTP Upgrade-рукопожатия. Браузер и сервер обмениваются фреймами в обе стороны. Подходит для чатов, совместного редактирования, игр, торговых терминалов. Требует отдельной обработки reconnect-логики и heartbeat (ping/pong каждые 30 секунд, иначе NAT-таблицы закрывают соединение).
WebRTC — peer-to-peer аудио/видео и данные между браузерами напрямую, минуя сервер. Сервер нужен только для сигнализации (STUN/TURN для обхода NAT). На практике TURN-сервер нужен примерно в 20–30% случаев (корпоративные сети, симметричный NAT), это важно учитывать в инфраструктуре.
Совместное редактирование — самая сложная часть
Collaborative editing — это не просто «кто последний записал, тот и прав». Это Operational Transformation или CRDT (Conflict-free Replicated Data Types).
Без алгоритма слияния коллизий: два пользователя редактируют документ одновременно, оба вставляют текст в позицию 45. Первый сохраняет — позиция сдвигается. Второй сохраняет поверх — его операция применяется к устаревшему состоянию. Текст либо дублируется, либо теряется.
Yjs — наиболее зрелая CRDT-библиотека для браузера. Интегрируется с ProseMirror, TipTap, CodeMirror, Monaco Editor. Провайдеры синхронизации: y-websocket для WebSocket-бэкенда, y-webrtc для P2P без сервера. Persistence — y-indexeddb для локального кэша, y-leveldb на сервере.
На практике: TipTap (ProseMirror-обёртка) + Yjs + y-websocket + Hocuspocus (Node.js WebSocket-сервер для Yjs с PostgreSQL persistence) — стандартный стек для коллаборативного текстового редактора. Hocuspocus из коробки даёт авторизацию на уровне документа, awareness (кто сейчас редактирует), persistence через любой storage.
Проблема, с которой сталкиваются почти все: размер Yjs-документа растёт со временем из-за истории операций. Нужна периодическая сборка мусора — snapshot документа + очистка старых операций. Без этого документ, над которым работали год, может весить 50MB.
Масштабирование WebSocket
Один WebSocket-сервер держит соединения в памяти. Если у вас два инстанса за load balancer — пользователь на инстансе A не получит сообщение, адресованное через инстанс B. Стандартное решение: Redis Pub/Sub как шина между инстансами. Socket.io из коробки поддерживает @socket.io/redis-adapter.
Sticky sessions (IP hash на Nginx) — альтернатива, но она создаёт неравномерное распределение нагрузки и не спасает при падении инстанса.
Для очень больших нагрузок (100k+ одновременных соединений) — отдельный WebSocket gateway (Pushpin, Centrifugo, Ably) перед основным API. Centrifugo написан на Go, держит 1M+ соединений на одном сервере и интегрируется с любым бэкендом через HTTP API.
Типичные проблемы реализации
Memory leak на сервере. Забыли удалить обработчик события при закрытии соединения. На Node.js это видно через process.memoryUsage() — heap растёт ~1MB в час. EventEmitter предупреждает о 10+ слушателях, но не всегда это замечают.
Thundering herd при реконнекте. Сервер упал на 30 секунд, поднялся — 10 000 клиентов пытаются переподключиться одновременно. Exponential backoff с jitter обязателен: delay = Math.min(baseDelay * 2^attempt + random(0, 1000), maxDelay).
Нет индикации потери соединения. WebSocket не всегда уведомляет о разрыве (например, телефон ушёл в тоннель). Heartbeat: клиент отправляет ping раз в 25 секунд, если pong не пришёл через 5 секунд — переподключение.
Процесс работы
Начинаем с выбора транспорта под конкретные сценарии использования — иногда в одном проекте нужны все три: SSE для системных нотификаций, WebSocket для чата, WebRTC для видеозвонков. Проектируем протокол сообщений (обычно JSON с type и payload, реже бинарный протокол через MessagePack). Разрабатываем с полноценным тестированием race conditions — это то, что обычно не покрывается юнит-тестами.
Нагрузочное тестирование с k6 + k6/experimental/websockets: моделируем сценарий 5 000 одновременных соединений с реальным паттерном отправки сообщений.
Сроки
Базовый WebSocket-чат или нотификации поверх существующего API: 1–3 недели. Коллаборативный редактор с Yjs и persistence: 4–8 недель. WebRTC видеозвонки с записью: 6–12 недель, значительная часть — интеграция с медиасервером (mediasoup, Janus).







