# Масштабирование: тысячи участников, мульти-нода, TURN-пул, SFU и стримы

Документ описывает **заложенную возможность** масштабирования и вещания на
тысячи зрителей. Важно: в текущем MVP реально работает **mesh-режим (до 8
участников) на одном узле**; конфигурация мульти-ноды/SFU/TURN-пула/стримов
принимается, валидируется, отдаётся через admin API и влияет на выдачу
ICE-серверов и лимитов комнат, но **оркестрация кластера и SFU ещё не
реализована** — тестировать этот функционал пока не на чем (см. §9).
Контракт нужен, чтобы интеграторы и дашборд могли готовить инфраструктуру
заранее.

## 1. Сценарии

| Сценарий | Участники | Медиа-режим |
|---|---|---|
| Личный/групповой звонок | 2–8 (сейчас), 2–100 (с SFU) | `mesh` → `sfu` |
| Вебинар | 1–50 спикеров + 100–10 000 зрителей | `broadcast` (SFU + receive-only зрители) |
| Стрим на внешнюю площадку | зрители на YouTube/VK/сайте | `broadcast` + RTMP/HLS egress |
| Крупная конференция | до нескольких тысяч | SFU-пул + CDN + мульти-нода сигналинга |

## 2. Конфигурация тенанта: `TenantConfig.scale`

```jsonc
{
  "scale": {
    "mediaMode": "mesh",              // mesh | sfu | broadcast
    "maxParticipants": 100,           // дефолт на комнату (2..50000)
    "maxVideoSubscriptions": 12,      // сколько видео-потоков тянет клиент (SFU)
    "signaling": {
      "nodeId": "node-1",
      "redisUrl": "redis://redis:6379",       // пусто = single-node
      "pubsubChannel": "agil:signaling",
      "stickySessions": true,
      "nodes": [
        { "id": "node-1", "url": "https://calls-1.example", "region": "ru-msk", "weight": 100 },
        { "id": "node-2", "url": "https://calls-2.example", "region": "ru-spb", "weight": 100 }
      ]
    },
    "sfu": {
      "provider": "livekit",          // livekit | mediasoup | external
      "url": "wss://sfu.example",
      "apiKeyEnv": "AGIL_SFU_API_KEY",
      "apiSecretEnv": "AGIL_SFU_API_SECRET",
      "region": "ru-msk",
      "maxParticipants": 5000
    },
    "turn": [
      {
        "id": "turn-msk",
        "urls": ["turn:turn-msk.example:3478?transport=udp", "turns:turn-msk.example:5349"],
        "username": "agil",
        "credentialEnv": "AGIL_TURN_MSK_SECRET",
        "region": "ru-msk",
        "weight": 100
      }
    ],
    "loadBalancer": { "strategy": "least-rooms", "healthPath": "/v1/health", "stickyCookie": "agil_node" },
    "webinar": {
      "enabled": false,
      "hostOnlyPublish": true,
      "viewerLimit": 10000,
      "chatMode": "moderated",
      "reactions": true,
      "raiseHandQueue": true
    },
    "streaming": { "enabled": false, "provider": "none", "rtmpUrl": "", "hlsUrl": "", "latencyMode": "low" },
    "cdn": { "enabled": false, "baseUrl": "", "provider": "" }
  }
}
```

Правила:
- `turn[].credentialEnv` — **имя** env-переменной, не секрет; сервер подставляет
  credential в `welcome.iceServers`. STUN добавляется автоматически.
- `signaling.nodes` — реестр узлов для LB/дашборда; в MVP используется только
  для статуса.
- `webinar.enabled` + `mediaMode != mesh` → эффективный режим `broadcast`.
- `limits.maxParticipants` и `scale.maxParticipants` подняты до 50 000, но
  фактический предел задаёт медиаплейн (mesh/SFU) и тариф.

## 3. Admin API (реализовано в контракте MVP)

| Метод | Путь | Назначение |
|---|---|---|
| GET | `/v1/admin/scale` | `ScaleStatusResponse`: конфиг, эффективный mediaMode, реестр узлов, TURN-пул (configured-флаги), SFU/webinar/streaming, ограничения |
| PUT | `/v1/admin/scale` | merge `{scale: Partial<ScaleConfig>}` в конфиг тенанта (SSRF-валидация URL, секреты только как env-имена) |
| GET | `/v1/admin/cluster/status` | `ClusterStatusResponse`: узлы, strategy, redisConfigured, `multiNodeActive` (MVP: `false`) |
| POST | `/v1/admin/scale/turn/test` | проверка доступности TURN-сервера (в MVP — конфигурационная проверка, без реального allocate) |

MCP: `agil_get_scale`, `agil_set_scale`, `agil_get_cluster_status`,
`agil_explain_scaling` (офлайн-гайд). Дашборд: карточка «Масштабирование» на
экране «Интеграции». Редактор: секция «Масштабирование и стриминг».

## 4. Мульти-нода сигналинга (design)

Проблема: WS-комнаты живут в памяти узла; при нескольких узлах участники одной
комнаты должны попадать на один узел, а broadcast (чат/присутствие/config/
анализ) — доходить до всех.

```
                 ┌──────────── LB (L7, sticky cookie / SNI+path) ────────────┐
                 │                                                          │
   browser ──────┼──► node-1 (WS+API) ◄── Redis pub/sub ──► node-2 (WS+API) │
                 │         │                                  │              │
                 │         └──────────► SFU-пул (LiveKit/mediasoup) ◄───────┘
                 │                        │
                 │                        └──► TURN-пул / CDN
```

- **Sticky routing:** LB по cookie `agil_node` (или consistent hash по roomId)
  → участник всегда на «своём» узле комнаты.
- **Room ownership:** у комнаты есть владелец-узел (lease в Redis); join
  перенаправляется на владельца (302/WS-редирект).
- **Broadcast:** чат, presence, state, config, analysis — публикуются в Redis
  канал `agil:signaling` с `{roomId, exceptNode}`; узлы рассылают локальным
  сокетам. Медиа не идёт через signaling.
- **Fan-out SFU:** медиа-потоки живут на SFU; signaling-узел только выдаёт
  токены и координирует.
- **Health/registry:** узлы регистрируются (TTL в Redis), LB опрашивает
  `/v1/health`, strategy `least-rooms`/`round-robin`/`geo`/`weighted`.
- **Sticky sessions** обязательны для mesh; для SFU — желательны.

MVP-статус: single-node (state в памяти), но контракт и health-флаги готовы;
реализация Redis-шины — следующий этап.

## 5. TURN-пул

- Несколько TURN в разных регионах/провайдерах: клиент получает весь пул в
  `welcome.iceServers`, браузер сам выбирает рабочий.
- Креды: статические (`username`+env) или TURN REST API (time-limited) — в
  контракте заложено env-имя; поддержка REST-кред — roadmap.
- TURN обязателен для корпоративных сетей и symmetric NAT; для вебинаров TURN
  закрывает только signaling/edge, медиа — SFU.
- Bandwidth: SFU-поток 720p ≈ 2–3 Мбит/с на зрителя; для 10 000 зрителей —
  CDN/мультикаст (B2B-сценарии — 1–2 тыс. на SFU-ноду + CDN).
- Мониторинг: алерты на allocation failures, relay port exhaustion, RTT.

## 6. SFU и вебинары

- **SFU:** LiveKit (Apache-2.0, Go) или mediasoup (ISC, Node). Клиент публикует
  один поток, получает подписки на N участников (`maxVideoSubscriptions`).
- **Режим `broadcast`:** спикеры публикуют; зрители только получают
  (receive-only), UI скрывает mic/cam/share, показывает «Вы зритель».
  `webinar.hostOnlyPublish`, `viewerLimit`, `chatMode` (moderated — сообщения
  проходят модерацию/очередь), `raiseHandQueue`.
- **Стрим наружу:** `streaming.provider = rtmp|hls|webrtc`; egress-сервис
  (LiveKit Egress/ffmpeg-воркер) публикует RTMP в YouTube/VK или HLS-плейлист;
  `latencyMode` low/standard.
- **CDN:** HLS/LL-HLS через CDN для тысяч пассивных зрителей; интерактив
  (спикеры) — через SFU.
- **Запись/транскрипт** на масштабе — server-side egress (не клиентская
  MediaRecorder), отдельный воркер.

## 7. Лимиты и capacity-план (ориентиры)

| Компонент | Один узел | Масштабирование |
|---|---|---|
| Signaling WS | ~5–10 тыс. соединений (Node, 1 vCPU/1 ГБ) | больше узлов + Redis |
| SFU | ~1–2 тыс. подписок на потоковую ноду (зависит от CPU) | пул SFU + регион |
| TURN | ~1–3 Гбит/с relay на сервер | пул TURN + anycast |
| Пассивные зрители | HLS/LL-HLS через CDN | CDN-провайдер |
| Чат на вебинаре | 50–100 сообщений/с на узел | Redis-шина + ограничение отправки |

## 8. Как включить (когда появится инфраструктура)

1. Поднять Redis, SFU (LiveKit), TURN-пул, LB (Nginx/HAProxy/облачный L7).
2. Заполнить `scale` через `PUT /v1/admin/scale` (или редактор/дашборд/MCP).
3. Убедиться, что `welcome.iceServers` содержит TURN-пул (проверить `/v1/health`,
   открыть звонок, `chrome://webrtc-internals`).
4. Для вебинара: `webinar.enabled=true`, `mediaMode='broadcast'`, создать
   комнату с `maxParticipants` из тарифа; зрители входят как `guest` с
   receive-only.
5. Для стрима: `streaming.enabled=true`, `provider='rtmp'`, `rtmpUrl` площадки.
6. Подключить мониторинг (PROMETHEUS/Grafana), алерты.

## 9. Что реализовано, а что нет

**Сделано в MVP (без тестирования масштаба — инфраструктуры пока нет):**
- Схема `TenantConfig.scale` (zod), дефолты, DTO и admin-эндпоинты;
- лимиты участников до 50 000, `RoomInfo.mediaMode`/`maxParticipants`;
- TURN-пул: подстановка ICE-серверов из конфига в `welcome` (STUN + TURN,
  credentials из env) — конфигурационно;
- UI редактора/дашборда для ввода топологии; MCP-инструменты; документация.

**Не реализовано (нужна инфраструктура и отдельный этап):**
- реальная Redis-шина мульти-ноды, room ownership/lease, sticky redirect;
- интеграция SFU (LiveKit/mediasoup), токены публикации/подписок, SVC/слои;
- receive-only режим зрителя на уровне сервера/медиа (сейчас — UI-гейтинг);
- RTMP/HLS egress, CDN, запись/транскрипт на масштабе;
- автоскейлинг, health-checks TURN/SFU, rate-limit на Redis;
- нагрузочные тесты (k6/webrtc-perf) и тюнинг.

Ориентир по срокам — `docs/estimate.md` (production-блок), подробный план —
`docs/backlog.md`.
