Кеширование и CDN для OTT-раздачи¶
Приложение для эксплуатации крупных инсталляций: как устроено кеширование OTT-ответов Perfect Streamer и как поставить перед узлом кеширующий reverse proxy или CDN. Режимы раздачи, форматы URL и авторизация описаны в основном разделе OTT и DVR.
Сервер формирует ответы трёх категорий, различающихся сроком жизни содержимого и пригодностью для кеширования промежуточными узлами (reverse proxy, CDN, клиентский кеш).
1. Модель кеширования¶
1.1. Ресурсы и HTTP-заголовки¶
Ресурс |
URL |
Content-Type |
Cache-Control |
|---|---|---|---|
TS-сегмент (HLS) |
|
|
|
VTT-сегмент (субтитры) |
|
|
|
fMP4-сегмент (DASH / LL-HLS) |
|
|
|
DASH MPD |
|
|
|
HLS master |
|
|
|
HLS media |
|
|
|
302 Redirect |
|
— |
|
Raw TS |
|
|
не задан; не кешируется |
1.2. Характеристики сегментов¶
Шестнадцатеричный идентификатор сегмента в URL (<keyHex> в путях
/h<sess>/<keyHex>.ts) формируется как CRC64 от стартового времени
сегмента и ID потока и глобально уникален. URL сегмента адресует
неизменное содержимое — при повторных запросах того же URL возвращается
идентичный байтовый поток (пока сегмент остаётся в пределах скользящего
окна).
Директива immutable подавляет условную ревалидацию клиентом
(If-None-Match, If-Modified-Since). Значение max-age=60
обеспечивает совместимость с типичным timeShiftBufferDepth=40s.
fMP4-сегменты CMAF (.m4s) и общий init.mp4 для DASH / Low-Latency
HLS адресуются аналогично и кешируются по той же модели (immutable,
max-age=60). Частичные сегменты (parts) LL-HLS — это
byte-range-запросы внутрь того же .m4s, поэтому отдельной записи в
кеше не образуют.
1.3. Характеристики манифестов¶
max-age=1 ограничивает верхнюю границу устаревания содержимого в кеше
одной секундой. При совместном использовании с proxy_cache_lock on
(nginx) всплески запросов к манифесту коалесцируются в один запрос на
origin в секунду.
1.4. Вариативность содержимого¶
При absPath=0 (значение по умолчанию, URL-параметра a нет)
манифесты HLS media и DASH MPD не содержат идентификатора сессии в теле.
Содержимое манифеста идентично между сессиями, принадлежащими одной
(stream, param)-комбинации. Это позволяет reverse-proxy кешу
переиспользовать запись между сессиями при нормализации ключа кеша.
При absPath=1 (URL-параметр a=1) в теле манифеста присутствуют
абсолютные URL, включающие схему, хост и идентификатор сессии. Содержимое
становится специфичным для сессии, возможность кросс-сессионного
переиспользования кеша отсутствует.
2. Поведение клиентов¶
Клиент |
URL обновления манифеста |
Воздействие на количество сессий |
|---|---|---|
HLS-плееры по media playlist |
|
Одна сессия на сеанс воспроизведения |
DASH-плееры по корневому URL |
|
Обрабатывается session reuse (см. 3.2) |
Браузерные плееры (MSE) |
|
Одна сессия на сеанс воспроизведения |
3. Специальные механизмы¶
3.1. HTTP 302 Redirect для DASH¶
Запрос вида /dash/<stream>/<login>/<pass>/index.mpd возвращает ответ
302 Found с заголовком Location: /h<sess>/index.mpd. Тело ответа
пустое. Авторизация и выделение сессии выполняются на этапе обработки
редиректа.
Клиенты, поддерживающие кеширование редиректа, обращаются к session URL напрямую в последующих запросах. Клиенты, не поддерживающие его, повторяют запрос редиректа. Стоимость повторной обработки редиректа ограничена проверкой аутентификации и операциями session reuse.
3.2. Session reuse для DASH¶
При обработке запроса /dash/.../index.mpd от того же логина к тому же
потоку (с тем же признаком adaptive) сервер находит уже существующую
DASH-сессию и возвращает её идентификатор повторно. Новая сессия не
создаётся, слот лимита одновременных подключений не расходуется.
Переиспользуются только сессии в одном режиме воспроизведения: живая
сессия и VOD-сессия архива (параметры t/d/epg) не
объединяются.
Применяется только к DASH. Для HLS отдельный механизм reuse не требуется: HLS-клиенты обновляют media playlist по session-URL и не создают новую сессию на каждом обновлении.
3.3. Переиспользование сегментов между сессиями¶
Путь /h<sess>/<keyHex>.ts не зависит от <sess> при разрешении
<keyHex> в содержимое: <keyHex> глобально однозначно
идентифицирует TS-сегмент в рамках потока. Nginx с нормализованным cache
key (обрезающим префикс /h<sess>/) обслуживает все запросы одного
и того же <keyHex> из единственной записи кеша, независимо от того,
какие клиенты их выдали.
Дедупликация применима только к content-addressed именам — сегментам с
16-символьным шестнадцатеричным идентификатором (.ts, .m4s с
<keyHex>, .vtt). Нумерованные сегменты live-DASH
(<номер>.m4s), init.mp4 и манифесты уникальны лишь в рамках
своей сессии — их ключ кеша должен сохранять префикс /h<sess>/,
иначе кеш будет отдавать содержимое одного потока зрителям другого.
Для DASH дедупликацию манифестов между клиентами одного логина
обеспечивает session reuse (см. 3.2).
4. Параметры запроса¶
Параметр |
Значение по умолчанию |
Влияние |
|---|---|---|
|
|
|
|
|
|
|
|
Минимальная длина окна для выдачи манифеста |
|
|
|
|
отсутствует (off) |
opt-in на HTTP/3 (QUIC): заставляет сервер выдать |
Изменение параметра через query string обновляет сохранённые в сессии
значения при её следующем переоткрытии. Параметры воспроизведения архива
t, d и epg описаны в Воспроизведение архива (VOD).
5. Нагрузочные характеристики¶
Нагрузка на origin масштабируется с числом одновременно наблюдаемых различных потоков. Увеличение числа клиентов, наблюдающих один и тот же поток, не увеличивает количество запросов к origin при наличии reverse-proxy кеша и нормализованного cache key.
Сценарий |
Частота origin-запросов (реф.) |
|---|---|
1 клиент на поток X |
MPD: 0.4 req/s, segment: 0.2 req/s |
N клиентов на один поток X (кеш включён) |
MPD: 1 req/s, segment: 0.2 req/s |
N DASH-клиентов в режиме повтора на одном потоке |
MPD: 1 req/s (с |
N клиентов на N различных потоков |
MPD: 0.4·N req/s, segment: 0.2·N req/s |
Сегменты дедуплицируются глобально по content-addressed именам; дедупликацию манифестов между клиентами обеспечивают session reuse (DASH) и кеш в пределах сессии.
6. Nginx как кеширующий reverse proxy¶
6.1. Базовая конфигурация¶
proxy_cache_path /var/cache/nginx/pss_segments
levels=1:2 keys_zone=pss_segments:100m
max_size=20g inactive=30m use_temp_path=off;
proxy_cache_path /var/cache/nginx/pss_manifests
levels=1:2 keys_zone=pss_manifests:10m
max_size=256m inactive=5m use_temp_path=off;
upstream pss_backend {
server 127.0.0.1:43972;
keepalive 64;
}
map $uri $pss_cache_key {
"~^/h[0-9a-f]{16}(?<tail>(/[0-9]+)?/[0-9a-f]{16}\.(ts|m4s)|/sub/[0-9]+/[0-9a-f]{16}\.vtt)$" "stream:$tail";
default $uri;
}
server {
listen 80;
server_name stream.example.com;
location ~* "^/h[0-9a-f]{16}(/[0-9]+|/sub/[0-9]+)?/([0-9a-f]+\.(ts|m4s|vtt)|init\.mp4)$" {
proxy_cache pss_segments;
proxy_cache_key $pss_cache_key;
proxy_cache_valid 200 60s;
proxy_cache_valid 404 403 0s;
proxy_cache_lock on;
proxy_cache_use_stale updating error timeout;
proxy_cache_revalidate on;
proxy_ignore_headers Vary;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://pss_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering on;
}
location ~* "(^/h[0-9a-f]{16}(/[0-9]+|/sub/[0-9]+)?/index\.(m3u8|mpd)$|^/(hls|dash|llhls)/.*\.(m3u8|mpd)$)" {
proxy_cache pss_manifests;
proxy_cache_key $pss_cache_key;
proxy_cache_valid 200 1s;
proxy_cache_valid 404 403 0s;
proxy_cache_lock on;
proxy_cache_lock_timeout 2s;
proxy_cache_use_stale updating;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://pss_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location / {
proxy_pass http://pss_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_buffering off;
proxy_read_timeout 3600s;
}
}
Порт origin в примере — порт потокового HTTP-сервера по умолчанию
(43972; HTTPS — 43982). Нормализуются только content-addressed имена
сегментов (см. 3.3); манифесты, init.mp4 и нумерованные сегменты
кешируются по полному URI своей сессии. Директива
proxy_ignore_headers Vary в location сегментов оправдана: сервер
сопровождает OTT-ответы заголовком Vary: Origin, и без неё кеш
делился бы на варианты по значению Origin клиента (см. 7).
6.2. Назначение директив¶
Директива |
Назначение |
|---|---|
|
Сериализует выполнение upstream-запросов при одновременных cache miss по одному ключу |
|
Возвращает устаревшую копию параллельным запросам в период обновления кеша |
|
Использует |
|
Запрещает кеширование ошибок авторизации и 404 |
|
Поддерживает пул persistent-соединений к origin |
|
Для сегментов; включает буферизацию ответа в nginx |
|
Для раздела |
6.3. Расчёт max_size кеша сегментов¶
Ориентировочное значение:
bitrate × timeShiftBufferDepth × distinct_streams × 2
Пример: 10 потоков × 8 Мбит/с × 40 с × 2 ≈ 800 МБ. Рекомендуется задать запас 10x для учёта вариативности битрейта.
6.4. TLS-терминация¶
Сервер Perfect Streamer принимает соединения на портах HTTP и HTTPS.
При TLS-терминации на nginx upstream использует порт HTTP. Пересылка
заголовков X-Forwarded-Proto и X-Forwarded-Host обязательна для
корректного формирования абсолютных URL при absPath=1.
server {
listen 443 ssl http2;
server_name stream.example.com;
ssl_certificate /etc/letsencrypt/live/stream.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/stream.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location ... {
proxy_pass http://pss_backend;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Host $host;
# + caching directives from 6.1
}
}
server {
listen 80;
server_name stream.example.com;
return 301 https://$host$request_uri;
}
При HTTPS между nginx и origin применяются директивы
proxy_ssl_verify, proxy_ssl_trusted_certificate. Для
loopback-соединений шифрование избыточно.
6.5. Мультихост¶
При обслуживании нескольких server_name из одного nginx-процесса
$host добавляется в cache key для изоляции контента:
map $uri $pss_cache_key {
"~^/h[0-9a-f]{16}(?<tail>(/[0-9]+)?/[0-9a-f]{16}\.(ts|m4s)|/sub/[0-9]+/[0-9a-f]{16}\.vtt)$" "$host:stream:$tail";
default "$host:$uri";
}
Размер keys_zone рассчитывается как 8000 ключей/MB. Для
мультихост-инсталляций с тысячами потоков рекомендуется
keys_zone=...:300m или выше.
7. Клиентское кеширование¶
Cache-Control: immutable обрабатывается современными браузерами.
Клиентский кеш возвращает сегмент без условного запроса при повторном
обращении (в том числе при обратном seek в пределах буфера плеера).
Service Workers могут применять стратегию cache-first на основании
содержимого Cache-Control. DASH-плееры используют MSE через
SourceBuffer; сегмент, помещённый в буфер, остаётся доступен без
повторного HTTP-запроса до выхода за границу скольжения.
Для кросс-доменных запросов сервер отдаёт
Access-Control-Allow-Origin: * вместе с заголовком
Vary: Origin, Access-Control-Request-Headers. Кеширующий прокси,
уважающий Vary, делит записи на варианты по значению Origin клиента
(браузерные плееры шлют Origin, консольные — нет), что снижает HIT rate.
Поскольку ACAO — универсальный «*», в location сегментов безопасно
добавить proxy_ignore_headers Vary (см. 6.1).
8. Размещение через CDN¶
Perfect Streamer совместим с CDN в режиме pull-from-origin.
Origin shield. Рекомендуется размещение одного или нескольких shield-узлов между CDN edge и origin для снижения частоты запросов на origin при глобальном распределении клиентов.
Purge. Content-addressed сегменты не требуют purge. При изменении
метаданных потока (кодек, разрешение) манифесты обновляются в течение
max-age=1 без явного purge.
Cache warming. При ожидаемом росте нагрузки на конкретный поток допустим прогрев CDN с нескольких географических точек до начала трансляции.
Геораспределение. Сегменты (max-age=60) хорошо подходят для
географически распределённого кеширования. Манифесты (max-age=1)
допускают задержку доставки до одной секунды — приемлемо для
live-вещания без низкой задержки.
9. Мониторинг¶
9.1. X-Cache-Status¶
Добавление add_header X-Cache-Status $upstream_cache_status; в каждый
location с кешированием. Значения:
Значение |
Описание |
|---|---|
|
Ответ из кеша |
|
В кеше отсутствовал, получен от origin и сохранён |
|
Просрочен, обновлён |
|
Отдана stale-копия параллельному запросу во время обновления |
|
|
|
Origin вернул 304 Not Modified |
|
Сработал |
9.2. Формат access-log¶
log_format pss_cache '$remote_addr $status $request_method "$request" '
'$body_bytes_sent rt=$request_time ut=$upstream_response_time '
'cache=$upstream_cache_status key=$pss_cache_key';
server {
access_log /var/log/nginx/pss.log pss_cache;
}
9.3. Метрики¶
Модуль nginx-vts экспортирует метрики per-zone в формате Prometheus:
GET /status/format/prometheus
Рекомендуемые пороги для алертов:
Метрика |
Порог |
Возможная причина |
|---|---|---|
Segment HIT rate |
< 90% за 5 минут |
Нарушена нормализация cache key; малый |
Manifest MISS rate |
> 50% за 1 минуту |
|
Upstream response time p95 |
> 500 мс за 1 минуту |
Перегрузка origin |
Cache zone fill |
> 90% за 10 минут |
Приближение к |
Качество доставки на стороне узла видно по метрикам сессий на экране «Клиенты» (Клиенты).
10. Диагностика¶
Симптом |
Вероятная причина |
Решение |
|---|---|---|
Segment HIT rate низкий |
|
Добавить |
404 на сегментах после выхода из окна |
Закешированный 404 при сегменте, выпавшем из sliding window |
Добавить |
Задержка playback start 2–5 с |
|
Снизить до 1–2 с; включить |
Manifest не обновляется |
задан |
Явно указать |
Рост TIME_WAIT на upstream |
Отсутствует |
Добавить |
403 на |
Клиент резолвит относительные URL от pre-redirect URL |
Сервер выдаёт |
Лаги, частые ребуферы у удалённых клиентов |
Низкий эффективный throughput TCP из-за slow start и idle restart при больших RTT (300 мс и выше) |
Тюнинг сетевого стека Linux на origin: см. 10.1 |
10.1. TCP-тюнинг origin для high-RTT клиентов¶
Проблема проявляется у клиентов с большим RTT до origin (например, 300 мс и выше) при битрейте потока, близком к пропускной способности канала. Симптомы в плеере — частые ребуферы и разрывы; на сервере клиент при этом выглядит нормально, ошибок нет.
Причина:
TCP slow start. Каждое новое TCP-соединение начинает с congestion window около 14 КБ и наращивает его за несколько RTT. При RTT 300 мс выход на полное окно занимает 2–3 секунды. За это время HLS/DASH-сегмент длительностью 5 с (4–6 МБ) скачивается заметно медленнее реального времени.
TCP idle restart. Между запросами сегментов клиент по HLS pull-модели делает паузу 4–5 с. По умолчанию ядро Linux после такой паузы сбрасывает congestion window соединения обратно к initial cwnd (поведение
net.ipv4.tcp_slow_start_after_idle=1). В результате при следующем GET keep-alive-соединение начинает передачу с медленного старта заново — даже на уже разогретой сессии.
Дополнительно ситуацию усугубляет то, что congestion control CUBIC по умолчанию плохо переносит длинные RTT и пакетные потери на промежуточных участках сети.
Решение — два sysctl-параметра на origin:
# Keep congestion window across idle pauses inside keep-alive sessions.
sysctl -w net.ipv4.tcp_slow_start_after_idle=0
# Use BBR instead of CUBIC: better behaviour on long-RTT paths
# with mild packet loss; paces sending instead of bursting.
modprobe tcp_bbr
sysctl -w net.ipv4.tcp_congestion_control=bbr
Для постоянного применения:
cat > /etc/sysctl.d/99-pss-net.conf <<EOF
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_congestion_control = bbr
EOF
sysctl --system
Главный эффект даёт первый параметр (tcp_slow_start_after_idle=0) —
он напрямую устраняет повторный slow start между сегментными запросами в
одном keep-alive-соединении. Второй (BBR) даёт дополнительную
устойчивость и применяется ко всем новым соединениям. Тюнинг не требует
перезапуска Perfect Streamer. К клиентам HTTP/3 (QUIC) эти параметры не
относятся: QUIC работает поверх UDP с собственным управлением
перегрузкой.
11. Безопасность¶
11.1. Session URL¶
URL формата /h<sess>/... выполняет функцию сессионного токена — не
требует повторной аутентификации. Срок жизни ограничен таймаутом
неактивности 60 секунд; при отсутствии активности сессия удаляется
автоматически.
Требования:
HTTPS для всех OTT-путей (
/hls/,/dash/,/h<sess>/) в production;Session ID в заголовке
Locationответа 302 не кешируется (no-cache, no-store).
11.2. Rate limiting¶
limit_req_zone $binary_remote_addr zone=dash_top:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=hls_top:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=llhls_top:10m rate=5r/s;
server {
location /dash/ {
limit_req zone=dash_top burst=20 nodelay;
proxy_pass http://pss_backend;
}
location /hls/ {
limit_req zone=hls_top burst=20 nodelay;
proxy_pass http://pss_backend;
}
location /llhls/ {
limit_req zone=llhls_top burst=20 nodelay;
proxy_pass http://pss_backend;
}
}
Session URL (/h<sess>/) не требует rate limiting — обработка дешёвая,
ответы кешируются.
11.3. Кеширование ответов с ошибками¶
proxy_cache_valid 200 60s;
proxy_cache_valid 301 302 0s;
proxy_cache_valid 404 403 0s;
proxy_cache_valid any 1s;
Запрещает кеширование редиректов (уникальный sess в Location) и ответов с ошибками авторизации или отсутствия ресурса.
11.4. Ограничение сетевого доступа к origin¶
Порт потокового HTTP-сервера (по умолчанию 43972; 43982 для HTTPS) должен быть закрыт для внешнего трафика. Допустимые конфигурации:
Bind Perfect Streamer на
127.0.0.1(при локальном nginx)Firewall-правило:
iptables -A INPUT -p tcp -m multiport --dports 43972,43982 ! -s 10.0.0.0/8 -j DROP
12. Интеграция с middleware¶
12.1. Модель prefix-login¶
Perfect Streamer поддерживает делегирование идентификации пользователя middleware-системе через механизм prefix-login — встроенную альтернативу полной интеграции с внешним биллингом (учётные записи, проверяемые внешней системой, настраиваются на экране «Пользователи / Логины», вкладка «Внешние (биллинг)», Пользователи / Логины).
У локального логина включается признак префикса, после чего сервер
принимает URL с логином вида <prefix><идентификатор_подписчика>:
/dash/test1/sub42/xxx/index.mpd
/hls/test1/sub43/xxx/index.m3u8
Здесь sub — настроенный логин-префикс, а 42/43 —
идентификаторы подписчиков middleware; пароль общий.
12.2. Статистика по подписчикам¶
В списке клиентов каждая сессия несёт и конфигурационный логин, и фактический URL-логин подписчика (match-login), по которому middleware сопоставляет сессии со своими абонентами. Данные видны на экране «Клиенты» (Клиенты).
12.3. Ограничения prefix-login¶
Общий пароль. Все подписчики prefix-пула используют одно значение пароля. Компрометация пароля предоставляет доступ к любому
<prefix><строка>.Гранулярность доступа. Список разрешённых потоков применяется ко всему prefix-пулу. Доступ на уровне отдельного подписчика обеспечивает интеграция с внешним биллингом.
Ротация пароля. Изменение пароля отключает всех активных подписчиков. Для постепенной замены требуется временное использование двух prefix-логинов.