El problema fundamental de la computación que resuelve esta re-arquitectura es la gestión de estado y la consistencia de datos en sistemas distribuidos de alto rendimiento, particularmente bajo cargas de tráfico "spiky" como las transmisiones en vivo. Tradicionalmente, la correlación de datos (enriquecimiento) se realizaba mediante lookups síncronos a una base de datos compartida. Sin embargo, este patrón introduce una dependencia crítica de latencia y disponibilidad, y se convierte en un cuello de botella de escalabilidad cuando los requisitos de escritura superan la capacidad elástica de la base de datos, especialmente en escenarios donde el pre-escalado es la única opción segura.

La tesis central es que, para cargas extremas y patrones de tráfico impredecibles, mover la lógica de enriquecimiento fuera de la ruta crítica de servicio y hacia un sistema de procesamiento de streams stateful es una estrategia superior. Esto permite desacoplar la generación de eventos de su enriquecimiento, delegando la complejidad de la gestión de estado y la resiliencia a un framework diseñado para ello, como Apache Flink. La clave es transformar una operación de escritura síncrona en una base de datos en un patrón de "fire-and-forget" seguido de un join asíncrono en el stream, lo que mejora la resiliencia, la escalabilidad y la capacidad de respuesta del sistema en su conjunto.

Este enfoque se alinea con la evolución de la arquitectura de sistemas distribuidos hacia patrones de event-driven y stream processing, donde la inmutabilidad de los eventos y la reconstrucción del estado a partir de streams son principios clave. La necesidad de este cambio se agudiza con el crecimiento de las transmisiones en vivo, que presentan desafíos únicos en términos de volumen, velocidad y variabilidad del tráfico, haciendo que las arquitecturas basadas en bases de datos síncronas sean insostenibles en la ruta crítica.

Arquitectura del Sistema

La arquitectura original del Ads Event Publisher se basaba en un Ads Metadata Registry, un Key-Value store globalmente replicado. Cuando el ad server tomaba una decisión, escribía los metadatos de la impresión en este registro. El cliente recibía un token opaco con identificadores de referencia. Posteriormente, el Ads Event Publisher decodificaba el token, realizaba un lookup en el registro y unía los metadatos con el evento del cliente para enriquecerlo.

La nueva arquitectura reemplaza este lookup síncrono con un job de Apache Flink stateful. El ad server ahora simplemente registra su respuesta (log and move on), y los eventos de cliente se envían a un stream de Kafka. El job de Flink consume dos streams de entrada: uno con las respuestas del ad server y otro con los eventos del cliente. El corazón del sistema es un operador personalizado de Flink, implementado como un KeyedCoProcessFunction, que realiza un streaming join. Este operador mantiene un estado local (en memoria, respaldado por RocksDB para persistencia y checkpoints) con las respuestas recientes del ad server y los eventos de cliente que aún no han encontrado su contraparte. El join se realiza en una ventana de 60 minutos, emparejando eventos por una clave de impresión común. Los eventos que llegan primero se retienen hasta que llega la respuesta, y viceversa. Los eventos que no se emparejan dentro de la ventana se marcan para recuperación.

Para manejar la distribución geográfica, en lugar de replicar todas las respuestas del servidor a todas las regiones, la arquitectura implementa un mecanismo de re-enrutamiento. El token de publicidad incluye la región de origen del ad server. Si un evento de cliente llega a una región diferente a la de su ad server, se reenvía al job de Flink en la región correcta para que se realice el join. Además, existe un companion path para eventos tardíos: un job batch de Spark, orquestado por Maestro, que se ejecuta cada hora para procesar eventos que excedieron la ventana de 60 minutos del job de Flink, utilizando un data warehouse con retención más larga para los metadatos. La deduplicación se realiza en stream mediante un ID idempotente generado por hashing de campos clave, manteniendo un estado local con un TTL corto para los IDs vistos recientemente, complementado con la propagación del ID al consumidor final para una deduplicación "exactly-once" si es requerida.

Flujo de Enriquecimiento de Eventos de Publicidad (Nueva Arquitectura)

  1. 1 Ad Server Decide el anuncio, registra la respuesta en un log y envía metadatos a Kafka.
  2. 2 Dispositivo Cliente Dispara eventos (ej. 'first quartile') con token opaco.
  3. 3 Ingesta de Eventos Eventos de cliente llegan a Kafka.
  4. 4 Flink Job (Normalize) Normaliza eventos de cliente de diferentes fuentes a un formato común.
  5. 5 Flink Job (Re-route) Inspecciona token, reenvía eventos a la región correcta si es necesario.
  6. 6 Flink Job (Join - KeyedCoProcessFunct... Empareja eventos de cliente con respuestas de ad server en ventana de 60 min....
  7. 7 Flink Job (Deduplicate) Elimina duplicados usando un ID idempotente y estado local.
  8. 8 Flink Job (Publish) Extrae beacons de medición y publica eventos enriquecidos a consumidores down...
CapaTecnologíaJustificación
data-processing Apache Flink Motor de procesamiento de streams stateful en tiempo real para el enriquecimiento y deduplicación de eventos de publicidad. Permite joins de streams y gestión de estado local. Uso de KeyedCoProcessFunction para control directo del estado de join, ventana de 60 minutos para el matching, RocksDB como backend de estado.
messaging Apache Kafka Bus de mensajes para la ingesta de eventos de cliente y respuestas del ad server, y para la publicación de eventos enriquecidos a los consumidores downstream. At-least-once delivery para eventos, lo que requiere deduplicación downstream.
data-processing Apache Spark Job batch complementario para la recuperación de eventos tardíos que exceden la ventana de Flink, re-emparejándolos con metadatos del data warehouse. Orquestado por Maestro, se ejecuta cada hora.
orchestration Maestro Orquestador de workflows de Netflix, utilizado para programar y gestionar el job batch de Spark para la recuperación de eventos tardíos.
storage Key-Value Store (Netflix's Metadata Registry) Sistema legado, reemplazado. Almacenaba metadatos de anuncios para lookups síncronos por el Ads Event Publisher. Globalmente replicado, lo que generaba un cuello de botella de escritura en la ruta crítica.
storage Data Warehouse Almacén de datos con retención a largo plazo para metadatos de anuncios, utilizado por el job batch de Spark para la recuperación de eventos tardíos.

Trade-offs

Ganancias
  • ▲ Resiliencia del ad serving
  • ▲▲ Escalabilidad para eventos en vivo
  • ▲ Reducción de dependencias en la ruta crítica
  • ▲ Frescura de eventos para sistemas de capping
  • ▲ Simplificación de la arquitectura (menos sistemas)
Costes
  • △ Complejidad de la gestión de estado en Flink
  • △ Necesidad de un mecanismo de re-enrutamiento regional
  • △ Necesidad de un job batch complementario para el 'long tail'

Fundamentos Teóricos

Este problema se relaciona directamente con los principios de sistemas distribuidos y la gestión de estado en entornos de alta concurrencia. La transición de un lookup síncrono en una base de datos replicada globalmente a un streaming join stateful evoca los conceptos de consistencia eventual y la paradoja de CAP Theorem. Mientras que la base de datos replicada buscaba una alta disponibilidad y consistencia (aunque a costa de latencia en la ruta crítica), la solución de Flink prioriza la disponibilidad y la tolerancia a particiones (AP en CAP) para el procesamiento de eventos, delegando la consistencia final a mecanismos asíncronos y de recuperación.

La elección de Apache Flink y su KeyedCoProcessFunction para el streaming join se basa en la teoría de procesamiento de streams con estado, donde los conceptos de "windows" y "watermarks" son fundamentales para manejar el tiempo de evento y el tiempo de procesamiento en un entorno distribuido. Aunque el artículo menciona que no se necesita la semántica de ventana para el ordenamiento, la gestión del estado local y la capacidad de procesar eventos fuera de orden (con un límite de ventana) son características clave de los frameworks de stream processing. La idea de "fire-and-forget" y el procesamiento asíncrono se alinea con los principios de sistemas reactivos y la resiliencia frente a fallos de componentes individuales, un tema recurrente en la literatura de sistemas distribuidos desde los trabajos pioneros de Leslie Lamport sobre el ordenamiento de eventos en sistemas distribuidos.