diff --git a/README.md b/README.md index 7ab1648..3d9fc19 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,8 @@ VidConf использует единый инсталлятор `install.sh` с ``` Требования к оборудованию и полное описание см. в [docs/deploy/install.md](docs/deploy/install.md) и [docs/architecture/adr/004-ai-tier-matrix.md](docs/architecture/adr/004-ai-tier-matrix.md). +Это требования под AI; сколько CPU/RAM/полосы нужно под саму видео-нагрузку (профиль +«сколько человек и камер») — [docs/deploy/hardware-sizing.md](docs/deploy/hardware-sizing.md). ### Docker Compose профили (низкоуровневый контроль) diff --git a/docs/deploy/capacity.md b/docs/deploy/capacity.md index bb4596a..b2f1220 100644 --- a/docs/deploy/capacity.md +++ b/docs/deploy/capacity.md @@ -1,5 +1,9 @@ # Нагрузочное тестирование SFU (LiveKit): методика и ёмкость +> Цифры здесь — с dev-Mac (см. предупреждение ниже), для реальных прод-замеров +> и готовой таблицы «профиль нагрузки → железо» см. +> [hardware-sizing.md](hardware-sizing.md). + Оценивает, сколько одновременных издателей аудио+видео и подписчиков выдерживает LiveKit SFU в текущей конфигурации compose (`deploy/livekit/livekit.yaml`), diff --git a/docs/deploy/hardware-profiles.md b/docs/deploy/hardware-profiles.md index c5a1175..ce1005f 100644 --- a/docs/deploy/hardware-profiles.md +++ b/docs/deploy/hardware-profiles.md @@ -61,4 +61,6 @@ VidConf использует **5 пресетов инсталлятора** (н - **[LLM Setup](llm-setup.md)** — ручная установка/скачивание моделей - **[Deploy: Мониторинг](monitoring.md)** — Prometheus/Grafana, алерты - **[Deploy: Масштабирование](scaling.md)** — горизонтальное масштабирование -- **[Deploy: Ёмкость](capacity.md)** — калькулятор нагрузки и IOPS +- **[Deploy: Ёмкость](capacity.md)** — калькулятор нагрузки и IOPS (синтетический тест) +- **[Deploy: Профиль нагрузки → железо](hardware-sizing.md)** — сайзинг под саму + видео-нагрузку (участники/камеры/полоса), на реальных замерах с прода diff --git a/docs/deploy/hardware-sizing.md b/docs/deploy/hardware-sizing.md new file mode 100644 index 0000000..785679b --- /dev/null +++ b/docs/deploy/hardware-sizing.md @@ -0,0 +1,243 @@ +# Профиль нагрузки → рекомендуемое железо (медиа) + +Отвечает на вопрос «сколько CPU, RAM и полосы нужно под ожидаемую видео-нагрузку» +для тех, кто разворачивает VidConf у себя. Разговор именно про **медиа** +(конференции, LiveKit SFU) — сайзинг под AI (транскрибация/суммаризация) описан +отдельно: [install.md](install.md), [hardware-profiles.md](hardware-profiles.md), +[ADR-004](../architecture/adr/004-ai-tier-matrix.md). Базовый пресет инсталлятора +без AI (`4 vCPU / 8 ГБ RAM / 40 ГБ диска`) рассчитан именно на медиа-нагрузку — +этот документ объясняет, какую конференцию такое железо реально держит и когда +его уже мало. + +От синтетического нагрузочного теста [capacity.md](capacity.md) (SFU на dev-Mac, +формула по CPU) этот документ отличается источником цифр: здесь — **измерения на +боевом сервере с реальными людьми**, не эмуляция. + +⚠️ Все числа ниже — **ориентировочные**, при указанных допущениях. Реальная +нагрузка зависит от поведения людей: сколько включат камеру, будет ли демонстрация +экрана, как долго говорят несколько человек одновременно. Используйте таблицу как +отправную точку для выбора железа, не как гарантию. + +## Точка привязки к реальности + +Единственные цифры ниже, которые не расчёт, а прямое измерение на боевом сервере +(`4 CPU, 8 ГБ RAM, Ubuntu 24.04`, тот же узел, где сейчас работает `vidconf.ru`): + +| Дата | Версия | Участников | Камер | Подписок на видео | Исходящий трафик LiveKit | CPU LiveKit | +|---|---|---|---|---|---|---| +| 28.07.2026 | 0.0.11, **до** `adaptiveStream` | 19 | 17 | ~306 | 140–169 Мбит/с устойчиво (пик 240) | 1.62 ядра из 4 | +| 31.07.2026 | 0.0.13, **после** `adaptiveStream`+`dynacast` | 30 | 27 | 783 | **70.6 Мбит/с** | 1.83 ядра из 4 | + +Второе измерение и есть якорь для формулы ниже — оно снято на конфигурации, +максимально близкой к дефолтной (пагинация сетки участников, `adaptiveStream`, +`dynacast`), без ручной настройки под тест. + +**Почему не пиковые 240 Мбит/с.** Пик — кратковременный всплеск, а не режим, в +котором сервер работал устойчиво; 1.62 ядра CPU намерены именно под устойчивые +140–169 Мбит/с. Если посчитать коэффициент «Мбит/с на ядро» по пиковому числу, +получится оптимистичнее примерно в полтора раза, чем в реальности — расчёт по +такому коэффициенту недооценит нужное железо. + +**Что показывает разница двух строк.** Во второй нагрузка выше (участников ×1.6, +подписок ×2.6), а трафик почти вдвое **ниже**, при том что CPU почти не +изменился. Это эффект `adaptiveStream`: клиент подписывается на трек, но получает +битрейт под фактический размер плитки на экране, а невидимые (не помещающиеся на +текущую страницу сетки) треки почти не занимают полосы. Всё, что дальше в этом +документе, посчитано **для конфигурации с `adaptiveStream`** (релиз ≥0.0.13, в +проекте включён с этой версии по умолчанию) — без него числа нужно умножать в +разы, см. следующий раздел. + +## Почему нельзя считать «все видят всех» + +SFU (LiveKit) пересылает пакеты, а не микширует их. Наивная формула трафика — +`N × (N−1) × битрейт` (каждый участник получает поток от каждого) — при 80 +участниках даёт единицы **гигабит в секунду**: недостижимо на одном сервере и не +имеет отношения к тому, что видит пользователь на экране. + +Реальная модель другая: клиент подписан не на всех, а на **видимые плитки**, и +получает под каждую подписку битрейт по фактическому размеру плитки — благодаря +пагинации сетки (с релиза 0.0.11) и `adaptiveStream`/`dynacast` (с 0.0.13). +Отсюда рабочая формула: + +``` +подписок на видео = камер × (участников − 1) +``` + +Она подтверждена обоими измерениями выше **точно**: 17 × 18 = 306, 27 × 29 = 783. +Это формула для типичного «видят всех камер» размещения (сетка без ручного +скрытия участников) — при включённом лимите плиток (см. ниже) число подписок не +растёт дальше лимита, даже если камер больше. + +**Насколько наивная формула хуже.** Гипотетически, если бы все 75 участников +большого собрания (профиль ниже) были источником видео и каждый получал полный +поток от каждого — `75 × 74 × 1.5 Мбит/с` (типичный битрейт публикации без +адаптации) — это **9.48 Гбит/с**: недостижимо ни на одном разумном сервере. +Модель выше на сопоставимом масштабе (профиль «Большое собрание», 15 камер из +75) даёт около 182 Мбит/с — **в 50 с лишним раз меньше**. Разница — не оптимизация +в мелочах, а другая по порядку величины задача, и именно поэтому SFU вообще +годится для конференций на десятки участников. + +Аудио в этой формуле — единицы процентов трафика: микрофоны обычно включены у +2–5 человек одновременно, замьюченный трек полосу не занимает, DTX/RED включены +по умолчанию. Дальше считаем аудио отдельным слагаемым, не путая с видео. + +## Формула + +``` +1. подписок = камер × (участников − 1) + (если включён лимит плиток в настройках инстанса — camер заменить на min(камер, лимит)) + +2. видео = подписок × битрейт_на_подписку + + битрейт_на_подписку зависит от того, помещаются ли все плитки на одну страницу: + - крупная плитка / говорящий в фокусе (мало плиток на экране) → 450 кбит/с + - мелкая плитка сетки, все участники на одной странице → 150 кбит/с + - сетка с пагинацией (участников больше лимита плиток) → 100 кбит/с + (часть подписок физически не на экране — почти не потребляет полосы) + +3. аудио = активных_микрофонов × участников × 40 кбит/с + +4. исходящая полоса сервера = видео + аудио ← главный параметр, см. ниже + +5. ядер CPU (LiveKit) ≈ max(2, ⌈исходящая_полоса_Мбит/с ÷ 90⌉) + + 1–2 ядра на остальной стек (backend, БД, Redis, coturn, nginx, ОС) + +6. RAM ≈ 4 ГБ база (без AI-профилей) — на этом масштабе RAM не была узким + местом ни на одном реальном или синтетическом тесте, планировать по CPU и полосе +``` + +**Откуда коэффициенты.** + +- 450 / 150 кбит/с — измерение одного трека клиентом в крупной и мелкой плитке + (релиз 0.0.13), округлено вверх от 453 и 147 для запаса. +- 100 кбит/с — обратный расчёт по якорному замеру 31.07.2026: + 70.6 Мбит/с ÷ 783 подписки ≈ 90 кбит/с в среднем, округлено вверх. Число ниже, + чем «мелкая плитка» (147), потому что в комнате на 30 участников часть из 783 + подписок физически не помещалась на текущую страницу сетки — `adaptiveStream` + почти обнулил их битрейт, а среднее по всем подпискам это отражает. +- 90 Мбит/с на ядро — из 28.07.2026: 140–169 Мбит/с устойчиво ÷ 1.62 ядра = + 86–104 Мбит/с/ядро, округлено вниз (консервативно, в пользу большего числа + ядер). +- Минимум 2 ядра и запас 1–2 ядра на остальной стек — эмпирический пол: на + обоих реальных замерах LiveKit не опускался ниже 1.6 ядра независимо от + трафика, а весь остальной стек (backend, Postgres, Redis, coturn, nginx) на + боевом сервере устойчиво укладывается в разницу между занятым LiveKit и 4 + доступными ядрами. + +⚠️ **Формула по полосе — не единственная граница.** Между двумя замерами +подписок стало в 2.6 раза больше, трафик упал вдвое, а CPU почти не изменился +(1.62 → 1.83 ядра) — то есть процессор тратится в первую очередь на +обработку пакетов/подписок, а не на байты. На сценариях с очень большим числом +мелких подписок (много участников, лимит плиток не выставлен) реальный CPU +может обогнать то, что предсказывает формула по полосе быстрее, чем ожидается +— держите эмпирический пол (2 ядра LiveKit минимум под любую активную +конференцию) и не полагайтесь только на деление на 90. + +## Профили нагрузки + +Все профили — при `adaptiveStream`+`dynacast` (по умолчанию с 0.0.13) и без +AI-профилей (`transcribe`/`llm`). Допущения по камерам/микрофонам — решение, +не измерение; подставьте свои, если знаете реальный сценарий. + +| Профиль | Сценарий | Камер | Подписок | Полоса (видео+аудио) | CPU (LiveKit) | RAM | Узкое место | +|---|---|---|---|---|---|---|---| +| Малая команда | 10 параллельных созвонов по 5 чел., 60% с камерой, 2 микрофона в каждом | 3×10 | 12×10=120 | ~58 Мбит/с | 2 ядра | 4 ГБ | нет — запас большой | +| Совещание | 1 конференция × 25 чел., 70% с камерой, 4 микрофона | 18 | 432 | ~69 Мбит/с | 2 ядра | 4 ГБ | полоса — близко к нашему якорю (70.6 Мбит/с на 30 чел.) | +| Большое собрание | 1 конференция × 75 чел., 20% с камерой (камер меньше лимита плиток), 5 микрофонов | 15 | 1110 | ~182 Мбит/с | 3 ядра | 4–6 ГБ | полоса и её цена у хостера | +| Смешанная нагрузка | «Малая команда» + «Совещание» одновременно на одном сервере | — | — | ~127 Мбит/с | 2 ядра | 4 ГБ | суммируется линейно | + +Малая команда и совещание укладываются в базовый пресет инсталлятора без AI +(`4 vCPU / 8 ГБ`) с большим запасом — это ровно тот масштаб, что подтверждён +якорным замером (30 чел./27 камер на этом же железе, CPU занят на 46%). Большое +собрание уже требует железа **больше** базового пресета — 3 ядра под сам +LiveKit плюс 1–2 под остальной стек означают, что 4 vCPU становятся тесными. + +### Когда профиль недостижим — и как его спасти + +«Большое собрание, все 75 человек с камерой» без ограничений: подписок +75 × 74 = 5550, полоса по коэффициенту пагинации (100 кбит/с) — уже **~555 +Мбит/с** только видео, плюс аудио. Такой канал недостижим на типичном железе +и его аренде — это не вопрос выбора сервера мощнее, это упирается в канал +и его стоимость у хостера. + +Спасает **лимит плиток на экране** (настройка инстанса «Максимум плиток на +странице», 25/16/9/4, с релиза 0.0.21): он ограничивает число подписок сверху +независимо от числа камер — `min(камер, лимит) × (участников − 1)`. При лимите +16 и том же собрании: 16 × 74 = 1184 подписки × 150 кбит/с (все 16 видны +одновременно, без пагинации) ≈ **178 Мбит/с** только видео — втрое меньше, чем +без лимита, но всё ещё требует железа заметно больше базового пресета (3+ ядра +LiveKit по формуле). Лимит делает профиль реалистичным, не дешёвым. Тот же +эффект даёт «Потолок качества публикации» (720p/360p/180p, тот же релиз) — +режет битрейт публикации у источника, а не только у подписчика. + +Если и это не помогает — речь уже не про один сервер, а про горизонтальное +масштабирование LiveKit-кластера, вне рамок этого документа. + +## Входящая полоса и канал клиента — отдельная история + +Таблица выше — **исходящая** полоса сервера (каждому подписчику отдельная +копия), она и есть главный параметр: растёт с числом участников и подписок. + +**Входящая** полоса (от клиентов к серверу) на порядок меньше: она равна сумме +битрейтов публикуемых потоков — `камер × ~0.3–1.5 Мбит/с` (зависит от «Потолка +качества публикации», 0.0.21) — и **не** умножается на число зрителей. Для +собрания на 75 человек с 15 камерами это 4.5–22.5 Мбит/с входящих — заметно +меньше 182 Мбит/с исходящих, узким местом почти никогда не становится. + +Отдельно — канал **самого клиента**, не сервера. Офис, откуда заходит половина +участников совещания, может упереться в свой исходящий/входящий канал раньше, +чем сервер упрётся в свой. Серверный сайзинг эту часть не решает — это забота +сетевой инфраструктуры на стороне участников. + +## Оговорка про канал и его стоимость + +На реалистичных профилях (кроме экстремальных, см. выше) узким местом +оказывается почти всегда не CPU — оба реальных замера показали комфортный +запас (1.6–1.83 ядра из 4) — а **исходящая полоса и её стоимость у хостера**. +Большинство тарифов VPS считают трафик либо лимитом с доплатой за перебор, +либо по 95-му перцентилю канала; при планировании крупных конференций +сверяйтесь с тарифом хостера на трафик/канал, а не только с числом ядер и +объёмом RAM. + +## Как посчитать под свой сценарий + +1. Оцените участников (N), долю с камерой, число одновременно активных + микрофонов. +2. `подписок = камер × (N − 1)`; если планируете включить лимит плиток — + `min(камер, лимит) × (N − 1)`. +3. Выберите битрейт на подписку по разделу «Формула» (450 / 150 / 100 кбит/с) + в зависимости от того, помещаются ли все камеры на одну страницу. +4. `видео = подписок × битрейт`, `аудио = микрофонов × N × 40 кбит/с`, + `полоса = видео + аудио`. +5. `ядер CPU ≈ max(2, ⌈полоса ÷ 90⌉) + 1–2` на остальной стек. +6. RAM — 4 ГБ база, не растёт заметно с этим масштабом участников (растёт с + выбранным уровнем AI, см. [ADR-004](../architecture/adr/004-ai-tier-matrix.md), + если он используется). +7. Сверьте полосу с тарифом хостера на трафик/канал — часто это упрётся раньше + железа. + +## Что может измениться + +⚠️ Коэффициент «90 Мбит/с на ядро» и оба якорных замера сняты **до** перевода +LiveKit с диапазона UDP-портов на `rtc.udp_port` (задача «Сеть LiveKit», релиз +0.0.20, задеплоено 02.08.2026) — до этой правки медиатрафик на хосте шёл через +процессы `docker-proxy` (userland-прокси Docker). Сама оптимизация меняет путь +пакетов на хосте, а не логику LiveKit, поэтому полоса из таблиц, скорее всего, +не изменится, а запас по CPU/сети хоста на практике может оказаться больше +указанного здесь. Новый нагрузочный тест с реальными участниками после этой +оптимизации пока не проводился — числа в этом документе консервативны и не +переоценивают требуемое железо, но при появлении нового замера на текущей +сети коэффициенты стоит пересчитать. + +## Смотрите также + +- [capacity.md](capacity.md) — синтетический нагрузочный тест SFU (`lk load-test` + на dev-Mac) и формула по CPU для верхней оценки ёмкости узла; используйте вместе + с этим документом, если нужна методика для собственного повторного теста. +- [install.md](install.md), [hardware-profiles.md](hardware-profiles.md), + [ADR-004](../architecture/adr/004-ai-tier-matrix.md) — сайзинг под AI + (транскрибация/суммаризация), отдельно от медиа. +- [monitoring.md](monitoring.md) — как снять реальные цифры со своего сервера + (CPU/RAM/сеть по контейнерам, Grafana). +- [DEPLOYMENT.md](DEPLOYMENT.md) — TURN, порты, что открыть в файрволе под + медиа-трафик.