Cache e CDN para a distribuição OTT¶
Apêndice para a operação de instalações de grande porte: como funciona o cache das respostas OTT do Perfect Streamer e como colocar um reverse proxy de cache ou uma CDN à frente do nó. Os modos de distribuição, os formatos de URL e a autorização são descritos na seção principal OTT e DVR.
O servidor forma respostas de três categorias, que diferem no tempo de vida do conteúdo e na aptidão para o cache por nós intermediários (reverse proxy, CDN, cache do cliente).
1. Modelo de cache¶
1.1. Recursos e cabeçalhos HTTP¶
Recurso |
URL |
Content-Type |
Cache-Control |
|---|---|---|---|
Segmento TS (HLS) |
|
|
|
Segmento VTT (legendas) |
|
|
|
Segmento fMP4 (DASH / LL-HLS) |
|
|
|
DASH MPD |
|
|
|
HLS master |
|
|
|
HLS media |
|
|
|
302 Redirect |
|
— |
|
Raw TS |
|
|
não definido; não é armazenado em cache |
1.2. Características dos segmentos¶
O identificador hexadecimal do segmento no URL (<keyHex> nos caminhos /h<sess>/<keyHex>.ts) é formado como o CRC64 do horário inicial do segmento e do ID do fluxo, sendo globalmente único. O URL do segmento endereça um conteúdo imutável — em requisições repetidas do mesmo URL é retornado um fluxo de bytes idêntico (enquanto o segmento permanecer dentro da janela deslizante).
A diretiva immutable suprime a revalidação condicional pelo cliente (If-None-Match, If-Modified-Since). O valor max-age=60 garante a compatibilidade com um timeShiftBufferDepth=40s típico.
Os segmentos fMP4 CMAF (.m4s) e o init.mp4 comum para DASH / Low-Latency HLS são endereçados de forma análoga e armazenados em cache segundo o mesmo modelo (immutable, max-age=60). Os segmentos parciais (parts) do LL-HLS são requisições byte-range dentro do mesmo .m4s e, por isso, não formam uma entrada separada no cache.
1.3. Características dos manifestos¶
max-age=1 limita a um segundo o limite superior de desatualização do conteúdo no cache. Em uso conjunto com proxy_cache_lock on (nginx), os picos de requisições ao manifesto são coalescidos em uma requisição ao origin por segundo.
1.4. Variabilidade do conteúdo¶
Com absPath=0 (valor padrão, sem o parâmetro de URL a) os manifestos HLS media e o DASH MPD não contêm o identificador da sessão no corpo. O conteúdo do manifesto é idêntico entre sessões pertencentes à mesma combinação (stream, param). Isso permite ao cache do reverse proxy reutilizar a entrada entre sessões quando a chave de cache é normalizada.
Com absPath=1 (parâmetro de URL a=1) o corpo do manifesto contém URLs absolutos que incluem o esquema, o host e o identificador da sessão. O conteúdo torna-se específico da sessão e a reutilização do cache entre sessões deixa de ser possível.
2. Comportamento dos clientes¶
Cliente |
URL de atualização do manifesto |
Efeito sobre a quantidade de sessões |
|---|---|---|
Players HLS pela media playlist |
|
Uma sessão por sessão de reprodução |
Players DASH pelo URL raiz |
|
Tratado pelo session reuse (ver 3.2) |
Players de navegador (MSE) |
|
Uma sessão por sessão de reprodução |
3. Mecanismos especiais¶
3.1. HTTP 302 Redirect para DASH¶
Uma requisição da forma /dash/<stream>/<login>/<pass>/index.mpd retorna a resposta 302 Found com o cabeçalho Location: /h<sess>/index.mpd. O corpo da resposta é vazio. A autorização e a alocação da sessão são executadas na etapa de processamento do redirecionamento.
Os clientes que suportam o cache do redirecionamento acessam o session URL diretamente nas requisições subsequentes. Os clientes que não o suportam repetem a requisição de redirecionamento. O custo do reprocessamento do redirecionamento limita-se à verificação de autenticação e às operações de session reuse.
3.2. Session reuse para DASH¶
Ao processar a requisição /dash/.../index.mpd do mesmo login para o mesmo fluxo (com o mesmo indicador adaptive), o servidor encontra uma sessão DASH já existente e retorna novamente o seu identificador. Nenhuma sessão nova é criada e nenhum slot do limite de conexões simultâneas é consumido. São reutilizadas apenas as sessões no mesmo modo de reprodução: uma sessão ao vivo e uma sessão VOD de arquivo (parâmetros t/d/epg) não são unificadas.
Aplica-se somente ao DASH. Para o HLS não é necessário um mecanismo de reuse separado: os clientes HLS atualizam a media playlist pelo session URL e não criam uma sessão nova a cada atualização.
3.3. Reutilização de segmentos entre sessões¶
O caminho /h<sess>/<keyHex>.ts não depende de <sess> na resolução de <keyHex> para o conteúdo: <keyHex> identifica de forma globalmente inequívoca o segmento TS dentro do fluxo. O Nginx com uma cache key normalizada (que remove o prefixo /h<sess>/) atende todas as requisições do mesmo <keyHex> a partir de uma única entrada de cache, independentemente de quais clientes as emitiram.
A deduplicação é aplicável somente aos nomes content-addressed — segmentos com identificador hexadecimal de 16 caracteres (.ts, .m4s com <keyHex>, .vtt). Os segmentos numerados do live-DASH (<número>.m4s), o init.mp4 e os manifestos são únicos apenas dentro da própria sessão — a sua cache key deve preservar o prefixo /h<sess>/, caso contrário o cache entregará o conteúdo de um fluxo aos espectadores de outro. Para o DASH, a deduplicação dos manifestos entre clientes do mesmo login é assegurada pelo session reuse (ver 3.2).
4. Parâmetros da requisição¶
Parâmetro |
Valor padrão |
Efeito |
|---|---|---|
|
|
|
|
|
|
|
|
Comprimento mínimo da janela para a emissão do manifesto |
|
|
|
|
ausente (off) |
opt-in para HTTP/3 (QUIC): faz o servidor emitir |
A alteração de um parâmetro pela query string atualiza os valores armazenados na sessão na sua próxima reabertura. Os parâmetros de reprodução do arquivo t, d e epg são descritos em Reprodução do arquivo (VOD).
5. Características de carga¶
A carga sobre o origin escala com a quantidade de fluxos distintos assistidos simultaneamente. O aumento do número de clientes que assistem ao mesmo fluxo não aumenta a quantidade de requisições ao origin quando existe um cache de reverse proxy e uma cache key normalizada.
Cenário |
Frequência de requisições ao origin (ref.) |
|---|---|
1 cliente no fluxo X |
MPD: 0.4 req/s, segment: 0.2 req/s |
N clientes em um único fluxo X (cache ativado) |
MPD: 1 req/s, segment: 0.2 req/s |
N clientes DASH em modo de repetição em um único fluxo |
MPD: 1 req/s (com |
N clientes em N fluxos distintos |
MPD: 0.4·N req/s, segment: 0.2·N req/s |
Os segmentos são deduplicados globalmente pelos nomes content-addressed; a deduplicação dos manifestos entre clientes é assegurada pelo session reuse (DASH) e pelo cache dentro da sessão.
6. Nginx como reverse proxy de cache¶
6.1. Configuração básica¶
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;
}
}
A porta do origin no exemplo é a porta padrão do servidor HTTP de streaming (43972; HTTPS — 43982). São normalizados apenas os nomes content-addressed dos segmentos (ver 3.3); os manifestos, o init.mp4 e os segmentos numerados são armazenados em cache pelo URI completo da sua sessão. A diretiva proxy_ignore_headers Vary no location dos segmentos é justificada: o servidor acompanha as respostas OTT com o cabeçalho Vary: Origin e, sem ela, o cache seria dividido em variantes conforme o valor de Origin do cliente (ver 7).
6.2. Finalidade das diretivas¶
Diretiva |
Finalidade |
|---|---|
|
Serializa a execução das requisições upstream em cache miss simultâneos pela mesma chave |
|
Retorna a cópia desatualizada às requisições paralelas durante o período de atualização do cache |
|
Utiliza |
|
Proíbe o cache dos erros de autorização e dos 404 |
|
Mantém um pool de conexões persistentes com o origin |
|
Para os segmentos; ativa a bufferização da resposta no nginx |
|
Para a seção |
6.3. Cálculo do max_size do cache de segmentos¶
Valor aproximado: bitrate × timeShiftBufferDepth × distinct_streams × 2
Exemplo: 10 fluxos × 8 Mbps × 40 s × 2 ≈ 800 MB. Recomenda-se prever uma margem de 10x para levar em conta a variabilidade do bitrate.
6.4. Terminação TLS¶
O servidor Perfect Streamer aceita conexões nas portas HTTP e HTTPS. Com a terminação TLS no nginx, o upstream utiliza a porta HTTP. O encaminhamento dos cabeçalhos X-Forwarded-Proto e X-Forwarded-Host é obrigatório para a formação correta dos URLs absolutos com 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;
}
Com HTTPS entre o nginx e o origin aplicam-se as diretivas proxy_ssl_verify e proxy_ssl_trusted_certificate. Para conexões de loopback a criptografia é redundante.
6.5. Vários hosts¶
Ao atender vários server_name a partir de um único processo nginx, $host é adicionado à cache key para isolar o conteúdo:
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";
}
O tamanho de keys_zone é calculado como 8000 chaves/MB. Para instalações multi-host com milhares de fluxos recomenda-se keys_zone=...:300m ou superior.
7. Cache no lado do cliente¶
Cache-Control: immutable é processado pelos navegadores modernos. O cache do cliente retorna o segmento sem requisição condicional em um acesso repetido (inclusive em um seek para trás dentro do buffer do player).
Os Service Workers podem aplicar a estratégia cache-first com base no conteúdo de Cache-Control. Os players DASH utilizam MSE através de SourceBuffer; um segmento colocado no buffer permanece disponível sem uma nova requisição HTTP até sair do limite de deslizamento.
Para requisições entre domínios o servidor entrega Access-Control-Allow-Origin: * junto com o cabeçalho Vary: Origin, Access-Control-Request-Headers. Um proxy de cache que respeita Vary divide as entradas em variantes conforme o valor de Origin do cliente (os players de navegador enviam Origin, os de console não), o que reduz a HIT rate. Como o ACAO é o universal «*», é seguro adicionar proxy_ignore_headers Vary no location dos segmentos (ver 6.1).
8. Distribuição através de CDN¶
O Perfect Streamer é compatível com CDN no modo pull-from-origin.
Origin shield. Recomenda-se a colocação de um ou vários nós shield entre o CDN edge e o origin para reduzir a frequência de requisições ao origin quando os clientes estão distribuídos globalmente.
Purge. Os segmentos content-addressed não requerem purge. Quando os metadados do fluxo mudam (codec, resolução), os manifestos são atualizados dentro de max-age=1 sem purge explícito.
Cache warming. Quando se espera um aumento de carga sobre um fluxo específico, é admissível o aquecimento da CDN a partir de vários pontos geográficos antes do início da transmissão.
Distribuição geográfica. Os segmentos (max-age=60) são bem adequados ao cache geograficamente distribuído. Os manifestos (max-age=1) admitem um atraso de entrega de até um segundo — aceitável para a transmissão live sem baixa latência.
9. Monitoramento¶
9.1. X-Cache-Status¶
Adição de add_header X-Cache-Status $upstream_cache_status; a cada location com cache. Valores:
Valor |
Descrição |
|---|---|
|
Resposta a partir do cache |
|
Ausente no cache, obtido do origin e armazenado |
|
Expirado, atualizado |
|
Cópia stale entregue a uma requisição paralela durante a atualização |
|
|
|
O origin retornou 304 Not Modified |
|
|
9.2. Formato do 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. Métricas¶
O módulo nginx-vts exporta métricas per-zone no formato Prometheus:
GET /status/format/prometheus
Limiares recomendados para alertas:
Métrica |
Limiar |
Causa possível |
|---|---|---|
Segment HIT rate |
< 90% em 5 minutos |
Normalização da cache key comprometida; |
Manifest MISS rate |
> 50% em 1 minuto |
|
Upstream response time p95 |
> 500 ms em 1 minuto |
Sobrecarga do origin |
Cache zone fill |
> 90% em 10 minutos |
Aproximação do |
A qualidade da entrega no lado do nó é visível nas métricas de sessão na tela «Clientes» (Clientes).
10. Diagnóstico¶
Sintoma |
Causa provável |
Solução |
|---|---|---|
Segment HIT rate baixo |
|
Adicionar |
404 nos segmentos após a saída da janela |
404 armazenado em cache para um segmento que saiu da sliding window |
Adicionar |
Atraso de playback start de 2–5 s |
|
Reduzir para 1–2 s; ativar |
Manifest não é atualizado |
|
Indicar explicitamente |
Crescimento de TIME_WAIT no upstream |
Ausência de |
Adicionar |
403 em |
O cliente resolve os URLs relativos a partir do URL anterior ao redirecionamento |
O servidor emite |
Travamentos e rebuffering frequente em clientes remotos |
Throughput TCP efetivo baixo devido a slow start e idle restart com RTT elevado (300 ms e acima) |
Ajuste da pilha de rede do Linux no origin: ver 10.1 |
10.1. Ajuste TCP do origin para clientes de alto RTT¶
O problema manifesta-se em clientes com RTT elevado até o origin (por exemplo, 300 ms e acima) quando o bitrate do fluxo está próximo da capacidade do canal. Os sintomas no player são rebuffering frequente e interrupções; no servidor, o cliente aparenta estar normal e não há erros.
Causa:
TCP slow start. Cada nova conexão TCP começa com uma congestion window de cerca de 14 KB e aumenta-a ao longo de vários RTT. Com RTT de 300 ms, atingir a janela completa leva 2–3 segundos. Nesse tempo, um segmento HLS/DASH com duração de 5 s (4–6 MB) é baixado sensivelmente mais devagar do que em tempo real.
TCP idle restart. Entre as requisições de segmentos, o cliente no modelo pull do HLS faz uma pausa de 4–5 s. Por padrão, após tal pausa o kernel do Linux redefine a congestion window da conexão de volta ao initial cwnd (comportamento de
net.ipv4.tcp_slow_start_after_idle=1). Como resultado, no GET seguinte a conexão keep-alive inicia a transmissão a partir do slow start novamente — mesmo em uma sessão já aquecida.
Adicionalmente, a situação é agravada pelo fato de que o congestion control CUBIC padrão lida mal com RTT longos e com perdas de pacotes em trechos intermediários da rede.
A solução consiste em dois parâmetros sysctl no 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
Para aplicação permanente:
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
O efeito principal vem do primeiro parâmetro (tcp_slow_start_after_idle=0) — ele elimina diretamente o slow start repetido entre as requisições de segmentos dentro de uma mesma conexão keep-alive. O segundo (BBR) confere robustez adicional e aplica-se a todas as novas conexões. O ajuste não requer a reinicialização do Perfect Streamer. Aos clientes HTTP/3 (QUIC) esses parâmetros não se aplicam: o QUIC funciona sobre UDP com o seu próprio controle de congestionamento.
11. Segurança¶
11.1. Session URL¶
Um URL do formato /h<sess>/... desempenha a função de token de sessão — não requer autenticação repetida. O tempo de vida é limitado por um timeout de inatividade de 60 segundos; na ausência de atividade a sessão é removida automaticamente.
Requisitos:
HTTPS para todos os caminhos OTT (
/hls/,/dash/,/h<sess>/) em produção;O Session ID no cabeçalho
Locationda resposta 302 não é armazenado em cache (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;
}
}
O Session URL (/h<sess>/) não requer rate limiting — o processamento é barato e as respostas são armazenadas em cache.
11.3. Cache das respostas com erro¶
proxy_cache_valid 200 60s;
proxy_cache_valid 301 302 0s;
proxy_cache_valid 404 403 0s;
proxy_cache_valid any 1s;
Proíbe o cache dos redirecionamentos (sess único em Location) e das respostas com erros de autorização ou de recurso inexistente.
11.4. Restrição do acesso de rede ao origin¶
A porta do servidor HTTP de streaming (43972 por padrão; 43982 para HTTPS) deve estar fechada ao tráfego externo. Configurações admissíveis:
Bind do Perfect Streamer em
127.0.0.1(com nginx local)Regra de firewall:
iptables -A INPUT -p tcp -m multiport --dports 43972,43982 ! -s 10.0.0.0/8 -j DROP
12. Integração com middleware¶
12.1. Modelo prefix-login¶
O Perfect Streamer suporta a delegação da identificação do usuário a um sistema de middleware através do mecanismo prefix-login — uma alternativa integrada à integração completa com o faturamento externo (as contas verificadas por um sistema externo são configuradas na tela «Usuários / Logins», aba «Remoto (faturação)», Usuários / Logins).
No login local é ativado o indicador de prefixo, após o que o servidor aceita URLs com um login da forma <prefix><id_do_assinante>:
/dash/test1/sub42/xxx/index.mpd
/hls/test1/sub43/xxx/index.m3u8
Aqui sub é o prefixo de login configurado e 42/43 são os identificadores dos assinantes do middleware; a senha é comum.
12.2. Estatísticas por assinante¶
Na lista de clientes, cada sessão carrega tanto o login de configuração quanto o login de URL real do assinante (match-login), pelo qual o middleware associa as sessões aos seus próprios assinantes. Os dados são visíveis na tela «Clientes» (Clientes).
12.3. Limitações do prefix-login¶
Senha comum. Todos os assinantes do pool de prefixo utilizam um único valor de senha. O comprometimento da senha concede acesso a qualquer
<prefix><cadeia>.Granularidade de acesso. A lista de fluxos permitidos aplica-se a todo o pool de prefixo. O acesso ao nível de um assinante individual é assegurado pela integração com o faturamento externo.
Rotação da senha. A alteração da senha desconecta todos os assinantes ativos. Para uma substituição gradual é necessário o uso temporário de dois prefix-logins.