Mise en cache et CDN pour la diffusion OTT¶
Annexe destinée à l’exploitation des grandes installations : comment fonctionne la mise en cache des réponses OTT de Perfect Streamer et comment placer un proxy inverse avec cache ou un CDN devant le nœud. Les modes de diffusion, les formats d’URL et l’autorisation sont décrits dans la section principale OTT et DVR.
Le serveur génère des réponses de trois catégories, qui diffèrent par la durée de vie du contenu et par leur aptitude à la mise en cache par les nœuds intermédiaires (proxy inverse, CDN, cache client).
1. Modèle de mise en cache¶
1.1. Ressources et en-têtes HTTP¶
Ressource |
URL |
Content-Type |
Cache-Control |
|---|---|---|---|
Segment TS (HLS) |
|
|
|
Segment VTT (sous-titres) |
|
|
|
Segment fMP4 (DASH / LL-HLS) |
|
|
|
DASH MPD |
|
|
|
HLS master |
|
|
|
HLS media |
|
|
|
302 Redirect |
|
— |
|
Raw TS |
|
|
non défini ; non mis en cache |
1.2. Caractéristiques des segments¶
L’identifiant hexadécimal du segment dans l’URL (<keyHex> dans les chemins /h<sess>/<keyHex>.ts) est calculé comme le CRC64 de l’heure de début du segment et de l’ID du flux, et il est globalement unique. L’URL d’un segment adresse un contenu immuable — les requêtes répétées sur la même URL renvoient un flux d’octets identique (tant que le segment reste dans les limites de la fenêtre glissante).
La directive immutable supprime la revalidation conditionnelle par le client (If-None-Match, If-Modified-Since). La valeur max-age=60 assure la compatibilité avec une valeur timeShiftBufferDepth=40s typique.
Les segments fMP4 CMAF (.m4s) et le fichier init.mp4 commun pour DASH / Low-Latency HLS sont adressés de la même manière et mis en cache selon le même modèle (immutable, max-age=60). Les segments partiels (parts) de LL-HLS sont des requêtes byte-range à l’intérieur du même .m4s et ne créent donc pas d’entrée de cache distincte.
1.3. Caractéristiques des manifestes¶
max-age=1 limite à une seconde la borne supérieure d’obsolescence du contenu dans le cache. En combinaison avec proxy_cache_lock on (nginx), les rafales de requêtes vers le manifeste sont regroupées en une seule requête par seconde vers l’origine.
1.4. Variabilité du contenu¶
Avec absPath=0 (valeur par défaut, paramètre d’URL a absent), les manifestes HLS media et le MPD DASH ne contiennent pas l’identifiant de session dans leur corps. Le contenu du manifeste est identique entre les sessions appartenant à une même combinaison (stream, param). Cela permet au cache du proxy inverse de réutiliser une entrée entre plusieurs sessions lorsque la clé de cache est normalisée.
Avec absPath=1 (paramètre d’URL a=1), le corps du manifeste contient des URL absolues incluant le schéma, l’hôte et l’identifiant de session. Le contenu devient spécifique à la session et la réutilisation du cache entre sessions n’est plus possible.
2. Comportement des clients¶
Client |
URL de rafraîchissement du manifeste |
Incidence sur le nombre de sessions |
|---|---|---|
Lecteurs HLS via la media playlist |
|
Une session par session de lecture |
Lecteurs DASH via l’URL racine |
|
Traité par la réutilisation de session (voir 3.2) |
Lecteurs en navigateur (MSE) |
|
Une session par session de lecture |
3. Mécanismes spécifiques¶
3.1. Redirection HTTP 302 pour DASH¶
Une requête de la forme /dash/<stream>/<login>/<pass>/index.mpd renvoie une réponse 302 Found avec l’en-tête Location: /h<sess>/index.mpd. Le corps de la réponse est vide. L’autorisation et l’allocation de la session sont effectuées lors du traitement de la redirection.
Les clients qui prennent en charge la mise en cache de la redirection s’adressent directement à l’URL de session dans les requêtes suivantes. Les clients qui ne la prennent pas en charge répètent la requête de redirection. Le coût du retraitement de la redirection se limite à la vérification de l’authentification et aux opérations de réutilisation de session.
3.2. Réutilisation de session pour DASH¶
Lors du traitement d’une requête /dash/.../index.mpd provenant du même identifiant vers le même flux (avec le même indicateur adaptive), le serveur retrouve une session DASH déjà existante et renvoie de nouveau son identifiant. Aucune nouvelle session n’est créée et aucun emplacement de la limite de connexions simultanées n’est consommé. Seules les sessions dans le même mode de lecture sont réutilisées : une session en direct et une session VOD d’archive (paramètres t/d/epg) ne sont pas fusionnées.
S’applique uniquement à DASH. Pour HLS, aucun mécanisme de réutilisation distinct n’est nécessaire : les clients HLS rafraîchissent la media playlist via l’URL de session et ne créent pas de nouvelle session à chaque rafraîchissement.
3.3. Réutilisation des segments entre sessions¶
Le chemin /h<sess>/<keyHex>.ts ne dépend pas de <sess> lors de la résolution de <keyHex> en contenu : <keyHex> identifie de manière globalement univoque un segment TS au sein du flux. Nginx doté d’une clé de cache normalisée (qui supprime le préfixe /h<sess>/) sert toutes les requêtes portant sur un même <keyHex> depuis une entrée de cache unique, quels que soient les clients qui les ont émises.
La déduplication ne s’applique qu’aux noms adressables par contenu — les segments dotés d’un identifiant hexadécimal de 16 caractères (.ts, .m4s avec <keyHex>, .vtt). Les segments numérotés du DASH live (<numéro>.m4s), init.mp4 et les manifestes ne sont uniques qu’au sein de leur propre session — leur clé de cache doit conserver le préfixe /h<sess>/, faute de quoi le cache servira le contenu d’un flux aux spectateurs d’un autre. Pour DASH, la déduplication des manifestes entre les clients d’un même identifiant est assurée par le session reuse (voir 3.2).
4. Paramètres de requête¶
Paramètre |
Valeur par défaut |
Effet |
|---|---|---|
|
|
|
|
|
|
|
|
Longueur minimale de la fenêtre requise pour servir un manifeste |
|
|
|
|
absent (off) |
opt-in pour HTTP/3 (QUIC) : force le serveur à émettre |
La modification d’un paramètre via la query string met à jour les valeurs enregistrées dans la session lors de sa prochaine réouverture. Les paramètres de lecture de l’archive t, d et epg sont décrits dans Lecture de l’archive (VOD).
5. Caractéristiques de charge¶
La charge sur l’origine évolue avec le nombre de flux distincts regardés simultanément. L’augmentation du nombre de clients regardant un même flux n’accroît pas le nombre de requêtes vers l’origine dès lors qu’un cache de proxy inverse et une clé de cache normalisée sont en place.
Scénario |
Fréquence des requêtes vers l’origine (réf.) |
|---|---|
1 client sur le flux X |
MPD: 0.4 req/s, segment: 0.2 req/s |
N clients sur un même flux X (cache activé) |
MPD: 1 req/s, segment: 0.2 req/s |
N clients DASH en boucle de rafraîchissement sur un même flux |
MPD: 1 req/s (avec |
N clients sur N flux distincts |
MPD: 0.4·N req/s, segment: 0.2·N req/s |
Les segments sont dédupliqués globalement d’après leurs noms adressables par contenu ; la déduplication des manifestes entre clients est assurée par la réutilisation de session (DASH) et par la mise en cache au sein de la session.
6. Nginx comme proxy inverse avec cache¶
6.1. Configuration de base¶
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;
}
}
Le port de l’origine dans l’exemple est le port par défaut du serveur HTTP de diffusion (43972 ; HTTPS — 43982). Seuls les noms de segments adressables par contenu sont normalisés (voir 3.3) ; les manifestes, init.mp4 et les segments numérotés sont mis en cache d’après l’URI complète de leur session. La directive proxy_ignore_headers Vary dans le location des segments est justifiée : le serveur accompagne les réponses OTT de l’en-tête Vary: Origin, et sans elle le cache se scinderait en variantes selon la valeur Origin du client (voir 7).
6.2. Rôle des directives¶
Directive |
Rôle |
|---|---|
|
Sérialise l’exécution des requêtes upstream lors de cache miss simultanés sur une même clé |
|
Renvoie une copie périmée aux requêtes parallèles pendant la mise à jour du cache |
|
Utilise |
|
Interdit la mise en cache des erreurs d’autorisation et des 404 |
|
Maintient un pool de connexions persistantes vers l’origine |
|
Pour les segments ; active la mise en tampon de la réponse dans nginx |
|
Pour la section |
6.3. Calcul de max_size du cache de segments¶
Valeur indicative : bitrate × timeShiftBufferDepth × distinct_streams × 2
Exemple : 10 flux × 8 Mbps × 40 s × 2 ≈ 800 MB. Il est recommandé de prévoir une marge de 10x pour tenir compte de la variabilité du débit.
6.4. Terminaison TLS¶
Le serveur Perfect Streamer accepte les connexions sur les ports HTTP et HTTPS. En cas de terminaison TLS sur nginx, l’upstream utilise le port HTTP. La retransmission des en-têtes X-Forwarded-Proto et X-Forwarded-Host est obligatoire pour former correctement les URL absolues lorsque 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;
}
Lorsque HTTPS est utilisé entre nginx et l’origine, les directives proxy_ssl_verify et proxy_ssl_trusted_certificate s’appliquent. Pour les connexions loopback, le chiffrement est superflu.
6.5. Multihôte¶
Lorsque plusieurs server_name sont servis par un même processus nginx, $host est ajouté à la clé de cache afin d’isoler les contenus :
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";
}
La taille de keys_zone se calcule sur la base de 8000 clés/MB. Pour les installations multihôtes comptant des milliers de flux, il est recommandé d’utiliser keys_zone=...:300m ou davantage.
7. Mise en cache côté client¶
Cache-Control: immutable est pris en charge par les navigateurs modernes. Le cache client restitue le segment sans requête conditionnelle lors d’un accès répété (y compris lors d’un seek arrière dans les limites du tampon du lecteur).
Les Service Workers peuvent appliquer une stratégie cache-first en s’appuyant sur le contenu de Cache-Control. Les lecteurs DASH utilisent MSE via SourceBuffer ; un segment placé dans le tampon reste disponible sans nouvelle requête HTTP jusqu’à ce qu’il sorte de la fenêtre glissante.
Pour les requêtes inter-domaines, le serveur renvoie Access-Control-Allow-Origin: * accompagné de l’en-tête Vary: Origin, Access-Control-Request-Headers. Un proxy cache qui respecte Vary scinde les entrées en variantes selon la valeur Origin du client (les lecteurs en navigateur envoient Origin, les lecteurs en console non), ce qui réduit le taux de HIT. Comme l’ACAO est un « * » universel, il est sans risque d’ajouter proxy_ignore_headers Vary dans le location des segments (voir 6.1).
8. Déploiement via un CDN¶
Perfect Streamer est compatible avec les CDN en mode pull-from-origin.
Origin shield. Il est recommandé de placer un ou plusieurs nœuds shield entre les edge du CDN et l’origine afin de réduire la fréquence des requêtes vers l’origine lorsque les clients sont répartis à l’échelle mondiale.
Purge. Les segments adressables par contenu ne nécessitent pas de purge. En cas de modification des métadonnées du flux (codec, résolution), les manifestes sont mis à jour dans le délai max-age=1 sans purge explicite.
Préchauffage du cache. Lorsqu’une hausse de charge est attendue sur un flux donné, il est possible de préchauffer le CDN depuis plusieurs points géographiques avant le début de la diffusion.
Répartition géographique. Les segments (max-age=60) se prêtent bien à une mise en cache géographiquement répartie. Les manifestes (max-age=1) tolèrent un délai de livraison allant jusqu’à une seconde — ce qui est acceptable pour une diffusion en direct sans faible latence.
9. Supervision¶
9.1. X-Cache-Status¶
Ajouter add_header X-Cache-Status $upstream_cache_status; dans chaque location où la mise en cache est active. Valeurs :
Valeur |
Description |
|---|---|
|
Réponse servie depuis le cache |
|
Absent du cache, récupéré depuis l’origine et enregistré |
|
Périmé, mis à jour |
|
Copie périmée servie à une requête parallèle pendant la mise à jour |
|
|
|
L’origine a renvoyé 304 Not Modified |
|
|
9.2. Format de l’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étriques¶
Le module nginx-vts exporte les métriques par zone au format Prometheus:
GET /status/format/prometheus
Seuils recommandés pour les alertes :
Métrique |
Seuil |
Cause possible |
|---|---|---|
Segment HIT rate |
< 90 % sur 5 minutes |
Normalisation de la clé de cache incorrecte ; |
Manifest MISS rate |
> 50 % sur 1 minute |
|
Upstream response time p95 |
> 500 ms sur 1 minute |
Surcharge de l’origine |
Cache zone fill |
> 90 % sur 10 minutes |
Approche de |
La qualité de la diffusion côté nœud est visible dans les métriques de sessions de l’écran « Clients » (Clients).
10. Diagnostic¶
Symptôme |
Cause probable |
Solution |
|---|---|---|
Taux de HIT des segments faible |
|
Ajouter |
404 sur les segments après leur sortie de la fenêtre |
404 mis en cache pour un segment sorti de la sliding window |
Ajouter |
Délai de démarrage de la lecture de 2 à 5 s |
|
Réduire à 1–2 s ; activer |
Le manifeste ne se met pas à jour |
|
Indiquer explicitement |
Augmentation du nombre de TIME_WAIT sur l’upstream |
|
Ajouter |
403 sur |
Le client résout les URL relatives à partir de l’URL antérieure à la redirection |
Le serveur émet |
Saccades et rebuffering fréquent chez les clients distants |
Débit TCP effectif faible en raison du slow start et de l’idle restart avec des RTT élevés (300 ms et plus) |
Réglage de la pile réseau Linux sur l’origine : voir 10.1 |
10.1. Réglage TCP de l’origine pour les clients à RTT élevé¶
Le problème se manifeste chez les clients présentant un RTT élevé jusqu’à l’origine (par exemple 300 ms et plus) lorsque le débit du flux est proche de la capacité du canal. Les symptômes côté lecteur sont un rebuffering fréquent et des coupures ; côté serveur, le client paraît pourtant normal et aucune erreur n’est signalée.
Cause :
TCP slow start. Chaque nouvelle connexion TCP démarre avec une congestion window d’environ 14 Ko et l’augmente sur plusieurs RTT. Avec un RTT de 300 ms, atteindre la fenêtre complète prend 2 à 3 secondes. Pendant ce temps, un segment HLS/DASH d’une durée de 5 s (4 à 6 Mo) est téléchargé nettement plus lentement que le temps réel.
TCP idle restart. Entre les requêtes de segments, le client, selon le modèle pull de HLS, marque une pause de 4 à 5 s. Par défaut, après une telle pause, le noyau Linux ramène la congestion window de la connexion à l’initial cwnd (comportement
net.ipv4.tcp_slow_start_after_idle=1). Résultat : lors du GET suivant, la connexion keep-alive reprend la transmission depuis le slow start — même sur une session déjà montée en régime.
La situation est encore aggravée par le fait que le congestion control CUBIC utilisé par défaut supporte mal les RTT longs et les pertes de paquets sur les tronçons intermédiaires du réseau.
La solution consiste en deux paramètres sysctl sur l’origine :
# 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
Pour une application 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
L’effet principal provient du premier paramètre (tcp_slow_start_after_idle=0) : il élimine directement le slow start répété entre les requêtes de segments au sein d’une même connexion keep-alive. Le second (BBR) apporte une robustesse supplémentaire et s’applique à toutes les nouvelles connexions. Ce réglage ne nécessite pas le redémarrage de Perfect Streamer. Ces paramètres ne concernent pas les clients HTTP/3 (QUIC) : QUIC fonctionne au-dessus d’UDP avec son propre contrôle de congestion.
11. Sécurité¶
11.1. Session URL¶
L’URL de la forme /h<sess>/... fait office de jeton de session — elle n’exige pas de nouvelle authentification. Sa durée de vie est limitée par un délai d’inactivité de 60 secondes ; en l’absence d’activité, la session est supprimée automatiquement.
Exigences :
HTTPS pour tous les chemins OTT (
/hls/,/dash/,/h<sess>/) en production ;L’identifiant de session présent dans l’en-tête
Locationde la réponse 302 n’est pas mis en 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;
}
}
L’URL de session (/h<sess>/) ne nécessite pas de rate limiting — son traitement est peu coûteux et les réponses sont mises en cache.
11.3. Mise en cache des réponses en erreur¶
proxy_cache_valid 200 60s;
proxy_cache_valid 301 302 0s;
proxy_cache_valid 404 403 0s;
proxy_cache_valid any 1s;
Interdit la mise en cache des redirections (sess unique dans Location) ainsi que des réponses en erreur d’autorisation ou de ressource absente.
11.4. Restriction de l’accès réseau à l’origine¶
Le port du serveur HTTP de diffusion (43972 par défaut ; 43982 pour HTTPS) doit être fermé au trafic externe. Configurations admissibles :
Lier Perfect Streamer à
127.0.0.1(lorsque nginx est local)Règle de pare-feu :
iptables -A INPUT -p tcp -m multiport --dports 43972,43982 ! -s 10.0.0.0/8 -j DROP
12. Intégration avec un middleware¶
12.1. Modèle prefix-login¶
Perfect Streamer prend en charge la délégation de l’identification de l’utilisateur à un système middleware via le mécanisme prefix-login — une alternative intégrée à une intégration complète avec une facturation externe (les comptes vérifiés par un système externe se configurent sur l’écran « Utilisateurs / Identifiants », onglet « Externes (facturation) », Utilisateurs / Identifiants).
Un indicateur de préfixe est activé sur un identifiant local, après quoi le serveur accepte des URL dont l’identifiant est de la forme <prefix><identifiant_abonné>:
/dash/test1/sub42/xxx/index.mpd
/hls/test1/sub43/xxx/index.m3u8
Ici, sub est le préfixe d’identifiant configuré, et 42/43 sont les identifiants d’abonnés du middleware ; le mot de passe est commun.
12.2. Statistiques par abonné¶
Dans la liste des clients, chaque session porte à la fois l’identifiant de configuration et l’identifiant d’URL effectif de l’abonné (match-login), grâce auquel le middleware associe les sessions à ses propres abonnés. Ces données sont visibles sur l’écran « Clients » (Clients).
12.3. Limitations de prefix-login¶
Mot de passe commun. Tous les abonnés du pool prefix utilisent la même valeur de mot de passe. La compromission du mot de passe donne accès à n’importe quel
<prefix><chaîne>.Granularité des accès. La liste des flux autorisés s’applique à l’ensemble du pool prefix. Un accès au niveau de chaque abonné est assuré par l’intégration avec une facturation externe.
Rotation du mot de passe. La modification du mot de passe déconnecte tous les abonnés actifs. Un remplacement progressif nécessite l’utilisation temporaire de deux identifiants prefix.