En tancar el mòdul 3 vam deixar tot l'inventari a punt: codi reproduïble, dades versionades, experiments amb memòria i un campió identificat esperant a models:/churn-cineclick@champion. Però un model registrat no genera valor fins que algú consumeix les seves prediccions. La pregunta que obre aquest mòdul no és tècnica sinó de negoci: qui necessita les prediccions de churn, amb quina frescor i a quin volum? La resposta a aquesta pregunta determina el patró de desplegament — batch, online o streaming — i equivocar-se aquí és un dels errors més cars de MLOps: equips que mantenen una API 24/7 per a prediccions que es consulten un cop al dia, o a l'inrevés, jobs nocturns que arriben tard a decisions que es prenen en segons. En aquesta lliçó aprendràs els tres patrons, els seus costos i beneficis, i prendràs amb la Laura la decisió raonada per a CineClick.

Contingut

  1. La pregunta prèvia a tot desplegament
  2. Patró batch: scoring periòdic
  3. Patró online: predicció síncrona sota demanda
  4. Patró streaming: predicció sobre esdeveniments
  5. Taula comparativa i criteris de decisió
  6. La decisió de CineClick, raonada
  7. Patrons híbrids: precomputar i servir des de la memòria cau

La pregunta prèvia a tot desplegament

Abans d'escriure una sola línia de codi de serving, cal respondre tres preguntes sobre el consumidor de les prediccions:

  • Qui consumeix? Una persona (analista de màrqueting), un altre sistema (el frontend del web), un procés (una campanya d'email)?
  • Amb quina frescor? La predicció pot tenir hores o dies d'antiguitat, o ha de reflectir el que l'usuari acaba de fer fa un segon?
  • A quin volum i cadència? Milions de files un cop per setmana? Centenars de peticions per segon, tot el dia? Ràfegues impredictibles?

Fixa't que cap de les tres preguntes no esmenta el model. El RandomForest campió de CineClick és exactament el mateix artefacte en els tres patrons; el que canvia és quan s'executa predict i on aterren els resultats. Aquesta separació és alliberadora: la feina dels mòduls 2 i 3 (paquet instal·lable, registry, features amb font única) serveix tal qual per a qualsevol patró.

A CineClick, la Laura convoca una reunió amb dos stakeholders i en surten dues respostes diferents:

  • Màrqueting: "Cada dilluns llancem la campanya de retenció. Necessitem la llista de clients amb risc alt d'abandonament, en una taula, el dilluns a les 8:00."
  • Producte: "Quan un client prem 'cancel·lar subscripció', volem decidir en aquell instant si li mostrem una oferta personalitzada. Tenim uns 300 ms de pressupost abans que la pantalla se senti lenta."

Dos consumidors, dues frescors, dos volums. Guarda't aquest conflicte: el resoldrem al final de la lliçó.

Patró batch: scoring periòdic

El patró batch (per lots) executa el model periòdicament sobre un conjunt gran d'entrades — típicament tota la base de clients — i escriu els resultats en un magatzem (taula de base de dades, fitxer Parquet, data warehouse). Els consumidors no criden el model: llegeixen la taula de resultats.

flowchart LR
    S[Scheduler<br/>dilluns 06:00] --> J[Job de scoring]
    R[(Registry MLflow<br/>churn-cineclick@champion)] --> J
    D[(clients_churn<br/>base completa)] --> J
    J --> T[(Taula<br/>prediccions_churn)]
    T --> M[Campanya de màrqueting]
    T --> B[Dashboards / BI]

Un job batch per a CineClick té aquesta forma (pseudocodi realista; l'orquestració amb scheduler la veurem a la lliçó 05-03):

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

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

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

def main() -> None:
    # 1. Carregar el campió des del registry: sempre la versió governada per l'àlies
    model = mlflow.sklearn.load_model(MODEL_URI)

    # 2. Llegir TOTA la base de clients actius (aquí, del magatzem analític)
    clients = pd.read_parquet("data/clients_actius.parquet")

    # 3. Construir features amb LA MATEIXA funció que va fer servir l'entrenament
    X = construir_features(clients)

    # 4. Un únic predict_proba vectoritzat sobre milers de files
    probs = model.predict_proba(X)[:, 1]  # columna 1 = probabilitat d'abandonament

    # 5. Escriure resultats amb metadades de traçabilitat
    resultat = pd.DataFrame({
        "id_client": clients["id_client"],
        "probabilitat_abandonament": probs,
        "data_scoring": datetime.now(timezone.utc).isoformat(),
        "model_uri": MODEL_URI,
    })
    resultat.to_parquet("data/prediccions_churn.parquet", index=False)

if __name__ == "__main__":
    main()

Punts clau del codi:

  • Línia a línia és material conegut: carregar del registry (03-02), features de la font única (03-03), predict_proba vectoritzat. Batch no requereix infraestructura nova — amb un script i un scheduler n'hi ha prou.
  • El pas 4 és un sol predict_proba sobre totes les files: els models de scikit-learn estan optimitzats per operar vectoritzats, i puntuar 100.000 clients de cop triga segons (hi tornarem amb números a 04-05).
  • Les metadades (data_scoring, model_uri) responen després a "quin model va produir aquesta llista?" — traçabilitat, el fil de tot el curs.

Avantatges: màxima simplicitat operativa (si el job falla a les 6:00, es rellança i a les 7:00 hi ha dades; ningú no estava esperant en línia), cost mínim (la màquina només existeix mentre corre el job), throughput altíssim. Limitació: la frescor. La predicció del dilluns es va calcular amb les dades del diumenge; si el dimarts el client obre 5 tiquets de suport, la taula no se n'assabenta fins al dilluns següent.

Patró online: predicció síncrona sota demanda

El patró online (o síncron, o request-response) exposa el model darrere d'una API: un consumidor envia les dades d'UN client i rep la predicció en la mateixa crida, en desenes o centenars de mil·lisegons.

sequenceDiagram
    participant F as Frontend CineClick
    participant A as API de predicció
    participant M as Model en memòria
    F->>A: POST /predir {dades del client}
    A->>M: construir_features + predict_proba
    M-->>A: probabilitat 0.87
    A-->>F: {"probabilitat_abandonament": 0.87, ...}
    Note over F: decideix mostrar l'oferta<br/>en < 300 ms

Quan cal online? Quan la decisió es pren al moment i depèn d'informació que pot no ser en cap batch previ:

  • El client prem "cancel·lar" ara i cal decidir l'oferta ara.
  • Detecció de frau a l'instant del pagament.
  • Recomanacions que reaccionen al que l'usuari acaba de fer durant la sessió.

El preu d'aquesta immediatesa és operatiu, i convé ser-ne molt conscient abans de triar-lo:

  • El servei ha d'estar viu 24/7: rèpliques, healthchecks, algú de guàrdia si cau.
  • La latència importa: cada petició té un pressupost (els 300 ms de producte), i cal mesurar percentils, no mitjanes (04-05).
  • Cal dimensionar per a pics: si una nit d'estrenes triplica les cancel·lacions, l'API ha d'aguantar (04-04).
  • La validació d'entrada és crítica: en batch controles les dades d'origen; en online qualsevol client et pot enviar qualsevol cosa (ho resoldrem amb Pydantic a 04-02).

En resum: online no és "batch però més modern"; és un compromís deliberat de més cost i complexitat a canvi de frescor instantània. Les tres properes lliçons (FastAPI, Docker, Kubernetes) existeixen precisament perquè el patró online exigeix aquesta infraestructura.

Patró streaming: predicció sobre esdeveniments

El tercer patró, streaming (o asíncron per esdeveniments), se situa entre els dos anteriors. Les prediccions no es demanen (online) ni es programen (batch): es disparen quan passa un esdeveniment. Els esdeveniments flueixen per una cua o bus de missatges (Kafka, RabbitMQ, Pub/Sub, Kinesis) i un consumidor amb el model els processa a mesura que arriben, publicant el resultat en un altre topic o en una taula.

flowchart LR
    E1[esdeveniment: tiquet obert] --> K[/cua d'esdeveniments/]
    E2[esdeveniment: pagament fallit] --> K
    E3[esdeveniment: 0 hores aquesta setmana] --> K
    K --> C[Consumidor amb model]
    C --> K2[/topic: risc_actualitzat/]
    K2 --> N[Sistema de notificacions]
    K2 --> T[(Taula de risc)]

Característiques que el defineixen (ens quedem en el pla conceptual — muntar Kafka queda fora del curs):

  • Ningú no espera la resposta: el productor de l'esdeveniment publica i continua amb la seva vida. La latència es mesura en "segons fins que l'efecte passa", no en mil·lisegons d'una crida bloquejant.
  • Frescor gairebé en temps real sense l'acoblament d'una API síncrona: si el consumidor cau, els esdeveniments s'acumulen a la cua i es processen en tornar — hi ha tolerància a fallades incorporada.
  • Complexitat operativa alta: cal operar el bus de missatges, gestionar offsets, reprocessaments, duplicats, ordre dels esdeveniments. És el patró que més infraestructura exigeix.

Per a churn, un cas d'ús streaming seria: "cada cop que un client obre el seu tercer tiquet del mes, recalcular el seu risc i, si supera 0.8, avisar l'equip de retenció en qüestió de minuts". CineClick no té avui aquest cas ni la infraestructura d'esdeveniments; l'important és que sàpigues reconèixer quan un problema és streaming: reaccionar a fets individuals amb frescor de segons, sense que ningú estigui esperant en línia.

Taula comparativa i criteris de decisió

Dimensió Batch Online (síncron) Streaming (esdeveniments)
Latència percebuda Hores/dies (fins al següent job) Mil·lisegons (10–500 ms per petició) Segons/minuts des de l'esdeveniment
Frescor de les dades La de l'últim job La de l'instant de la petició La de l'esdeveniment que dispara
Volum típic Milions de files per execució 1 fila per petició, N peticions/s 1 esdeveniment cada cop, flux continu
Cost d'infraestructura Baix (còmput efímer, només mentre corre) Mitjà/alt (servei 24/7, rèpliques, pics) Alt (bus de missatges + consumidors 24/7)
Complexitat operativa Baixa (script + scheduler; fallada → rellançar) Mitjana (API, healthchecks, escalat, guàrdia) Alta (Kafka/cues, offsets, reprocessaments)
Tolerància a fallades Alta (rellançar el job) Baixa (caiguda = errors visibles per a l'usuari) Alta (la cua reté els esdeveniments)
Validació d'entrada Controlada en origen Crítica (entrada externa arbitrària) Mitjana (contractes d'esdeveniments)
Exemples d'ús Campanyes, scoring de cartera, informes, propensió mensual Frau en el pagament, oferta en cancel·lar, pricing dinàmic, cerca Alertes per comportament, personalització near-real-time, IoT

Criteris de decisió, en ordre:

  1. Comença per la frescor que el negoci necessita de debò, no per la que sona millor. "Temps real" és una petició freqüent que, quan preguntes dues vegades, sol voler dir "cada matí ja va bé".
  2. Si ningú no espera la resposta en línia, no muntis una API. Aquest és l'error típic número u: mantenir un servei 24/7 (amb la seva guàrdia, el seu escalat i la seva factura) per a prediccions que un job de 10 minuts setmanals cobriria. La versió inversa també existeix: intentar servir una decisió de 300 ms llegint una taula batch de fa 6 dies.
  3. Batch és el patró per defecte. És el més barat i el més simple d'operar; només s'abandona quan la frescor ho exigeix. Molts equips madurs serveixen el 80% dels seus models en batch.
  4. Streaming només si ja existeix (o es justifica) la infraestructura d'esdeveniments. Adoptar Kafka per a un sol model gairebé mai no compensa.
  5. Els patrons no són excloents: un mateix model pot servir-se en batch per a un consumidor i online per a un altre. Que és, exactament, el cas de CineClick.

La decisió de CineClick, raonada

Tornem a la reunió de la Laura. Apliquem els criteris a cada consumidor:

Màrqueting (campanya setmanal de retenció). Frescor necessària: setmanal. Volum: tota la base de clients de cop. Ningú no espera en línia: la campanya es prepara el dilluns al matí amb la taula ja escrita. Veredicte clar: batch. Un job setmanal que carrega models:/churn-cineclick@champion, puntua la base completa i escriu prediccions_churn. És el cas de negoci principal — el que va justificar el projecte — i muntar-hi una API seria l'error típic de la taula anterior. Aquest job quedarà orquestrat amb scheduler, reintents i alertes a la lliçó 05-03; de moment en tenim prou amb l'esquema de script que ja has vist.

Producte (oferta en el moment de la cancel·lació). Frescor necessària: l'instant mateix — la decisió fa servir l'estat actual del client i s'ha de resoldre en menys de 300 ms mentre la pantalla de cancel·lació carrega. Podria servir-se des de la taula batch? Gairebé: però la taula té fins a 6 dies d'antiguitat, i justament el comportament recent (tiquets d'aquesta setmana, hores d'aquesta setmana) és el senyal més valuós quan algú està a punt de cancel·lar. Producte ho va verificar amb dades: dels clients que cancel·len, una part rellevant mostra el deteriorament en els últims dies. Veredicte: online. En Marc, que ha patit APIs innecessàries a la seva empresa anterior, fa d'advocat del diable i pregunta si compensa; la resposta és que sí perquè hi ha una decisió al moment amb pressupost de mil·lisegons, no perquè online sigui més modern.

La decisió final de l'equip, documentada al repo:

CineClick serveix el model churn-cineclick amb dos patrons sobre el mateix artefacte: (1) batch setmanal per a la campanya de retenció de màrqueting — cas principal, s'orquestra al mòdul 5; (2) servei online per a l'oferta en el flux de cancel·lació — s'implementa amb FastAPI a 04-02, s'empaqueta amb Docker a 04-03 i es desplega escalat a 04-04. Tots dos carreguen models:/churn-cineclick@champion i importen construir_features del paquet: mateix model, mateixes features, dues cadències.

Fixa't en la conseqüència arquitectònica de les decisions anteriors del curs: com que el model viu al registry amb un àlies i les features tenen font única, afegir un segon patró de consum no duplica res. Si el mòdul 3 hagués acabat amb un pickle solt i features copiades, ara tindríem dues còpies de cada cosa per mantenir sincronitzades.

Patrons híbrids: precomputar i servir des de la memòria cau

Hi ha un punt intermedi que convé conèixer: precomputar en batch i servir online des d'una memòria cau. Un job periòdic calcula les prediccions de tots els clients i les carrega en un magatzem de lectura ràpida (Redis, una taula indexada); l'"API" es limita a fer un lookup per id_client en ~1 ms, sense executar el model.

Online pur Híbrid (precomputat + memòria cau)
Latència 10–500 ms (features + predict) ~1–5 ms (lookup)
Frescor Instant de la petició La de l'últim job de precomputació
Entrades noves/no vistes Sí (calcula sobre el que arribi) No (si el client no és a la memòria cau, no hi ha predicció)
Cost per petició Còmput real a cada crida Gairebé nul en lectura; el cost es paga al job
Quan brilla L'entrada canvia entre peticions Univers d'entrades conegut i finit; trànsit de lectura alt

Per a CineClick l'híbrid es va descartar com a patró principal del cas "cancel·lació" per la raó de frescor ja vista (el senyal recent és el que importa), però no desapareix del mapa: a la lliçó 04-05 el recuperarem com a optimització — cacejar respostes amb TTL curt per abaratir la inferència sense renunciar del tot a la frescor. Molts sistemes reals de recomanació funcionen així: candidats precomputats en batch, refinament lleuger online.

Errors Comuns i Consells

  • Error: triar el patró per moda i no per consumidor. "Volem temps real" sense un cas que ho exigeixi porta a pagar infraestructura 24/7 per a dades que es miren un cop al dia. Pregunta sempre: qui espera la resposta i quant pot esperar?
  • Error: servir decisions de mil·lisegons des d'una taula batch envellida. L'invers de l'anterior. Si el senyal fresc és el que prediu (com en la cancel·lació de CineClick), una predicció de fa 6 dies pot ser pitjor que no mostrar res.
  • Error: tractar els patrons com a excloents. Un mateix model del registry pot alimentar batch i online alhora. El que no s'ha de duplicar és l'artefacte ni la lògica de features.
  • Error: oblidar la traçabilitat en batch. Una taula de prediccions sense data_scoring ni model_uri és una bomba de rellotgeria: ningú no sabrà quin model va generar quina llista quan màrqueting pregunti per què un client va rebre l'oferta.
  • Consell: escriu la decisió de patró al repo (un docs/decisions/ o el README), amb els consumidors, la frescor acordada i les alternatives descartades. D'aquí a sis mesos algú proposarà "passar-ho tot a streaming" i agrairàs tenir el raonament escrit.
  • Consell: dimensiona amb números, no amb adjectius. "Molt de trànsit" no és una dada; "40 cancel·lacions/hora en pic" sí, i probablement et sorprendrà que poc que és.

Exercicis

Exercici 1

Classifica cada cas en batch, online o streaming, i justifica-ho amb les tres preguntes (qui consumeix, frescor, volum):

a) Un banc decideix en el moment del pagament amb targeta si la transacció és fraudulenta. b) Una asseguradora calcula cada nit la propensió de renovació de tota la seva cartera per a l'equip comercial de l'endemà. c) Una app de logística recalcula el risc de retard d'un enviament cada cop que arriba un esdeveniment d'escaneig en un centre de distribució, per actualitzar l'avís al client en qüestió de minuts.

Exercici 2

L'equip de BI de CineClick demana "un endpoint d'API per consultar la probabilitat de churn de qualsevol client des dels seus dashboards, que es refresquen cada matí". Quin patró recomanes i per què? Quina pregunta faries abans de decidir?

Exercici 3

Modifica el pseudocodi de score_batch.py perquè, a més d'escriure el Parquet complet, generi un segon fitxer clients_risc_alt.parquet només amb els clients la probabilitat dels quals superi 0.7, ordenats de més a menys risc — el format que màrqueting vol per a la campanya.

Solucions

Solució 1

a) Online. Consumeix un altre sistema (el processador de pagaments) que bloqueja la transacció esperant la resposta; frescor de l'instant (les dades de la transacció acaben de néixer); volum de N peticions/s amb pressupost de mil·lisegons. És l'exemple canònic d'online. b) Batch. Consumeix un equip humà l'endemà; la frescor diària és suficient; volum de tota la cartera de cop. Ningú no espera en línia: job nocturn cap a una taula. c) Streaming. El disparador és un esdeveniment individual (l'escaneig), ningú no espera bloquejat la resposta, i la frescor requerida és de minuts, no de mil·lisegons ni de dies. Els esdeveniments flueixen per una cua i un consumidor amb el model els processa quan arriben.

Solució 2

Batch, servit des de la taula prediccions_churn que ja existirà per a màrqueting — els dashboards es refresquen cada matí, així que la frescor setmanal (o diària, si s'augmenta la cadència del job) és suficient i no hi ha ningú esperant en línia. Muntar un endpoint online per a això seria l'error típic: BI faria centenars de consultes contra una API 24/7 per obtenir dades que un JOIN amb la taula resol gratis. La pregunta prèvia: amb quina frescor real necessiteu la dada? Si la resposta fos "volem veure l'efecte d'una acció de retenció la mateixa tarda", caldria apujar la cadència del batch — i només si demanessin l'instant exacte es justificaria reutilitzar el servei online de 04-02.

Solució 3

    # ... després de construir `resultat` ...
    risc_alt = (
        resultat[resultat["probabilitat_abandonament"] > 0.7]
        .sort_values("probabilitat_abandonament", ascending=False)
    )
    risc_alt.to_parquet("data/clients_risc_alt.parquet", index=False)

Dos detalls a valorar: el llindar 0.7 és una decisió de negoci (quants clients pot absorbir la campanya), no un paràmetre del model — mereix viure en configuració, no hardcodejat; i el fitxer hereta les columnes de traçabilitat (data_scoring, model_uri), de manera que la llista que rep màrqueting continua sent auditable.

Conclusió

Ja tens el mapa: batch per a prediccions periòdiques i massives sense ningú esperant (barat, simple, el patró per defecte), online per a decisions al moment amb pressupost de mil·lisegons (car i exigent, però insubstituïble quan la frescor és el senyal), i streaming per reaccionar a esdeveniments individuals en segons (potent, però només amb la infraestructura que ho sostingui). I saps que l'elecció la dicta el consumidor — qui, amb quina frescor, a quin volum — no la moda. CineClick ho té decidit i documentat: batch setmanal per a la campanya de màrqueting (s'orquestrarà al mòdul 5) i un servei online per a l'oferta en el flux de cancel·lació, tots dos bevent del mateix models:/churn-cineclick@champion i de la mateixa construir_features. La lliçó següent construeix aquest servei online de debò: una API amb FastAPI que valida les entrades amb Pydantic, carrega el campió en arrencar, importa les features de la font única i respon en mil·lisegons amb la probabilitat d'abandonament. És hora que el model de la Laura atengui la seva primera petició.

© Copyright 2026. Tots els drets reservats