Almacenamiento en caché y CDN para la difusión OTT¶
Apéndice para la explotación de instalaciones de gran tamaño: cómo funciona el almacenamiento en caché de las respuestas OTT de Perfect Streamer y cómo situar un reverse proxy con caché o una CDN delante del nodo. Los modos de difusión, los formatos de URL y la autorización se describen en la sección principal OTT y DVR.
El servidor genera respuestas de tres categorías, que se diferencian por el tiempo de vida del contenido y por su idoneidad para el almacenamiento en caché por nodos intermedios (reverse proxy, CDN, caché del cliente).
1. Modelo de caché¶
1.1. Recursos y cabeceras HTTP¶
Recurso |
URL |
Content-Type |
Cache-Control |
|---|---|---|---|
Segmento TS (HLS) |
|
|
|
Segmento VTT (subtítulos) |
|
|
|
Segmento fMP4 (DASH / LL-HLS) |
|
|
|
DASH MPD |
|
|
|
HLS master |
|
|
|
HLS media |
|
|
|
302 Redirect |
|
— |
|
Raw TS |
|
|
no definido; no se almacena en caché |
1.2. Características de los segmentos¶
El identificador hexadecimal del segmento en el URL (<keyHex> en las rutas /h<sess>/<keyHex>.ts) se calcula como CRC64 del instante de inicio del segmento y del ID del flujo, y es globalmente único. El URL del segmento direcciona contenido inmutable: las solicitudes repetidas del mismo URL devuelven un flujo de bytes idéntico (mientras el segmento permanezca dentro de la ventana deslizante).
La directiva immutable suprime la revalidación condicional por parte del cliente (If-None-Match, If-Modified-Since). El valor max-age=60 garantiza la compatibilidad con el valor típico timeShiftBufferDepth=40s.
Los segmentos fMP4 CMAF (.m4s) y el init.mp4 común para DASH / Low-Latency HLS se direccionan de forma análoga y se almacenan en caché según el mismo modelo (immutable, max-age=60). Los segmentos parciales (parts) de LL-HLS son solicitudes byte-range dentro del mismo .m4s, por lo que no generan una entrada de caché propia.
1.3. Características de los manifiestos¶
max-age=1 limita a un segundo la cota superior de obsolescencia del contenido en la caché. Usado junto con proxy_cache_lock on (nginx), los picos de solicitudes al manifiesto se agrupan en una única solicitud al origin por segundo.
1.4. Variabilidad del contenido¶
Con absPath=0 (valor por defecto, sin el parámetro de URL a), los manifiestos HLS media y DASH MPD no contienen el identificador de sesión en el cuerpo. El contenido del manifiesto es idéntico entre las sesiones que pertenecen a una misma combinación (flujo, param). Esto permite que la caché del reverse proxy reutilice la entrada entre sesiones cuando se normaliza la clave de caché.
Con absPath=1 (parámetro de URL a=1), el cuerpo del manifiesto contiene URL absolutos que incluyen el esquema, el host y el identificador de sesión. El contenido pasa a ser específico de la sesión y no existe la posibilidad de reutilizar la caché entre sesiones.
2. Comportamiento de los clientes¶
Cliente |
URL de actualización del manifiesto |
Efecto sobre el número de sesiones |
|---|---|---|
Reproductores HLS mediante el media playlist |
|
Una sesión por sesión de reproducción |
Reproductores DASH mediante el URL raíz |
|
Se resuelve mediante session reuse (véase 3.2) |
Reproductores de navegador (MSE) |
|
Una sesión por sesión de reproducción |
3. Mecanismos especiales¶
3.1. HTTP 302 Redirect para DASH¶
Una solicitud de la forma /dash/<stream>/<login>/<pass>/index.mpd devuelve una respuesta 302 Found con la cabecera Location: /h<sess>/index.mpd. El cuerpo de la respuesta está vacío. La autorización y la asignación de la sesión se realizan durante el procesamiento de la redirección.
Los clientes que admiten el almacenamiento en caché de la redirección acceden directamente al session URL en las solicitudes posteriores. Los clientes que no lo admiten repiten la solicitud de redirección. El coste del procesamiento repetido de la redirección se limita a la comprobación de autenticación y a las operaciones de session reuse.
3.2. Session reuse para DASH¶
Al procesar una solicitud /dash/.../index.mpd procedente del mismo login y dirigida al mismo flujo (con el mismo indicador adaptive), el servidor localiza una sesión DASH ya existente y devuelve de nuevo su identificador. No se crea una sesión nueva ni se consume una plaza del límite de conexiones simultáneas. Solo se reutilizan sesiones que estén en el mismo modo de reproducción: una sesión en directo y una sesión VOD del archivo (parámetros t/d/epg) no se combinan.
Se aplica únicamente a DASH. Para HLS no se requiere un mecanismo de reuse independiente: los clientes HLS actualizan el media playlist mediante el session-URL y no crean una sesión nueva en cada actualización.
3.3. Reutilización de segmentos entre sesiones¶
La ruta /h<sess>/<keyHex>.ts no depende de <sess> al resolver <keyHex> en contenido: <keyHex> identifica de forma globalmente inequívoca un segmento TS dentro del flujo. Nginx con una cache key normalizada (que recorta el prefijo /h<sess>/) atiende todas las solicitudes de un mismo <keyHex> desde una única entrada de caché, con independencia de qué clientes las hayan emitido.
La deduplicación solo es aplicable a los nombres content-addressed: los segmentos con identificador hexadecimal de 16 caracteres (.ts, .m4s con <keyHex>, .vtt). Los segmentos numerados de live-DASH (<número>.m4s), el init.mp4 y los manifiestos son únicos solo dentro de su propia sesión: su clave de caché debe conservar el prefijo /h<sess>/, de lo contrario la caché entregaría el contenido de un flujo a los espectadores de otro. En DASH, la deduplicación de manifiestos entre los clientes de un mismo login la proporciona el session reuse (véase 3.2).
4. Parámetros de la solicitud¶
Parámetro |
Valor por defecto |
Efecto |
|---|---|---|
|
|
|
|
|
|
|
|
Longitud mínima de la ventana para emitir el manifiesto |
|
|
|
|
ausente (off) |
opt-in a HTTP/3 (QUIC): obliga al servidor a emitir |
La modificación de un parámetro mediante la query string actualiza los valores guardados en la sesión en su siguiente reapertura. Los parámetros de reproducción del archivo t, d y epg se describen en Reproducción del archivo (VOD).
5. Características de carga¶
La carga sobre el origin escala con el número de flujos distintos vistos simultáneamente. El aumento del número de clientes que ven un mismo flujo no incrementa la cantidad de solicitudes al origin cuando existe una caché de reverse proxy y una cache key normalizada.
Escenario |
Frecuencia de solicitudes al origin (ref.) |
|---|---|
1 cliente en el flujo X |
MPD: 0.4 req/s, segment: 0.2 req/s |
N clientes en un mismo flujo X (caché activada) |
MPD: 1 req/s, segment: 0.2 req/s |
N clientes DASH en modo de repetición sobre un mismo flujo |
MPD: 1 req/s (con |
N clientes en N flujos distintos |
MPD: 0.4·N req/s, segment: 0.2·N req/s |
Los segmentos se deduplican globalmente por sus nombres content-addressed; la deduplicación de manifiestos entre clientes la proporcionan el session reuse (DASH) y la caché dentro de la sesión.
6. Nginx como reverse proxy con caché¶
6.1. Configuración 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;
}
}
El puerto del origin en el ejemplo es el puerto por defecto del servidor HTTP de streaming (43972; HTTPS — 43982). Solo se normalizan los nombres content-addressed de los segmentos (véase 3.3); los manifiestos, el init.mp4 y los segmentos numerados se almacenan en caché por el URI completo de su sesión. La directiva proxy_ignore_headers Vary en el location de los segmentos está justificada: el servidor acompaña las respuestas OTT con la cabecera Vary: Origin y, sin ella, la caché se dividiría en variantes según el valor de Origin del cliente (véase 7).
6.2. Función de las directivas¶
Directiva |
Función |
|---|---|
|
Serializa la ejecución de las solicitudes upstream ante cache miss simultáneos sobre una misma clave |
|
Devuelve la copia obsoleta a las solicitudes paralelas durante el periodo de actualización de la caché |
|
Utiliza |
|
Prohíbe el almacenamiento en caché de los errores de autorización y de los 404 |
|
Mantiene un pool de conexiones persistentes con el origin |
|
Para los segmentos; activa el almacenamiento en búfer de la respuesta en nginx |
|
Para la sección |
6.3. Cálculo de max_size de la caché de segmentos¶
Valor orientativo: bitrate × timeShiftBufferDepth × distinct_streams × 2
Ejemplo: 10 flujos × 8 Mbps × 40 s × 2 ≈ 800 MB. Se recomienda prever un margen de 10x para tener en cuenta la variabilidad del bitrate.
6.4. Terminación TLS¶
El servidor Perfect Streamer acepta conexiones en los puertos HTTP y HTTPS. Cuando la terminación TLS se realiza en nginx, el upstream utiliza el puerto HTTP. El reenvío de las cabeceras X-Forwarded-Proto y X-Forwarded-Host es obligatorio para formar correctamente los URL absolutos con 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;
}
Si se emplea HTTPS entre nginx y el origin, se aplican las directivas proxy_ssl_verify y proxy_ssl_trusted_certificate. Para las conexiones loopback el cifrado es superfluo.
6.5. Multihost¶
Cuando un mismo proceso de nginx atiende varios server_name, se añade $host a la cache key para aislar el contenido:
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";
}
El tamaño de keys_zone se calcula a razón de 8000 claves/MB. Para instalaciones multihost con miles de flujos se recomienda keys_zone=...:300m o superior.
7. Almacenamiento en caché en el cliente¶
Cache-Control: immutable es procesado por los navegadores modernos. La caché del cliente devuelve el segmento sin solicitud condicional cuando se accede de nuevo a él (incluido el seek hacia atrás dentro del búfer del reproductor).
Los Service Workers pueden aplicar la estrategia cache-first basándose en el contenido de Cache-Control. Los reproductores DASH utilizan MSE a través de SourceBuffer; un segmento colocado en el búfer permanece disponible sin una nueva solicitud HTTP hasta que sale del límite de la ventana deslizante.
Para las solicitudes entre dominios, el servidor devuelve Access-Control-Allow-Origin: * junto con la cabecera Vary: Origin, Access-Control-Request-Headers. Un proxy con caché que respete Vary divide las entradas en variantes según el valor de Origin del cliente (los reproductores de navegador envían Origin; los de consola, no), lo que reduce el HIT rate. Dado que ACAO es el «*» universal, en el location de los segmentos es seguro añadir proxy_ignore_headers Vary (véase 6.1).
8. Distribución a través de CDN¶
Perfect Streamer es compatible con CDN en modo pull-from-origin.
Origin shield. Se recomienda situar uno o varios nodos shield entre el CDN edge y el origin para reducir la frecuencia de solicitudes al origin cuando los clientes están distribuidos globalmente.
Purge. Los segmentos content-addressed no requieren purge. Al cambiar los metadatos del flujo (códec, resolución), los manifiestos se actualizan en el plazo de max-age=1 sin purge explícito.
Cache warming. Si se prevé un aumento de la carga sobre un flujo concreto, es admisible precalentar la CDN desde varios puntos geográficos antes del inicio de la emisión.
Distribución geográfica. Los segmentos (max-age=60) se adaptan bien al almacenamiento en caché geográficamente distribuido. Los manifiestos (max-age=1) admiten un retardo de entrega de hasta un segundo, lo que resulta aceptable para la emisión en directo sin baja latencia.
9. Monitorización¶
9.1. X-Cache-Status¶
Se añade add_header X-Cache-Status $upstream_cache_status; a cada location con caché. Valores:
Valor |
Descripción |
|---|---|
|
Respuesta servida desde la caché |
|
No estaba en la caché; se obtuvo del origin y se almacenó |
|
Caducado; se actualizó |
|
Se entregó una copia stale a una solicitud paralela durante la actualización |
|
|
|
El origin devolvió 304 Not Modified |
|
Se activó |
9.2. Formato del 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¶
El módulo nginx-vts exporta métricas per-zone en formato Prometheus:
GET /status/format/prometheus
Umbrales recomendados para las alertas:
Métrica |
Umbral |
Causa posible |
|---|---|---|
Segment HIT rate |
< 90% durante 5 minutos |
Normalización de la cache key incorrecta; |
Manifest MISS rate |
> 50% durante 1 minuto |
|
Upstream response time p95 |
> 500 ms durante 1 minuto |
Sobrecarga del origin |
Cache zone fill |
> 90% durante 10 minutos |
Proximidad a |
La calidad de la entrega en el lado del nodo se observa en las métricas de sesión de la pantalla «Clientes» (Clientes).
10. Diagnóstico¶
Síntoma |
Causa probable |
Solución |
|---|---|---|
Segment HIT rate bajo |
|
Añadir |
404 en los segmentos tras salir de la ventana |
404 almacenado en caché de un segmento que salió de la sliding window |
Añadir |
Retardo del playback start de 2–5 s |
|
Reducirlo a 1–2 s; activar |
El manifest no se actualiza |
está definido |
Indicar explícitamente |
Crecimiento de TIME_WAIT en el upstream |
Falta |
Añadir |
403 en |
El cliente resuelve los URL relativos respecto al URL pre-redirect |
El servidor emite |
Cortes y rebúferes frecuentes en los clientes remotos |
Throughput TCP efectivo bajo debido al slow start y al idle restart con RTT elevados (300 ms o más) |
Ajuste de la pila de red de Linux en el origin: véase 10.1 |
10.1. Ajuste TCP del origin para clientes con RTT alto¶
El problema se manifiesta en los clientes con un RTT elevado hasta el origin (por ejemplo, 300 ms o más) cuando el bitrate del flujo se aproxima a la capacidad del canal. Los síntomas en el reproductor son rebúferes e interrupciones frecuentes; en el servidor, en cambio, el cliente se ve normal y no hay errores.
Causa:
TCP slow start. Cada nueva conexión TCP comienza con una congestion window de unos 14 KB y la incrementa a lo largo de varios RTT. Con un RTT de 300 ms, alcanzar la ventana completa lleva de 2 a 3 segundos. Durante ese tiempo, un segmento HLS/DASH de 5 s de duración (4–6 MB) se descarga notablemente más despacio que en tiempo real.
TCP idle restart. Entre las solicitudes de segmentos, el cliente hace una pausa de 4–5 s conforme al modelo pull de HLS. Por defecto, tras esa pausa el kernel de Linux restablece la congestion window de la conexión al initial cwnd (comportamiento
net.ipv4.tcp_slow_start_after_idle=1). Como resultado, en el siguiente GET la conexión keep-alive vuelve a iniciar la transmisión con arranque lento, incluso en una sesión ya calentada.
Adicionalmente, la situación se agrava porque el congestion control CUBIC, activo por defecto, tolera mal los RTT largos y las pérdidas de paquetes en los tramos intermedios de la red.
La solución consiste en dos parámetros sysctl en el 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 aplicarlos de forma 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
El efecto principal lo aporta el primer parámetro (tcp_slow_start_after_idle=0): elimina directamente el slow start repetido entre las solicitudes de segmentos dentro de una misma conexión keep-alive. El segundo (BBR) aporta robustez adicional y se aplica a todas las conexiones nuevas. El ajuste no requiere reiniciar Perfect Streamer. Estos parámetros no afectan a los clientes HTTP/3 (QUIC): QUIC funciona sobre UDP con su propio control de congestión.
11. Seguridad¶
11.1. Session URL¶
Un URL con el formato /h<sess>/... desempeña la función de token de sesión: no requiere una nueva autenticación. Su tiempo de vida está limitado por un tiempo de espera de inactividad de 60 segundos; si no hay actividad, la sesión se elimina automáticamente.
Requisitos:
HTTPS para todas las rutas OTT (
/hls/,/dash/,/h<sess>/) en producción;El Session ID de la cabecera
Locationde la respuesta 302 no se almacena en caché (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;
}
}
El session URL (/h<sess>/) no requiere rate limiting: su procesamiento es barato y las respuestas se almacenan en caché.
11.3. Almacenamiento en caché de las respuestas con errores¶
proxy_cache_valid 200 60s;
proxy_cache_valid 301 302 0s;
proxy_cache_valid 404 403 0s;
proxy_cache_valid any 1s;
Prohíbe el almacenamiento en caché de las redirecciones (sess único en Location) y de las respuestas con errores de autorización o de recurso inexistente.
11.4. Restricción del acceso de red al origin¶
El puerto del servidor HTTP de streaming (43972 por defecto; 43982 para HTTPS) debe estar cerrado al tráfico externo. Configuraciones admisibles:
Vincular Perfect Streamer a
127.0.0.1(con nginx local)Regla de firewall:
iptables -A INPUT -p tcp -m multiport --dports 43972,43982 ! -s 10.0.0.0/8 -j DROP
12. Integración con middleware¶
12.1. Modelo prefix-login¶
Perfect Streamer permite delegar la identificación del usuario en un sistema middleware mediante el mecanismo prefix-login, una alternativa integrada a la integración completa con una facturación externa (las cuentas verificadas por un sistema externo se configuran en la pantalla «Usuarios / Inicios de sesión», pestaña «Externos (facturación)», Usuarios / Inicios de sesión).
En un login local se activa el indicador de prefijo, tras lo cual el servidor acepta URL con un login de la forma <prefix><id_de_suscriptor>:
/dash/test1/sub42/xxx/index.mpd
/hls/test1/sub43/xxx/index.m3u8
Aquí sub es el prefijo de login configurado y 42/43 son los identificadores de los suscriptores del middleware; la contraseña es común.
12.2. Estadísticas por suscriptor¶
En la lista de clientes, cada sesión lleva tanto el login de configuración como el login de URL real del suscriptor (match-login), con el que el middleware asocia las sesiones a sus abonados. Los datos se muestran en la pantalla «Clientes» (Clientes).
12.3. Limitaciones de prefix-login¶
Contraseña común. Todos los suscriptores del pool de prefijo utilizan un único valor de contraseña. El compromiso de la contraseña otorga acceso a cualquier
<prefix><cadena>.Granularidad del acceso. La lista de flujos permitidos se aplica a todo el pool de prefijo. El acceso a nivel de suscriptor individual lo proporciona la integración con una facturación externa.
Rotación de la contraseña. El cambio de contraseña desconecta a todos los suscriptores activos. Para una sustitución gradual es necesario usar temporalmente dos prefix-logins.