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 для равномерного распределения нагрузки между узлами.

  1. **Stateless gateway**: все WebSocket-серверы не хранят состояние - клиент может переподключиться к любому узлу
  2. **Shared broker** (Redis Pub/Sub, Kafka, RabbitMQ): события маршрутизируются через центральную шину между всеми gateway-узлами
  3. **Consistent hashing ring**: новые соединения и задачи распределяются равномерно - добавление узла перераспределяет только 1/N долю нагрузки
  4. **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-соединений клиентов?

Связанные уроки

  • net-65-consistent-hashing
Horizontal Scaling Patterns

0

1

Войти