Real-Time Backend
Horizontal Scaling Patterns
Twitch транслировал чемпионат мира по League of Legends на 8 миллионов одновременных зрителей. Как масштабировать WebSocket-инфраструктуру до таких цифр - не переписывая код каждый раз, когда нагрузка удваивается?
- Twitch масштабировал до 8 миллионов одновременных зрителей через stateless edge-серверы - каждый узел независим, падение одного не затрагивает остальных
- Discord держит stateless gateway-флот (Elixir) + Cassandra для state, что позволяет добавлять gateway-серверы без координации между ними
- Slack использует Kubernetes HPA для автоматического масштабирования pod'ов во время пиков нагрузки (понедельник утром, крупные анонсы компаний)
- Socket.io cluster adapter через Redis Pub/Sub позволяет запускать произвольное количество pod'ов - emit в комнату достигает всех клиентов независимо от того, на каком pod'е они сидят
Shared Nothing
В архитектуре **Shared Nothing** каждый узел кластера обслуживает только тех клиентов, которых он принял - и не знает ничего о соседних узлах. Никакой общей памяти, никаких общих дисков. Это позволяет добавлять узлы горизонтально без координации и без роста латентности при масштабировании.
Discord использует именно эту модель для своих stateless gateway-серверов: каждый gateway держит WebSocket-соединения и читает состояние из Cassandra/Redis - но ни один gateway не разговаривает с другим напрямую. Падение одного узла не затрагивает остальных.
- Каждый узел независим - нет shared memory, нет shared disk
- Состояние клиента хранится во внешнем слое (Redis, Cassandra, PostgreSQL)
- Любой запрос можно направить на любой узел (load balancer без sticky sessions)
- Масштабирование - добавить узел и включить его в балансировщик
Twitch масштабировал до 8 миллионов одновременных зрителей именно через shared-nothing gateway: каждый edge-сервер принимает RTMP-поток или HLS-сессию автономно, без координации между edge-нодами.
Почему Shared Nothing позволяет добавлять узлы без деградации производительности?
Shared State
Когда разные узлы должны знать о состоянии друг друга - например, какой пользователь подключён к какому серверу - нужен **Shared State**: внешнее хранилище, доступное всем узлам одновременно.
Socket.io решает эту задачу через **cluster adapter**: все pod'ы подписываются на один Redis Pub/Sub канал. Когда клиент на pod A посылает событие в комнату, pod A публикует его в Redis, а Redis рассылает по всем pod'ам - те доставляют сообщение клиентам на своей стороне.
- Redis Pub/Sub - простейший вариант: события рассылаются всем подписчикам
- Redis Hash - хранение mapping «userId → nodeId» для точечной доставки
- Cassandra - для персистентного состояния (история сообщений, профили)
- Shared state создаёт единую точку отказа - нужна кластеризация самого хранилища
Slack использует горизонтальное масштабирование pod'ов на Kubernetes с shared Redis для хранения presence-состояния (кто онлайн, кто в каком канале). Kubernetes HPA автоматически добавляет pod'ы при росте CPU/memory - без изменения кода.
Socket.io cluster adapter использует Redis для того, чтобы...
Gossip Protocol
**Gossip Protocol** - децентрализованный способ распространения информации о состоянии кластера. Каждый узел периодически выбирает случайных соседей и обменивается с ними своим «взглядом» на кластер. Через несколько раундов все узлы сходятся к единому состоянию - без единого координатора.
Cassandra использует gossip для распределения информации о topology: кто жив, кто упал, какой узел отвечает за какой диапазон ключей. Каждые 1 секунду каждый узел gossip'ит с 1-3 соседями. Информация распространяется за O(log N) раундов.
- Нет единой точки отказа - любой узел может упасть без потери работоспособности
- Eventual consistency: информация расходится за O(log N) раундов, не мгновенно
- Масштабируется линейно: добавление узла не увеличивает нагрузку на других
- Используется в: Cassandra, DynamoDB, Riak, Consul, Kubernetes
Gossip - не только для обнаружения отказов. Cassandra через gossip передаёт схему keyspace, token ownership и load информацию. Без gossip каждый узел пришлось бы вручную конфигурировать при изменении кластера.
За сколько раундов gossip-протокол распространяет информацию по кластеру из N узлов?
Scaling Patterns в реальных системах
На практике горизонтальное масштабирование realtime-систем комбинирует три паттерна: stateless gateway (shared nothing), shared broker для координации событий, и consistent hashing для равномерного распределения нагрузки между узлами.
- **Stateless gateway**: все WebSocket-серверы не хранят состояние - клиент может переподключиться к любому узлу
- **Shared broker** (Redis Pub/Sub, Kafka, RabbitMQ): события маршрутизируются через центральную шину между всеми gateway-узлами
- **Consistent hashing ring**: новые соединения и задачи распределяются равномерно - добавление узла перераспределяет только 1/N долю нагрузки
- **Kubernetes HPA**: автоматическое добавление/удаление pod'ов по CPU/memory метрикам, Slack использует этот подход для масштабирования под пики активности
Discord обрабатывает более 4 миллионов одновременных голосовых соединений, комбинируя stateless Voice Gateway (Elixir) + Cassandra для хранения состояния + consistent hashing для маршрутизации голосовых каналов по серверам регионов.
Горизонтальное масштабирование решает любые проблемы производительности - достаточно добавить серверов
Горизонтальное масштабирование работает только для stateless или правильно спроектированных stateful компонентов. Узкое место может оказаться в shared broker, базе данных или сети - добавление gateway-узлов тогда ничего не даёт.
Закон Амдала: если 20% системы не масштабируется горизонтально, максимальное ускорение ограничено 5x независимо от количества добавленных узлов. Перед масштабированием нужно найти фактический bottleneck через profiling и метрики.
При добавлении нового узла в consistent hashing ring с N существующими узлами - сколько трафика перераспределяется?
Итоги
- **Shared Nothing** - узлы независимы, состояние во внешнем хранилище, любой балансировщик без sticky sessions
- **Shared State** (Redis Pub/Sub, Cassandra) - координация событий между узлами через внешнюю шину, не через прямое межузловое общение
- **Gossip Protocol** - децентрализованное распространение topology-информации за O(log N) раундов, без единой точки отказа
- **Consistent Hashing** - добавление узла перераспределяет только 1/(N+1) нагрузки, а не всю
Связанные темы
Горизонтальное масштабирование опирается на несколько фундаментальных паттернов распределённых систем:
- Consistent Hashing — Алгоритм распределения нагрузки по узлам ring'а - основа stateless маршрутизации
- WebSocket Clustering — Практическая реализация shared-nothing gateway для realtime-соединений
- CAP Theorem — Теоретическая основа компромиссов между consistency и availability при горизонтальном масштабировании
Вопросы для размышления
- Ваш WebSocket-сервер хранит room membership в памяти процесса. Что нужно изменить, чтобы запустить 10 инстансов без потери функциональности?
- Gossip обеспечивает eventual consistency - состояние кластера сходится за несколько секунд. В каких сценариях realtime-системы это приемлемо, а в каких критично?
- Consistent hashing ring добавляет узел - часть соединений должна переехать. Как реализовать плавный переезд без обрыва WebSocket-соединений клиентов?