Loading...

Caso práctico

Encuestas en directo a escala: el patrón cachear y purgar

Una encuesta en un directo es el peor patrón de lectura posible: todos los espectadores quieren la misma respuesta en el mismo segundo, y en cuanto el presentador abre una pregunta nueva todos vuelven a preguntar. Sondear el backend no sobrevive a eso. Así ejecutamos las encuestas de yayında.tv y las interacciones dentro del reproductor de Videoonly con aproximadamente una petición al origen por cambio, sin nada más exótico que una URL cacheada y una llamada de purga.

9 Intermedio Updated

Encuestas en directo a escala: el patrón cachear y purgar

Por qué una encuesta en directo rompe el enfoque evidente

La implementación evidente es un endpoint que la página llama cada pocos segundos. Funciona en pruebas con cinco personas y se cae en producción, porque una audiencia en directo está sincronizada como nunca lo está el tráfico web normal.

Todos están viendo el mismo momento. El presentador abre una pregunta y treinta mil navegadores la piden dentro del mismo segundo. Todos quieren una respuesta idéntica, así que ejecutas la misma consulta treinta mil veces y devuelves los mismos bytes. Después siguen preguntando cada pocos segundos durante el resto de la emisión, haya cambiado algo o no.

La salida habitual son los WebSockets: mantener una conexión por espectador y empujar. Funciona, pero has asumido estado de conexión, escalar una capa de sockets, tormentas de reconexión cuando una red móvil tiembla y una ruta alternativa para clientes que no pueden mantener la conexión.

Hay una opción más simple, y es la que tenemos en producción.

El patrón: una URL cacheada, purgada al cambiar

El estado de la encuesta es pequeño, idéntico para todos y cambia rara vez: un puñado de veces por emisión. Esa es exactamente la forma para la que se construyó una CDN.

Así que publícalo como una URL JSON cacheable normal. Los espectadores piden esa URL y responde el edge más cercano. Tu origen ve la primera petición de cada ubicación y prácticamente nada después, por muchos que estén mirando.

Cuando el presentador abre, cierra o edita una pregunta, el backend purga esa única URL. La siguiente petición falla en el edge, trae el estado nuevo una vez, y todos los espectadores posteriores vuelven a servirse desde caché.

Lo interesante es lo que ocurre cuando crece la audiencia: nada. Diez mil espectadores o cien mil, el origen sigue viendo una petición por cambio y ubicación. El coste sigue la frecuencia con la que cambia la encuesta, no cuánta gente mira.

Cómo funciona en yayında.tv

yayında.tv es nuestra plataforma de emisión en directo, y este es todo el mecanismo.

La lectura es un endpoint que devuelve la encuesta activa con sus preguntas y respuestas en JSON, servido a través de la CDN. Envía cabeceras CORS permisivas para que el reproductor pueda pedirlo desde el dominio donde esté incrustada la emisión. La URL lleva un hash derivado del nombre del canal y un secreto del servidor, lo que evita que sea trivialmente enumerable sin dejar de ser perfectamente cacheable: la URL es estable para un canal dado, así que es una clave de caché real y no una por usuario.

La escritura son unas pocas líneas en el controlador que inicia o detiene una encuesta: construye la misma URL, llama a la API de purga y listo. No hay cola, ni difusión, ni capa de sockets: la CDN es la difusión.

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

Y dentro del reproductor: Videoonly

Videoonly aplica la misma idea dentro del reproductor de vídeo. Las interacciones dentro del reproductor —una capa con una pregunta, una tarjeta de patrocinio, una llamada a la acción sincronizada con un momento de la emisión— son todas el mismo tipo de problema: una pequeña porción de estado que todos los espectadores necesitan y que cambia cuando un operador decide cambiarla.

Así que el reproductor obtiene su configuración de capas desde una URL cacheada en lugar de preguntar a la API con un temporizador. Cuando alguien programa o retira una capa, esa URL se purga. El reproductor la recoge en su siguiente petición, y la API no está en el camino crítico en absoluto.

El mismo razonamiento vale para cualquier otra cosa con estas propiedades: un marcador, una franja de "sonando ahora", flags de funcionalidad para un evento en directo, una cuenta atrás que se puede cortar antes de tiempo.

Acertar con los detalles

Unas pocas cosas deciden si esto resulta fluido o molesto en la práctica.

**Mantén la URL estable.** Cualquier cosa por usuario en la ruta o la query —un id de sesión, un timestamp anticaché— da a cada espectador su propia clave de caché y vuelves a golpear el origen con todos. La personalización va en una segunda petición aparte, no en la compartida.

**Pon un TTL con el que puedas vivir igualmente.** La purga es lo que hace rápidos los cambios, pero un TTL finito es tu red de seguridad para el día en que una llamada de purga falle. Lo bastante corto para que una purga perdida sea un tropiezo, lo bastante largo para que el origen siga tranquilo.

**Purga la URL exacta que sirves.** Si el objeto cacheado es `?s=abc` y purgas la ruta sin la query, no se invalida nada y la encuesta antigua se queda. Esta es la forma más común de que el patrón falle en silencio.

**Separa lecturas de escrituras.** Los espectadores leen la URL cacheada; los votos van a un endpoint normal, sin caché. Las escrituras son una fracción de las lecturas —un espectador vota una vez y lee continuamente—, así que la ruta de escritura puede seguir siendo corriente.

**No pongas secretos en el hash de la URL.** Es ofuscación, no autorización: suficiente para frenar la enumeración casual, y debe seguir siendo compatible con la caché. Cualquier cosa que necesite protección real no pertenece a un objeto compartido y cacheado.

Cuándo usar WebSockets en su lugar

Este patrón no es un sustituto universal, y conviene ser claro sobre dónde se acaba.

Encaja cuando el estado es compartido por todos, pequeño y cambia a escalas de tiempo humanas: encuestas, capas, marcadores, flags. No encaja cuando cada espectador necesita datos distintos, cuando las actualizaciones son continuas en vez de ocasionales, o cuando necesitas entrega por debajo del segundo con garantías de orden. El chat en vivo es el contraejemplo obvio: por usuario, constante y sensible a la latencia. Ahí usa una capa de sockets.

El encuadre honesto es que la mayoría de funciones "en tiempo real" de una página de emisión no son en tiempo real en absoluto. Son cambios ocasionales sobre un estado compartido, y una URL cacheada más una purga los resuelve con una fracción de las piezas móviles.

Preguntas frecuentes

¿Con qué rapidez ven el cambio los espectadores?

Tan rápido como se propague la purga más la siguiente petición del cliente. En la práctica, uno o dos segundos, de sobra para una emisión, ya que el presentador está hablando durante la transición.

¿Y si falla una llamada de purga?

Para eso está el TTL. Una purga fallida significa que el cambio entra cuando expira el objeto en lugar de inmediatamente. Mantén el TTL lo bastante corto para que sea un retraso y no una caída, y registra los fallos de purga para detectar patrones.

¿Funciona con query strings en la URL?

Sí, siempre que la CDN incluya la query string en la clave de caché y purgues la URL exacta que sirves. Purgar solo la ruta cuando el objeto está cacheado con query string es el error clásico.

¿A dónde van los votos?

A un endpoint normal sin caché. Solo se cachea la lectura compartida. Los votos son una fracción mínima del tráfico, porque cada espectador vota una vez y lee continuamente.

¿Es más barato que WebSockets?

Normalmente sí, y el mayor ahorro es operativo. No hay capa de sockets que escalar, ni estado de conexión que mantener, ni tormenta de reconexiones cuando cae una red móvil. El coste de origen sigue la frecuencia de cambio de la encuesta, no cuánta gente mira.