SIRINVIDEO Документация администратора и интегратора На сайт 2026.08

Производительность

От чего зависит нагрузка, как настроить хост, как правильно измерить загрузку процессора и что делать, когда ресурсов не хватает.

Сервер рассчитан на тысячи потоков и тысячи зрителей на одной машине. Чтобы это работало, нужно понимать три вещи: что именно тратит ресурсы, как их правильно измерять и какие настройки хоста обязательны.

Ресурсы расходуются в строгом порядке значимости: процессор → память → сеть. На реальной установке первым заканчивается процессор, вторым — память, и только в последнюю очередь упирается сеть. Планируйте и диагностируйте в том же порядке.

Во всех примерах media.example.com — ваш сервер, $TOKEN — токен доступа к API. Получить токен:

TOKEN=$(curl -sS -X POST https://media.example.com/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"password":"<пароль-администратора>"}' | sed -E 's/.*"token":"([^"]+)".*/\1/')

Что нагружает сервер#

Ресурс Основной потребитель Растёт от
Процессор приём и упаковка потоков + отправка байт зрителям числа потоков и числа зрителей
Память кольцевые буферы фрагментов, по одному на поток числа потоков × битрейт × глубина окна
Сеть сумма входящего и исходящего трафика битрейтов потоков и числа зрителей

Процессор#

Работа делится на две неравные части.

  • Приём и упаковка потока. Выполняется один раз на поток, независимо от того, сколько протоколов раздачи включено и сколько зрителей смотрит. Поток закреплён за одним ядром — см. Архитектуру.
  • Отправка зрителю. Выполняется для каждой сессии. Это самая массовая работа на нагруженном сервере, и она пропорциональна числу отправленных байт.

Отсюда практические выводы:

  • Лишние протоколы раздачи почти бесплатны. Упаковка общая; второй и третий протокол добавляют только отправку. Не выключайте протоколы «ради экономии» — экономии не будет.
  • Тысяча зрителей одного потока дешевле, чем сто зрителей десяти потоков. В первом случае упаковка выполняется один раз, во втором — десять. То же верно и для памяти: зритель на другом ядре получает ссылку на готовый фрагмент, а не его копию.

Дороже среднего обходятся:

Что Почему дороже
HTTPS / RTMPS / RTSPS / WSS шифрование каждого исходящего байта и рукопожатие на каждое соединение
WebRTC (WHEP) у каждого зрителя своё шифрование и свой поток пакетов — общей отправки нет
RTSP по UDP отправка отдельными RTP-пакетами вместо крупных фрагментов

Запись архива (DVR), напротив, дешёвая: на диск дописываются уже упакованные фрагменты, без повторной упаковки и без копирования.

Авторизация просмотра и защита от хотлинка отключают кэширование сегментов

Пока поток открыт, сегменты отдаются как неизменяемые и кэшируются браузером и CDN. Как только включается авторизация просмотра (auth_backend) или защита от хотлинка (allowed_referers), сегменты этого потока помечаются как некэшируемые — и весь трафик приходит на сервер, а не в кэш. Для крупной раздачи это может означать кратный рост исходящего трафика. Планируйте заранее.

Память#

Основной потребитель — кольцевой буфер фрагментов у каждого потока. Его размер оценивается как:

память на поток ≈ битрейт потока × segment_seconds × (segments_count + 1)

Единица прибавляется потому, что сверх окна манифеста в памяти удерживается ещё один сегмент — для клиентов, которые запросили его с опозданием. При значениях по умолчанию (segment_seconds 2, segments_count 5) поток на 4 Мбит/с занимает под буфер примерно 6 МБ.

Зрители памяти почти не добавляют: они читают общий буфер по курсору. Отдельных очередей на зрителя нет — медленный клиент отстаёт только сам и никогда не тормозит ни поток, ни соседей.

Источник file:// целиком лежит в памяти

Локальный файл, поднятый как живой поток, читается в память на весь срок жизни потока. Десять потоков из одного файла — это десять его копий в оперативной памяти. Для file:// используйте короткие ролики; для больших файлов — библиотеку VoD, она читает с диска порциями.

Служебные буферы наблюдаемости фиксированы и в расчёт ёмкости не входят: журнал в памяти — 4096 записей, аудит — 4096, лента событий — 512 кадров, ряды метрик — по 60 отсчётов на уровень. Ничто из этого не переживает перезапуск.

Сеть#

Считается прямо: сумма входящего трафика (битрейты всех источников) плюс сумма исходящего (битрейт × число зрителей на каждом потоке). Отдельно учитывайте копирование архива в S3, если оно настроено, — это дополнительный исходящий трафик.

Директива cores#

Параметр Тип и допустимые значения По умолчанию Применение
cores целое ≥ 0. 0 — все производительные ядра хоста; положительное число — ровно столько ядер, начиная с самых быстрых 0 рестарт

cores задаёт, сколько рабочих ядер займёт сервер. Каждое ядро владеет своими соединениями и своими потоками; поток целиком обрабатывается на одном ядре, а зрители распределяются по всем.

Проверить, что применилось, можно по строке в журнале при старте:

sudo journalctl -u hastreamer -n 50 --no-pager | grep 'hastreamer —'
hastreamer — 3 stream(s) · http :8080 (HLS+player+api) · rtsp :8554 · 16 cores (cpu affinity [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
cores 1 — это не «поменьше нагрузки»

Директива не ограничивает потребление, а сводит всю работу на одно ядро. С cores 1 все потоки до единого встают на ядро 0: оно упирается в 100 %, а остальные ядра простаивают. Симптом на работающем сервере — рывки видео и рост задержки при том, что общая загрузка хоста невелика.

Если нужно ограничить потребление, ограничивайте его средствами systemd (CPUQuota=), а не директивой cores.

Посмотреть фактическое распределение потоков по ядрам:

curl -sS "https://media.example.com/api/v1/streams?fields=id,core&limit=200" \
  -H "Authorization: Bearer $TOKEN"
{"items":[{"core":0,"id":"cam1"},{"core":1,"id":"cam2"},{"core":2,"id":"cam3"}]}

Если в ответе у всех потоков "core": 0 — проверьте cores в конфигурации.

Как посчитать ёмкость сервера#

Универсальной формулы «столько-то потоков на ядро» не существует: цена зависит от битрейта, разрешения, длины GOP, набора протоколов и доли TLS. Ёмкость считают измерением на пилоте, а не по таблице.

Порядок действий.

  1. Поднимите репрезентативную выборку. Возьмите 10–20 реальных источников — тех же моделей и тех же профилей, что пойдут в эксплуатацию.

  2. Дайте типовую нагрузку зрителями. Важно попасть в реальное соотношение: сколько зрителей на поток, по каким протоколам, какая доля HTTPS.

  3. Измерьте занятые ядра и память способом из следующего раздела, на установившемся режиме, а не в первые секунды после старта.

  4. Разделите и экстраполируйте. Стоимость приёма линейна по числу потоков, стоимость отдачи — по числу отправленных байт. Считайте два слагаемых отдельно:

    занятые ядра ≈ (ядер на поток) × число потоков + (ядер на Гбит/с отдачи) × отдача
    
  5. Оставьте запас. Планируйте установившуюся загрузку не выше 70 % рабочих ядер. Запас уходит на пики переподключений, на утренние всплески зрителей и на обслуживание архива.

По памяти считайте по формуле из раздела выше и добавьте служебный расход процесса. Текущий расход всегда виден в rss_mb.

Что заканчивается первым

Практическое правило: процессор упирается раньше памяти, а память — раньше сети. Если у вас кончилась память при незагруженном процессоре, ищите причину, а не докупайте память: почти всегда это источник file:// или слишком большое окно сегментов.

Настройка хоста#

Часть настроек сервер применяет сам при старте — но только если запущен от root (штатный hastreamer.service так и запускается). Всё, что он не смог применить, он печатает в журнал в виде готовых команд.

Проверьте, что он написал при последнем старте:

sudo journalctl -u hastreamer --no-pager | grep 'tune:' | tail -20

Нормальный вывод на настроенном хосте:

tune: fd limit 1048576 ✓
tune: host sysctl + governor already optimal ✓

Если хост настроен не полностью, вывод будет таким:

tune: ⚠ host is not optimally tuned. Run as root (or drop into /etc/sysctl.d/99-hastreamer.conf):
  sysctl -w net.core.somaxconn="65535"   # a 25k-viewer connect storm overflows a 4096 accept backlog

Лимит файловых дескрипторов#

Каждое соединение зрителя, каждый источник и каждый файл архива — это дескриптор. Значения по умолчанию в дистрибутивах (1024) не годятся: сервер упрётся в них мгновенно.

Лимит задаётся в юните systemd и поднимается сервером до жёсткого значения при старте:

# /etc/systemd/system/hastreamer.service
LimitNOFILE=1048576
LimitMEMLOCK=infinity

LimitMEMLOCK=infinity нужен потому, что io_uring регистрирует буферы и файлы в ядре и не должен наследовать низкий лимит дистрибутива.

Лимит поднят
sudo journalctl -u hastreamer --no-pager | grep 'tune: fd limit' | tail -1

Ожидается tune: fd limit 1048576 ✓. Строка вида tune: ⚠ fd limit is 1024 (< 100k) означает, что LimitNOFILE не применился — проверьте юнит и выполните systemctl daemon-reload.

Текущее число открытых дескрипторов всегда видно в GET /api/v1/system (поле open_fds).

Параметры ядра (sysctl)#

Установщик кладёт файл /etc/sysctl.d/99-hastreamer.conf. Его содержимое:

net.ipv4.tcp_ecn = 0
kernel.io_uring_disabled = 0
net.ipv4.ip_local_port_range = 1024 65535
net.core.netdev_max_backlog = 50000
net.core.somaxconn = 65535
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 131072 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216
net.ipv4.udp_rmem_min = 16384
net.ipv4.udp_wmem_min = 16384
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
fs.file-max = 2097152
fs.nr_open = 2097152
vm.swappiness = 1
vm.max_map_count = 1048576

Применить без перезагрузки:

sudo sysctl --system

Смысл ключевых значений:

Параметр Зачем
kernel.io_uring_disabled = 0 обязателен — без io_uring сервер не работает
net.ipv4.tcp_ecn = 0 на некоторых маршрутах согласование ECN приводит к потере фрагмента TLS-приветствия, и соединение зависает до завершения рукопожатия
net.ipv4.ip_local_port_range тысячи исходящих подключений к камерам и пары UDP-портов на зрителя исчерпывают узкий диапазон
net.core.somaxconn, netdev_max_backlog всплеск подключений не должен переполнять очереди приёма
net.core.rmem_max, wmem_max потолок буферов сокета; ниже него udp_ingest_rcvbuf будет обрезан ядром
vm.swappiness = 1 живые буферы видео не должны уходить в подкачку
io_uring обязателен

Ядро Linux 5.1 или новее с разрешённым io_uring. Если kernel.io_uring_disabled равно 1 или 2, сервер не сможет работать — политики безопасности, запрещающие io_uring, несовместимы с продуктом. Проверка: cat /proc/sys/kernel/io_uring_disabled должно вернуть 0.

Регулятор частоты процессора#

Экономичные режимы регулятора дают заметный разброс задержки. При запуске от root сервер сам переводит все ядра в режим performance; иначе печатает команду. Проверить вручную:

cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
performance

Ограничения ресурсов в юните#

В штатном юните заданы жёсткие потолки cgroup v2 — подгоните их под свою машину:

MemoryHigh=6G      # мягкий порог: ядро начинает вытеснять страницы
MemoryMax=8G       # жёсткий потолок
TasksMax=8192
# CPUQuota=800%    # 800% = восемь ядер; снимите комментарий, чтобы ограничить процессор

MemoryMax — это гарантия, что процесс не уронит хост. Сопоставляйте CPUQuota с cores, оставляя одно-два ядра хосту.

Как измерять загрузку правильно#

Это самая частая ошибка при разборе жалоб на производительность.

Не измеряйте процессор по %cpu из top

Мгновенный %cpu в top, htop или ps — это одна выборка, и на многоядерном сервере с короткими всплесками работы её шум сопоставим с самим значением. По одному такому числу нельзя ни принять решение, ни сравнить два замера.

Правильно: взять прирост процессорного времени процесса за интервал и разделить его на длину интервала. Получится число занятых ядер — величина, которую можно складывать, сравнивать и экстраполировать.

Способ 1 — считать самому#

Скрипт ниже реализует ровно это правило: (cputime₂ − cputime₁) ÷ длину интервала.

sudo tee /usr/local/bin/hs-cpu > /dev/null <<'EOF'
#!/usr/bin/env bash
# Загрузка процессора сервером = прирост процессорного времени ÷ длину интервала.
# Результат — число ЗАНЯТЫХ ЯДЕР. Использование: hs-cpu [интервал_сек]
PID=$(systemctl show -p MainPID --value hastreamer)
SECS=${1:-10}
HZ=$(getconf CLK_TCK)
ticks() { awk '{ n = split($0, a, ") "); split(a[n], f, " "); print f[12] + f[13] }' "/proc/$PID/stat"; }
T1=$(ticks); sleep "$SECS"; T2=$(ticks)
awk -v d="$((T2 - T1))" -v hz="$HZ" -v s="$SECS" \
    'BEGIN { printf "занято ядер: %.2f\n", d / hz / s }'
EOF
sudo chmod +x /usr/local/bin/hs-cpu
sudo hs-cpu 30
занято ядер: 3.41

Интервал берите не меньше 10 секунд, а для отчётных замеров — 30–60. Короткий интервал возвращает тот же шум, от которого вы уходили.

Способ 2 — спросить сервер#

Сервер измеряет себя тем же способом и публикует результат:

curl -sS https://media.example.com/api/v1/system \
  -H "Authorization: Bearer $TOKEN"
{"cpus":20,"worker_cores":16,"host_cpu_busy_pct":22.4,
 "per_core_busy_pct":[24.1,21.7,23.0,19.8,22.5,20.9,23.6,21.2,
                      22.8,20.4,23.9,21.6,22.1,20.7,23.3,21.9,0.0,0.0,0.0,0.0],
 "proc_cpu_cores":3.41,"rss_mb":1024,
 "mem_total_mb":65536,"mem_available_mb":40960,"open_fds":4231,"uptime_s":86400,
 "udp_ingest_kernel_drops":0,"udp_ingest_queue_dropped":0,
 "udp_ingest_recv_errs":0,"udp_ingest_rx_queued_kb":0,"udp_egress_bind_fails":0}
Поле Что означает
proc_cpu_cores занятые ядра процессом — прирост процессорного времени ÷ время. Сравнивать с показаниями top нельзя
worker_cores сколько ядер занял сервер (результат cores)
per_core_busy_pct загрузка каждого ядра хоста — так видно перекос на одно ядро
rss_mb память процесса
open_fds открытые дескрипторы
udp_ingest_kernel_drops датаграммы, отброшенные ядром на приёме RTSP-по-UDP

Способ 3 — метрики#

Те же величины отдаются в формате Prometheus:

curl -sS https://media.example.com/metrics \
  -H "Authorization: Bearer $TOKEN" | grep -E '^hastreamer_(process|worker)'
hastreamer_process_cpu_cores 3.4100
hastreamer_worker_cores 16
hastreamer_process_rss_bytes 1073741824

Это те же величины, что в /api/v1/system, только в форме экспозиции: proc_cpu_coreshastreamer_process_cpu_cores, rss_mbhastreamer_process_rss_bytes.

/metrics требует учётных данных

Эндпоинт закрыт и принимает те же токены, что и /api/v1, только заголовком Authorization. Выдайте системе мониторинга отдельный токен чтения — см. Мониторинг и метрики.

Что делать при нехватке ресурсов#

Сначала определите, какой ресурс кончился, а потом действуйте по таблице.

Симптом Что это значит Что делать
proc_cpu_cores близко к worker_cores процессор исчерпан увеличьте cores, если ядра хоста свободны; иначе разносите нагрузку на второй сервер
одно ядро в per_core_busy_pct занято, остальные простаивают вся работа на одном ядре проверьте cores — вероятно, стоит 1; либо на этом ядре стоит поток с непропорционально высоким битрейтом
rss_mb растёт, процессор свободен память тратится не на отдачу ищите источники file://; уменьшите segments_count и segment_seconds
mem_available_mb близко к нулю память хоста исчерпана уменьшите окно сегментов, снизьте MemoryHigh, вынесите часть потоков
udp_ingest_kernel_drops растёт ядро не успевает вычитывать RTSP-по-UDP поднимите udp_ingest_rcvbufnet.core.rmem_max под него), затем udp_ingest_pool_slots
open_fds близко к лимиту кончаются дескрипторы поднимите LimitNOFILE, проверьте, не осталось ли брошенных сессий
растёт задержка, ресурсы свободны это не ресурсы см. Диагностику неисправностей

Что даёт наибольший эффект, в порядке убывания:

  1. Уберите из отдачи то, чего никто не смотрит. Включите on_demand на потоках, которые запрашивают редко: без зрителей они не подключаются к источнику вовсе. Работает только на потоках без записи архива — у записываемого потока on_demand игнорируется.
  2. Снизьте битрейт на источниках. Самый действенный рычаг: он уменьшает сразу процессор, память и сеть. Перекодирования на сервере нет — профили задаются на камере.
  3. Ограничьте число сессий. max_sessions <n>; — общий потолок по всем протоколам; сверх него новые зрители получают отказ, а уже подключённые не отключаются.
  4. Уменьшите окно сегментов. segments_count и segment_seconds прямо задают память на поток.
  5. Сохраните кэшируемость сегментов там, где это допустимо. Если поток и так публичный, не включайте на нём allowed_referers — иначе весь его трафик пойдёт мимо кэшей на сервер.

Тонкая настройка приёма по RTSP/UDP#

Эти параметры имеют смысл только если камеры забираются по UDP (source_transport udp или udp-then-tcp). При транспорте по умолчанию (tcp) они не действуют.

Параметр Тип и допустимые значения По умолчанию Применение
udp_ingest_rcvbuf байты, 655361073741824 16777216 рестарт
udp_ingest_shards целое 116 2 рестарт
udp_ingest_pool_slots целое 644096 1024 рестарт
udp_pull_rate попыток в секунду, 0100000; 0 — без ограничения 100 рестарт

Порядок действий при потерях: сначала растёт udp_ingest_kernel_drops — поднимайте udp_ingest_rcvbufnet.core.rmem_max не ниже него, иначе ядро обрежет значение). Если растёт udp_ingest_recv_errs — поднимайте udp_ingest_pool_slots. udp_pull_rate сглаживает всплеск одновременных подключений после перезапуска.

Это не тюнинг приёма MPEG-TS по udp://

Перечисленные параметры настраивают маршрутизатор RTSP-по-UDP. У приёма MPEG-TS через input udp://… собственный маршрутизатор с фиксированными буферами, и он этими директивами не настраивается.

Куда двигаться дальше#