Joins, agregaciones y Watermarks en Streaming
Objetivo: entender por qué Spark necesita guardar datos temporalmente (state), cómo combinar streams y cómo evitar que el estado crezca indefinidamente.
1. ¿Qué es el estado (State)?
Imagina que queremos calcular las ventas de cada farmacia cada 5 minutos.
Llegan tres tickets:
Hora del ticket | Farmacia | Importe |
|---|---|---|
10:01 | 109 | 100 € |
10:02 | 109 | 50 € |
10:04 | 109 | 75 € |
Spark tiene que recordar las cantidades anteriores mientras espera nuevos eventos.
Streaming Events
100 € → 50 € → 75 €State Store
225 €
Farmacia 109 · Ventana 10:00–10:05Resultado agregado
Ese acumulador de 225 € forma parte del estado.
El problema: si tenemos millones de farmacias, clientes o ventanas temporales, el estado puede crecer muchísimo.
Aquí es donde entran los watermarks.
2. ¿Qué es un Watermark?
Un watermark indica hasta qué punto del tiempo de los eventos podemos considerar que los datos antiguos han llegado.
Por ejemplo:
.withWatermark("event_time", "10 minutes")Significa que permitimos un retraso de referencia de 10 minutos respecto al avance del tiempo de evento observado.
Simplificando:
Watermark≈Max(event_time)−10 minutos
Si el evento más reciente tiene hora 10:30, el watermark de referencia sería 10:20.
En una agregación por ventanas, Spark puede cerrar y liberar el estado de aquellas ventanas cuya finalización queda por detrás del watermark. Un evento demasiado tardío puede quedar descartado. Databricks on AWS
3. Simulador: ¿qué ocurre con un ticket que llega tarde?
Supongamos que el último evento observado es de las 10:30.
15 minutos
5 min
10 min
15 min
10:18
Watermark de referencia
10:15
Ventana del ticket
10:15–10:20
Ventana todavía abierta
El ticket todavía puede contribuir al agregado.
La gran decisión: cuanto mayor sea la tolerancia, más eventos tardíos podemos admitir, pero también tendremos que conservar estado durante más tiempo.
4. Código real: ventas cada 5 minutos
Vamos a crear una Streaming Table que calcula las ventas por farmacia y ventana temporal.
from pyspark import pipelines as dp
from pyspark.sql import functions as F
@dp.table(name="sales_5min")
def sales_5min():
tickets = (
spark.readStream.table("silver_tickets")
.withWatermark("event_time", "10 minutes")
)
return (
tickets
.groupBy(
F.window("event_time", "5 minutes"),
F.col("pharmacy_id")
)
.agg(
F.sum("amount").alias("total_sales")
)
)
Tres ideas:
withWatermarkestablece cuánto retraso admitimos.windowagrupa los tickets en intervalos de 5 minutos.groupBy + summantiene el estado necesario para sumar los eventos.
Importante: con una agregación streaming en modo append, los resultados de una ventana se publican cuando esta se cierra según el watermark, no necesariamente cada 5 minutos. Para un KPI operacional que deba mostrar importes parciales inmediatamente, este diseño puede no ser el apropiado.
Databricks recomienda watermarks en las agregaciones streaming para controlar el estado y mantener el procesamiento incremental. Databricks on AWS
5. Joins: dos escenarios diferentes
A. Stream + tabla estática
Queremos enriquecer cada ticket con el nombre de su farmacia.
@dp.table(name="enriched_tickets")
def enriched_tickets():
tickets = spark.readStream.table("silver_tickets")
pharmacies = spark.read.table("dim_pharmacy")
return tickets.join(
pharmacies,
"pharmacy_id",
"left"
)Es un stream-static join. Normalmente no necesitamos watermark para este caso.
Pero hay un matiz: si mañana cambia el nombre de una farmacia, los tickets ya procesados no se actualizan automáticamente. El join se aplica cuando se procesa cada microbatch. Databricks on AWS
B. Stream + Stream
Imagina dos streams:
tickets: compras realizadas.app_tracking: interacciones del cliente con la aplicación.
Queremos relacionar una compra con interacciones que ocurrieron durante los 10 minutos anteriores.
tickets = (
spark.readStream.table("silver_tickets")
.withWatermark("event_time", "15 minutes")
)
tracking = (
spark.readStream.table("app_tracking")
.withWatermark("event_time", "15 minutes")
)
result = tickets.alias("t").join(
tracking.alias("a"),
"""
t.sso_id = a.sso_id
AND a.event_time BETWEEN
t.event_time - INTERVAL 10 MINUTES
AND t.event_time
""",
"inner"
)Aquí tenemos dos condiciones fundamentales:
Watermarks en ambos streams: delimitan cuánto estado necesitamos conservar.
Condición temporal del join: especifica hasta cuándo dos eventos pueden coincidir.
Sin estos límites, Spark podría necesitar conservar una enorme cantidad de eventos antiguos buscando posibles coincidencias. Databricks on AWS
6. ¿Qué elegiría para vuestra plataforma?
Caso de uso | Estrategia inicial |
|---|---|
Tickets + dimensión farmacia | Stream-static join |
Tickets + App Tracking correlacionados | Stream-stream join con watermarks |
Ventas agregadas cada 5 min | MV Gold o agregación streaming según latencia |
Ventas diarias históricas | Materialized View |
Para los KPIs diarios que mostráis en Power BI, priorizaría una Materialized View si necesitáis incorporar correcciones históricas y mantener resultados actualizados, en lugar de perder tickets que llegan tarde por un watermark demasiado pequeño.
7. Mini ejercicio
Tienes dos streams:
Tickets de compra.
Eventos de App Tracking.
Quieres relacionar las compras con eventos de tracking próximos en el tiempo, pero te preocupa que Spark termine consumiendo demasiada memoria.
¿Cuál es la mejor solución?
¡Correcto!
Ambos mecanismos permiten limitar el estado que Spark necesita mantener. La tolerancia debe elegirse según el retraso real de los eventos.Qué debes recordar:
State: memoria lógica de eventos anteriores necesaria para calcular resultados.
Watermark: controla cuándo pueden descartarse estados antiguos y eventos demasiado tardíos.
Stream-static join: relativamente sencillo, pero no corrige automáticamente resultados históricos.
Stream-stream join: necesita una estrategia explícita para limitar el estado.
La siguiente lección será especialmente útil para evaluar Lakeflow frente a dbt: cómo funcionan realmente las actualizaciones incrementales y cuándo Spark tiene que recalcular datos.
Una idea más en tu cuaderno.
Marca esta lección cuando hayas terminado de repasarla.
Material importado del curso original. Simulaciones conservadas como ejemplos estáticos; para sus controles interactivos, consulta el original.