Loading...

Étude de cas

Sondages en direct à grande échelle : le motif cache-et-purge

Un sondage sur un direct est le pire schéma de lecture possible : tous les spectateurs veulent la même réponse à la même seconde, et dès que l'animateur ouvre une nouvelle question, ils redemandent tous. Interroger le backend en boucle n'y survit pas. Voici comment nous faisons tourner les sondages de yayında.tv et les interactions dans le lecteur de Videoonly avec environ une requête vers l'origine par changement, sans rien de plus exotique qu'une URL mise en cache et un appel de purge.

9 Intermédiaire Updated

Sondages en direct à grande échelle : le motif cache-et-purge

Pourquoi un sondage en direct casse l'approche évidente

L'implémentation évidente est un endpoint que la page appelle toutes les quelques secondes. Cela marche en test avec cinq personnes et s'effondre en production, parce qu'une audience en direct est synchronisée comme le trafic web ordinaire ne l'est jamais.

Tout le monde regarde le même instant. L'animateur ouvre une question et trente mille navigateurs la demandent dans la même seconde. Ils veulent tous une réponse identique : vous exécutez donc la même requête trente mille fois et renvoyez les mêmes octets. Ensuite ils continuent de demander toutes les quelques secondes pendant le reste de l'émission, que quelque chose ait changé ou non.

L'échappatoire habituelle, ce sont les WebSockets : garder une connexion par spectateur et pousser. Cela fonctionne, mais vous avez pris en charge l'état de connexion, la mise à l'échelle d'une couche de sockets, les tempêtes de reconnexion quand un réseau mobile hoquette, et un chemin de repli pour les clients incapables de tenir une connexion.

Il existe une option plus simple, et c'est celle que nous exploitons en production.

Le motif : une URL en cache, purgée à chaque changement

L'état d'un sondage est petit, identique pour tout le monde et change rarement : quelques fois par émission. C'est exactement la forme pour laquelle un CDN est fait.

Publiez-le donc comme une simple URL JSON cacheable. Les spectateurs demandent cette URL et le edge le plus proche répond. Votre origine voit la première requête de chaque emplacement et presque rien ensuite, quel que soit le nombre de spectateurs.

Quand l'animateur ouvre, ferme ou modifie une question, le backend purge cette unique URL. La requête suivante rate le cache, récupère le nouvel état une fois, et tous les spectateurs suivants sont de nouveau servis depuis le cache.

La propriété intéressante, c'est ce qui se passe quand l'audience grandit : rien. Dix mille spectateurs ou cent mille, l'origine voit toujours une requête par changement et par emplacement. Le coût suit la fréquence de changement du sondage, pas le nombre de spectateurs.

À quoi cela ressemble dans yayında.tv

yayında.tv est notre plateforme de diffusion en direct, et voici tout le mécanisme.

Côté lecture, un endpoint renvoie le sondage actif avec ses questions et réponses en JSON, servi via le CDN. Il envoie des en-têtes CORS permissifs pour que le lecteur puisse le récupérer depuis le domaine où la diffusion est intégrée. L'URL porte un hash dérivé du nom de la chaîne et d'un secret côté serveur, ce qui l'empêche d'être trivialement énumérable tout en restant parfaitement cacheable : l'URL est stable pour une chaîne donnée, c'est donc une vraie clé de cache et non une clé par utilisateur.

Côté écriture, quelques lignes dans le contrôleur qui démarre ou arrête un sondage : reconstruire la même URL, appeler l'API de purge, terminé. Pas de file d'attente, pas de diffusion, pas de couche de sockets — le CDN est la diffusion.

The two halves
// READ — what every viewer hits, cached at the edge
GET https://cdn.example.tv/{channel}/survey?s={hash}

{
  "status": "success",
  "survey": {
    "id": 42,
    "questions": [
      { "text": "Who takes the penalty?",
        "answers": [{"id": 1, "text": "..."}, {"id": 2, "text": "..."}] }
    ]
  }
}

// WRITE — the broadcaster opens the poll; the backend purges that one URL
cdnctl purge --account <account_uuid> \
             --path "/{channel}/survey?s={hash}" --type exact

// or straight from the app, on the same event that flips the poll live

Et dans le lecteur : Videoonly

Videoonly applique la même idée à l'intérieur du lecteur vidéo. Les interactions dans le lecteur — un calque de question, une carte sponsor, un appel à l'action calé sur un moment du direct — relèvent toutes du même problème : un petit morceau d'état dont tous les spectateurs ont besoin et qui change quand un opérateur décide qu'il change.

Le lecteur récupère donc sa configuration de calques depuis une URL en cache plutôt que d'interroger l'API sur un minuteur. Quand quelqu'un programme ou retire un calque, cette URL est purgée. Le lecteur le prend en compte à sa prochaine requête, et l'API n'est plus du tout sur le chemin critique.

Le même raisonnement vaut pour tout ce qui a ces propriétés : un tableau de scores, un bandeau « en cours de lecture », des feature flags pour un événement en direct, un compte à rebours qu'on peut écourter.

Bien gérer les détails

Quelques points décident si c'est fluide ou pénible en pratique.

**Gardez l'URL stable.** Tout ce qui est propre à l'utilisateur dans le chemin ou la query — un identifiant de session, un timestamp anti-cache — donne à chaque spectateur sa propre clé de cache et vous revoilà à frapper l'origine pour tout le monde. La personnalisation appartient à une seconde requête distincte, pas à la requête partagée.

**Fixez un TTL avec lequel vous pouvez vivre de toute façon.** C'est la purge qui rend les changements rapides, mais un TTL fini est votre filet de sécurité le jour où un appel de purge échoue. Assez court pour qu'une purge manquée soit un accroc, assez long pour que l'origine reste tranquille.

**Purgez exactement l'URL que vous servez.** Si l'objet en cache est `?s=abc` et que vous purgez le chemin sans la query, rien n'est invalidé et l'ancien sondage reste affiché. C'est la façon la plus courante dont ce motif échoue en silence.

**Séparez lectures et écritures.** Les spectateurs lisent l'URL en cache ; les votes vont vers un endpoint normal, non caché. Les écritures sont une fraction des lectures — un spectateur vote une fois et lit en continu — la voie d'écriture peut donc rester ordinaire.

**Ne mettez pas de secrets dans le hash de l'URL.** C'est de l'obscurcissement, pas de l'autorisation : de quoi arrêter l'énumération opportuniste, et cela doit rester compatible avec le cache. Tout ce qui a vraiment besoin d'être protégé n'a rien à faire dans un objet partagé et mis en cache.

Quand préférer les WebSockets

Ce motif n'est pas un remplacement universel, et il vaut mieux dire clairement où il s'arrête.

Il convient quand l'état est partagé par tous, petit, et change à une échelle de temps humaine : sondages, calques, tableaux de scores, flags. Il ne convient pas quand chaque spectateur a besoin de données différentes, quand les mises à jour sont continues plutôt qu'occasionnelles, ou quand il faut une livraison sous la seconde avec garanties d'ordre. Le chat en direct est le contre-exemple évident : propre à l'utilisateur, constant et sensible à la latence. Utilisez-y une couche de sockets.

Le cadrage honnête, c'est que la plupart des fonctionnalités « temps réel » d'une page de diffusion ne sont pas du temps réel du tout. Ce sont des changements occasionnels d'un état partagé, et une URL en cache plus une purge les traitent avec une fraction des pièces mobiles.

Questions fréquentes

En combien de temps les spectateurs voient-ils le changement ?

Le temps que la purge se propage, plus la prochaine requête du client. En pratique une à deux secondes — largement suffisant pour une émission, puisque l'animateur parle par-dessus la transition.

Que se passe-t-il si un appel de purge échoue ?

C'est à cela que sert le TTL. Une purge ratée signifie que le changement arrive à l'expiration de l'objet plutôt qu'immédiatement. Gardez le TTL assez court pour que ce soit un délai et non une panne, et journalisez les échecs de purge pour repérer une tendance.

Est-ce que cela marche avec une query string dans l'URL ?

Oui, tant que le CDN inclut la query string dans la clé de cache et que vous purgez exactement l'URL servie. Purger le seul chemin alors que l'objet est mis en cache avec une query string est l'erreur classique.

Où vont les votes ?

Vers un endpoint normal, non mis en cache. Seule la lecture partagée est cachée. Les votes représentent une part infime du trafic, car chaque spectateur vote une fois et lit en continu.

Est-ce moins cher que les WebSockets ?

Généralement oui, et la plus grosse économie est opérationnelle. Pas de couche de sockets à mettre à l'échelle, pas d'état de connexion à conserver, pas de tempête de reconnexions quand un réseau mobile lâche. Le coût d'origine suit la fréquence de changement du sondage, pas le nombre de spectateurs.