Al cerrar el módulo 3 dejamos todo el inventario listo: código reproducible, datos versionados, experimentos con memoria y un campeón identificado esperando en models:/churn-cineclick@champion. Pero un modelo registrado no genera valor hasta que alguien consume sus predicciones. La pregunta que abre este módulo no es técnica sino de negocio: ¿quién necesita las predicciones de churn, con qué frescura y a qué volumen? La respuesta a esa pregunta determina el patrón de despliegue — batch, online o streaming — y equivocarse aquí es uno de los errores más caros de MLOps: equipos que mantienen una API 24/7 para predicciones que se consultan una vez al día, o al revés, jobs nocturnos que llegan tarde a decisiones que se toman en segundos. En esta lección aprenderás los tres patrones, sus costes y beneficios, y tomarás con Laura la decisión razonada para CineClick.

Contenido

  1. La pregunta previa a todo despliegue
  2. Patrón batch: scoring periódico
  3. Patrón online: predicción síncrona bajo demanda
  4. Patrón streaming: predicción sobre eventos
  5. Tabla comparativa y criterios de decisión
  6. La decisión de CineClick, razonada
  7. Patrones híbridos: precomputar y servir desde caché

La pregunta previa a todo despliegue

Antes de escribir una sola línea de código de serving, hay que responder tres preguntas sobre el consumidor de las predicciones:

  • ¿Quién consume? ¿Una persona (analista de marketing), otro sistema (el frontend de la web), un proceso (una campaña de email)?
  • ¿Con qué frescura? ¿La predicción puede tener horas o días de antigüedad, o debe reflejar lo que el usuario acaba de hacer hace un segundo?
  • ¿A qué volumen y cadencia? ¿Millones de filas una vez a la semana? ¿Cientos de peticiones por segundo, todo el día? ¿Ráfagas impredecibles?

Fíjate en que ninguna de las tres preguntas menciona el modelo. El RandomForest campeón de CineClick es exactamente el mismo artefacto en los tres patrones; lo que cambia es cuándo se ejecuta predict y dónde aterrizan los resultados. Esta separación es liberadora: el trabajo de los módulos 2 y 3 (paquete instalable, registry, features con fuente única) sirve tal cual para cualquier patrón.

En CineClick, Laura convoca una reunión con dos stakeholders y salen dos respuestas distintas:

  • Marketing: "Cada lunes lanzamos la campaña de retención. Necesitamos la lista de clientes con riesgo alto de abandono, en una tabla, el lunes a las 8:00."
  • Producto: "Cuando un cliente pulsa 'cancelar suscripción', queremos decidir en ese instante si mostrarle una oferta personalizada. Tenemos unos 300 ms de presupuesto antes de que la pantalla se sienta lenta."

Dos consumidores, dos frescuras, dos volúmenes. Guarda este conflicto: lo resolveremos al final de la lección.

Patrón batch: scoring periódico

El patrón batch (por lotes) ejecuta el modelo periódicamente sobre un conjunto grande de entradas — típicamente toda la base de clientes — y escribe los resultados en un almacén (tabla de base de datos, fichero Parquet, data warehouse). Los consumidores no llaman al modelo: leen la tabla de resultados.

flowchart LR
    S[Scheduler<br/>lunes 06:00] --> J[Job de scoring]
    R[(Registry MLflow<br/>churn-cineclick@champion)] --> J
    D[(clientes_churn<br/>base completa)] --> J
    J --> T[(Tabla<br/>predicciones_churn)]
    T --> M[Campaña de marketing]
    T --> B[Dashboards / BI]

Un job batch para CineClick tiene esta forma (pseudocódigo realista; la orquestación con scheduler la veremos en la lección 05-03):

# scripts/score_batch.py — job de scoring semanal (esquema)
import mlflow
import pandas as pd
from datetime import datetime, timezone

from cineclick_churn.features import construir_features  # fuente única (03-03)

MODEL_URI = "models:/churn-cineclick@champion"

def main() -> None:
    # 1. Cargar el campeón desde el registry: siempre la versión gobernada por el alias
    modelo = mlflow.sklearn.load_model(MODEL_URI)

    # 2. Leer TODA la base de clientes activos (aquí, del almacén analítico)
    clientes = pd.read_parquet("data/clientes_activos.parquet")

    # 3. Construir features con LA MISMA función que usó el entrenamiento
    X = construir_features(clientes)

    # 4. Un único predict_proba vectorizado sobre miles de filas
    probs = modelo.predict_proba(X)[:, 1]  # columna 1 = probabilidad de abandono

    # 5. Escribir resultados con metadatos de trazabilidad
    resultado = pd.DataFrame({
        "id_cliente": clientes["id_cliente"],
        "probabilidad_abandono": probs,
        "fecha_scoring": datetime.now(timezone.utc).isoformat(),
        "modelo_uri": MODEL_URI,
    })
    resultado.to_parquet("data/predicciones_churn.parquet", index=False)

if __name__ == "__main__":
    main()

Puntos clave del código:

  • Línea a línea es material conocido: cargar del registry (03-02), features de la fuente única (03-03), predict_proba vectorizado. Batch no requiere infraestructura nueva — un script y un scheduler bastan.
  • El paso 4 es un solo predict_proba sobre todas las filas: los modelos de scikit-learn están optimizados para operar vectorizado, y puntuar 100.000 clientes de golpe tarda segundos (volveremos a esta idea con números en 04-05).
  • Los metadatos (fecha_scoring, modelo_uri) responden después a "¿qué modelo produjo esta lista?" — trazabilidad, el hilo de todo el curso.

Ventajas: máxima simplicidad operativa (si el job falla a las 6:00, se relanza y a las 7:00 hay datos; nadie estaba esperando en línea), coste mínimo (la máquina solo existe mientras corre el job), throughput altísimo. Limitación: la frescura. La predicción del lunes se calculó con los datos del domingo; si el martes el cliente abre 5 tickets de soporte, la tabla no se entera hasta el lunes siguiente.

Patrón online: predicción síncrona bajo demanda

El patrón online (o síncrono, o request-response) expone el modelo detrás de una API: un consumidor envía los datos de UN cliente y recibe la predicción en la misma llamada, en decenas o centenas de milisegundos.

sequenceDiagram
    participant F as Frontend CineClick
    participant A as API de predicción
    participant M as Modelo en memoria
    F->>A: POST /predecir {datos del cliente}
    A->>M: construir_features + predict_proba
    M-->>A: probabilidad 0.87
    A-->>F: {"probabilidad_abandono": 0.87, ...}
    Note over F: decide mostrar la oferta<br/>en < 300 ms

¿Cuándo hace falta online? Cuando la decisión se toma en el momento y depende de información que puede no estar en ningún batch previo:

  • El cliente pulsa "cancelar" ahora y hay que decidir la oferta ahora.
  • Detección de fraude en el instante del pago.
  • Recomendaciones que reaccionan a lo que el usuario acaba de hacer en la sesión.

El precio de esa inmediatez es operativo, y conviene ser muy consciente de él antes de elegirlo:

  • El servicio debe estar vivo 24/7: réplicas, healthchecks, alguien de guardia si cae.
  • La latencia importa: cada petición tiene un presupuesto (los 300 ms de producto), y hay que medir percentiles, no medias (04-05).
  • Hay que dimensionar para picos: si una noche de estrenos triplica las cancelaciones, la API debe aguantar (04-04).
  • La validación de entrada es crítica: en batch controlas los datos de origen; en online cualquier cliente puede enviarte cualquier cosa (lo resolveremos con Pydantic en 04-02).

En resumen: online no es "batch pero más moderno"; es un compromiso deliberado de más coste y complejidad a cambio de frescura instantánea. Las tres próximas lecciones (FastAPI, Docker, Kubernetes) existen precisamente porque el patrón online exige esa infraestructura.

Patrón streaming: predicción sobre eventos

El tercer patrón, streaming (o asíncrono por eventos), se sitúa entre los dos anteriores. Las predicciones no se piden (online) ni se programan (batch): se disparan cuando ocurre un evento. Los eventos fluyen por una cola o bus de mensajes (Kafka, RabbitMQ, Pub/Sub, Kinesis) y un consumidor con el modelo los procesa a medida que llegan, publicando el resultado en otro topic o en una tabla.

flowchart LR
    E1[evento: ticket abierto] --> K[/cola de eventos/]
    E2[evento: pago fallido] --> K
    E3[evento: 0 horas esta semana] --> K
    K --> C[Consumidor con modelo]
    C --> K2[/topic: riesgo_actualizado/]
    K2 --> N[Sistema de notificaciones]
    K2 --> T[(Tabla de riesgo)]

Características que lo definen (nos quedamos en lo conceptual — montar Kafka queda fuera del curso):

  • Nadie espera la respuesta: el productor del evento publica y sigue con su vida. La latencia se mide en "segundos hasta que el efecto ocurre", no en milisegundos de una llamada bloqueante.
  • Frescura casi en tiempo real sin el acoplamiento de una API síncrona: si el consumidor cae, los eventos se acumulan en la cola y se procesan al volver — hay tolerancia a fallos incorporada.
  • Complejidad operativa alta: hay que operar el bus de mensajes, gestionar offsets, reprocesamientos, duplicados, orden de eventos. Es el patrón que más infraestructura exige.

Para churn, un caso de uso streaming sería: "cada vez que un cliente abre su tercer ticket del mes, recalcular su riesgo y, si supera 0.8, avisar al equipo de retención en minutos". CineClick no tiene hoy ese caso ni la infraestructura de eventos; lo importante es que sepas reconocer cuándo un problema es streaming: reaccionar a hechos individuales con frescura de segundos, sin que nadie esté esperando en línea.

Tabla comparativa y criterios de decisión

Dimensión Batch Online (síncrono) Streaming (eventos)
Latencia percibida Horas/días (hasta el siguiente job) Milisegundos (10–500 ms por petición) Segundos/minutos desde el evento
Frescura de los datos La del último job La del instante de la petición La del evento que dispara
Volumen típico Millones de filas por ejecución 1 fila por petición, N peticiones/s 1 evento cada vez, flujo continuo
Coste de infraestructura Bajo (cómputo efímero, solo mientras corre) Medio/alto (servicio 24/7, réplicas, picos) Alto (bus de mensajes + consumidores 24/7)
Complejidad operativa Baja (script + scheduler; fallo → relanzar) Media (API, healthchecks, escalado, guardia) Alta (Kafka/colas, offsets, reprocesos)
Tolerancia a fallos Alta (relanzar el job) Baja (caída = errores visibles al usuario) Alta (la cola retiene los eventos)
Validación de entrada Controlada en origen Crítica (entrada externa arbitraria) Media (contratos de eventos)
Ejemplos de uso Campañas, scoring de cartera, informes, propensión mensual Fraude en el pago, oferta al cancelar, pricing dinámico, búsqueda Alertas por comportamiento, personalización near-real-time, IoT

Criterios de decisión, en orden:

  1. Empieza por la frescura que el negocio necesita de verdad, no por la que suena mejor. "Tiempo real" es una petición frecuente que, al preguntar dos veces, suele significar "cada mañana está bien".
  2. Si nadie espera la respuesta en línea, no montes una API. Este es el error típico número uno: mantener un servicio 24/7 (con su guardia, su escalado y su factura) para predicciones que un job de 10 minutos semanales cubriría. La versión inversa también existe: intentar servir una decisión de 300 ms leyendo una tabla batch de hace 6 días.
  3. Batch es el patrón por defecto. Es el más barato y el más simple de operar; solo se abandona cuando la frescura lo exige. Muchos equipos maduros sirven el 80% de sus modelos en batch.
  4. Streaming solo si ya existe (o se justifica) la infraestructura de eventos. Adoptar Kafka para un solo modelo casi nunca compensa.
  5. Los patrones no son excluyentes: un mismo modelo puede servirse en batch para un consumidor y online para otro. Que es, exactamente, el caso de CineClick.

La decisión de CineClick, razonada

Volvamos a la reunión de Laura. Apliquemos los criterios a cada consumidor:

Marketing (campaña semanal de retención). Frescura necesaria: semanal. Volumen: toda la base de clientes de golpe. Nadie espera en línea: la campaña se prepara el lunes por la mañana con la tabla ya escrita. Veredicto claro: batch. Un job semanal que carga models:/churn-cineclick@champion, puntúa la base completa y escribe predicciones_churn. Es el caso de negocio principal — el que justificó el proyecto — y montar una API para él sería el error típico de la tabla anterior. Este job quedará orquestado con scheduler, reintentos y alertas en la lección 05-03; de momento nos basta el esquema de script que ya viste.

Producto (oferta en el momento de la cancelación). Frescura necesaria: el instante mismo — la decisión usa el estado actual del cliente y debe resolverse en menos de 300 ms mientras la pantalla de cancelación carga. ¿Podría servirse desde la tabla batch? Casi: pero la tabla tiene hasta 6 días de antigüedad, y justo el comportamiento reciente (tickets de esta semana, horas de esta semana) es la señal más valiosa cuando alguien está a punto de cancelar. Producto lo verificó con datos: de los clientes que cancelan, una parte relevante muestra el deterioro en los últimos días. Veredicto: online. Marc, que ha sufrido APIs innecesarias en su empresa anterior, hace de abogado del diablo y pregunta si compensa; la respuesta es que sí porque hay una decisión en el momento con presupuesto de milisegundos, no porque online sea más moderno.

La decisión final del equipo, documentada en el repo:

CineClick sirve el modelo churn-cineclick con dos patrones sobre el mismo artefacto: (1) batch semanal para la campaña de retención de marketing — caso principal, se orquesta en el módulo 5; (2) servicio online para la oferta en el flujo de cancelación — se implementa con FastAPI en 04-02, se empaqueta con Docker en 04-03 y se despliega escalado en 04-04. Ambos cargan models:/churn-cineclick@champion e importan construir_features del paquete: mismo modelo, mismas features, dos cadencias.

Fíjate en la consecuencia arquitectónica de las decisiones anteriores del curso: como el modelo vive en el registry con un alias y las features tienen fuente única, añadir un segundo patrón de consumo no duplica nada. Si el módulo 3 hubiera terminado con un pickle suelto y features copiadas, ahora tendríamos dos copias de cada cosa que mantener sincronizadas.

Patrones híbridos: precomputar y servir desde caché

Existe un punto intermedio que conviene conocer: precomputar en batch y servir online desde una caché. Un job periódico calcula las predicciones de todos los clientes y las carga en un almacén de lectura rápida (Redis, una tabla indexada); la "API" se limita a hacer un lookup por id_cliente en ~1 ms, sin ejecutar el modelo.

Online puro Híbrido (precomputado + caché)
Latencia 10–500 ms (features + predict) ~1–5 ms (lookup)
Frescura Instante de la petición La del último job de precomputación
Entradas nuevas/no vistas Sí (calcula sobre lo que llegue) No (si el cliente no está en la caché, no hay predicción)
Coste por petición Cómputo real en cada llamada Casi nulo en lectura; el coste se paga en el job
Cuándo brilla La entrada cambia entre peticiones Universo de entradas conocido y finito; tráfico de lectura alto

Para CineClick el híbrido se descartó como patrón principal del caso "cancelación" por la razón de frescura ya vista (la señal reciente es la que importa), pero no desaparece del mapa: en la lección 04-05 lo recuperaremos como optimización — cachear respuestas con TTL corto para abaratar la inferencia sin renunciar del todo a la frescura. Muchos sistemas reales de recomendación funcionan así: candidatos precomputados en batch, refinamiento ligero online.

Errores Comunes y Consejos

  • Error: elegir el patrón por moda y no por consumidor. "Queremos tiempo real" sin un caso que lo exija lleva a pagar infraestructura 24/7 para datos que se miran una vez al día. Pregunta siempre: ¿quién espera la respuesta y cuánto puede esperar?
  • Error: servir decisiones de milisegundos desde una tabla batch envejecida. El inverso del anterior. Si la señal fresca es la que predice (como en la cancelación de CineClick), una predicción de hace 6 días puede ser peor que no mostrar nada.
  • Error: tratar los patrones como excluyentes. Un mismo modelo del registry puede alimentar batch y online a la vez. Lo que no debe duplicarse es el artefacto ni la lógica de features.
  • Error: olvidar la trazabilidad en batch. Una tabla de predicciones sin fecha_scoring ni modelo_uri es una bomba de relojería: nadie sabrá qué modelo generó qué lista cuando marketing pregunte por qué un cliente recibió la oferta.
  • Consejo: escribe la decisión de patrón en el repo (un docs/decisiones/ o el README), con los consumidores, la frescura acordada y las alternativas descartadas. Dentro de seis meses alguien propondrá "pasarlo todo a streaming" y agradecerás tener el razonamiento escrito.
  • Consejo: dimensiona con números, no con adjetivos. "Mucho tráfico" no es un dato; "40 cancelaciones/hora en pico" sí, y probablemente te sorprenda lo poco que es.

Ejercicios

Ejercicio 1

Clasifica cada caso en batch, online o streaming, y justifica con las tres preguntas (quién consume, frescura, volumen):

a) Un banco decide en el momento del pago con tarjeta si la transacción es fraudulenta. b) Una aseguradora calcula cada noche la propensión de renovación de toda su cartera para el equipo comercial del día siguiente. c) Una app de logística recalcula el riesgo de retraso de un envío cada vez que llega un evento de escaneo en un centro de distribución, para actualizar el aviso al cliente en minutos.

Ejercicio 2

El equipo de BI de CineClick pide "un endpoint de API para consultar la probabilidad de churn de cualquier cliente desde sus dashboards, que se refrescan cada mañana". ¿Qué patrón recomiendas y por qué? ¿Qué pregunta harías antes de decidir?

Ejercicio 3

Modifica el pseudocódigo de score_batch.py para que, además de escribir el Parquet completo, genere un segundo fichero clientes_riesgo_alto.parquet solo con los clientes cuya probabilidad supere 0.7, ordenados de mayor a menor riesgo — el formato que marketing quiere para la campaña.

Soluciones

Solución 1

a) Online. Consume otro sistema (el procesador de pagos) que bloquea la transacción esperando la respuesta; frescura del instante (los datos de la transacción acaban de nacer); volumen de N peticiones/s con presupuesto de milisegundos. Es el ejemplo canónico de online. b) Batch. Consume un equipo humano al día siguiente; frescura diaria es suficiente; volumen de toda la cartera de golpe. Nadie espera en línea: job nocturno a una tabla. c) Streaming. El disparador es un evento individual (el escaneo), nadie espera bloqueado la respuesta, y la frescura requerida es de minutos, no de milisegundos ni de días. Los eventos fluyen por una cola y un consumidor con el modelo los procesa al llegar.

Solución 2

Batch, servido desde la tabla predicciones_churn que ya existirá para marketing — los dashboards se refrescan cada mañana, así que la frescura semanal (o diaria, si se aumenta la cadencia del job) es suficiente y no hay nadie esperando en línea. Montar un endpoint online para esto sería el error típico: BI haría cientos de consultas contra una API 24/7 para obtener datos que un JOIN con la tabla resuelve gratis. La pregunta previa: ¿con qué frescura real necesitáis el dato? Si la respuesta fuera "queremos ver el efecto de una acción de retención en la misma tarde", habría que subir la cadencia del batch — y solo si pidieran el instante exacto se justificaría reutilizar el servicio online de 04-02.

Solución 3

    # ... tras construir `resultado` ...
    riesgo_alto = (
        resultado[resultado["probabilidad_abandono"] > 0.7]
        .sort_values("probabilidad_abandono", ascending=False)
    )
    riesgo_alto.to_parquet("data/clientes_riesgo_alto.parquet", index=False)

Dos detalles a valorar: el umbral 0.7 es una decisión de negocio (cuántos clientes puede absorber la campaña), no un parámetro del modelo — merece vivir en configuración, no hardcodeado; y el fichero hereda las columnas de trazabilidad (fecha_scoring, modelo_uri), de modo que la lista que recibe marketing sigue siendo auditable.

Conclusión

Ya tienes el mapa: batch para predicciones periódicas y masivas sin nadie esperando (barato, simple, el patrón por defecto), online para decisiones en el momento con presupuesto de milisegundos (caro y exigente, pero insustituible cuando la frescura es la señal), y streaming para reaccionar a eventos individuales en segundos (potente, pero solo con la infraestructura que lo sostenga). Y sabes que la elección la dicta el consumidor — quién, con qué frescura, a qué volumen — no la moda. CineClick lo tiene decidido y documentado: batch semanal para la campaña de marketing (se orquestará en el módulo 5) y un servicio online para la oferta en el flujo de cancelación, ambos bebiendo del mismo models:/churn-cineclick@champion y de la misma construir_features. La siguiente lección construye ese servicio online de verdad: una API con FastAPI que valida las entradas con Pydantic, carga al campeón al arrancar, importa las features de la fuente única y responde en milisegundos con la probabilidad de abandono. Es hora de que el modelo de Laura atienda su primera petición.

© Copyright 2026. Todos los derechos reservados