← Volver al blog

Spark Structured Streaming vs Apache Flink vs Kafka Streams: matriz de decisión para el ingeniero de datos

Latencia, estado, integración con el ecosistema, curvas de aprendizaje y casos reales en Cloudera CDP y Databricks — una comparación práctica para tomar decisiones de arquitectura de streaming.

Si trabajas con datos streaming en una empresa real, sabes que elegir el motor correcto no es un ejercicio académico. Es una decisión que determina tu latencia, tu costo operativo, la complejidad de tu plataforma y —lo más importante— la capacidad de tu organización para operar datos en tiempo real de forma sostenible.

Las plataformas de streaming no son un detalle técnico. Son una pieza fundamental de las arquitecturas modernas orientadas a eventos y procesamiento de datos en tiempo real.

He pasado los últimos años implementando pipelines streaming en Cloudera CDP (CDE, CFM) y Databricks, en clientes de banca, seguros y minería. He visto equipos elegir Spark por inercia, Flink por promesas técnicas, y Kafka Streams por cercanía al broker. Y he visto a todos arrepentirse al menos una vez —no porque la tecnología fuera mala, sino porque no era la adecuada para su contexto.

Regla de oro

No existe el mejor motor de streaming. Existe el mejor motor para tu escenario: latencia tolerable, ecosistema existente, madurez del equipo y costo operativo.

Los contendientes

Tres tecnologías aparecen con frecuencia en arquitecturas enterprise basadas en datos, Kafka y procesamiento distribuido.

Apache Flink es un motor especializado en procesamiento de streams, con un modelo de estado muy potente y herramientas avanzadas para event-time, ventanas y procesamiento con eventos fuera de orden.

Kafka Streams es la extensión natural de Kafka: una librería embebida que permite construir aplicaciones de procesamiento de eventos sin desplegar un cluster de procesamiento independiente.

Spark Structured Streaming es el todoterreno: si tu equipo ya trabaja con Spark, PySpark, DataFrames, SQL y plataformas como Cloudera CDE o Databricks, la transición desde batch hacia streaming resulta mucho más natural.

La tabla siguiente resume las diferencias principales.

Dimensión Spark Structured Streaming Apache Flink Kafka Streams
Latencia ~100 ms en micro-batch; Continuous Processing puede llegar a ~1 ms* Baja latencia, procesamiento continuo por evento Procesamiento por registro, latencia de milisegundos
Garantías Exactly-once en micro-batch; Continuous Processing → at-least-once Exactly-once para el estado con checkpoints; end-to-end según source/sink Exactly-once mediante Kafka Transactions / EOS
Estado Stateful operations y APIs de estado; menor flexibilidad que Flink para casos complejos Muy avanzado: Keyed State, Timers, Broadcast State, Process Functions State Stores
Ventanas Event-time, processing-time, tumbling, sliding, session Event-time, processing-time, tumbling, sliding, session Tumbling, hopping, session y otras ventanas
Integración batch Excelente: misma API DataFrame/Dataset Excelente: streams acotados y no acotados No es un motor batch generalista
Deploy Spark Cluster / Kubernetes / YARN / Standalone Flink Cluster / Kubernetes / YARN Librería embebida en la aplicación
Lenguajes Python, Scala, Java, R, SQL Java, Scala, Python, SQL Principalmente Java/Scala
Ecosistema CDP Muy fuerte con CDE/Spark + Cloudera Streaming Nativo: Cloudera Streaming Analytics (Flink) + SQL Stream Builder Nativo: Cloudera Streams Messaging (Kafka) + Surveyor + Schema Registry
Ecosistema Databricks Nativo: Spark, Struct. Streaming, Delta Lake, Unity Catalog Integración externa Integración mediante Kafka
Mejor escenario Batch + Streaming + Data Lake Streaming complejo, estado y event-time Aplicaciones y microservicios centrados en Kafka

* Los ~100 ms y ~1 ms son valores de referencia documentados por Spark para sus modelos de ejecución. No representan una garantía de latencia para cualquier aplicación o infraestructura. El modo Continuous Processing es experimental/legacy en documentación reciente y tiene restricciones de operaciones. El modo micro-batch es el modelo predeterminado y soporta las operaciones stateful de Structured Streaming.

Cuándo usar Spark Structured Streaming

Spark Structured Streaming convierte un cálculo batch en un procesamiento incremental sobre datos que llegan continuamente. El mismo modelo de programación basado en DataFrames/Datasets puede utilizarse para trabajar con datos estáticos y streaming. Esto permite reutilizar conocimientos y componentes existentes de Spark, incluyendo SQL, joins, agregaciones, ventanas y operaciones sobre DataFrames.

Por defecto, Structured Streaming utiliza un modelo de micro-batch. La documentación de Spark indica que este modelo puede alcanzar latencias de alrededor de 100 ms y proporciona garantías exactly-once para los escenarios compatibles.

Spark también dispone de Continuous Processing, un modo de baja latencia que puede alcanzar aproximadamente 1 ms, pero con garantías at-least-once y con restricciones importantes sobre las operaciones soportadas.

Por eso, no conviene resumir Spark simplemente como "Spark = 100 ms". La realidad es:

Spark Structured Streaming

Micro-batch
    ↓
~100 ms
Exactly-once
Soporte completo de Structured Streaming

Continuous Processing
    ↓
~1 ms
At-least-once
Restricciones de operaciones

La documentación actual de Spark también contempla modos de procesamiento de baja latencia adicionales, por lo que la elección debe hacerse según la versión y las capacidades concretas utilizadas.

El caso más fuerte para Spark aparece cuando ya tienes un ecosistema Spark. Si tu equipo escribe PySpark todos los días, tu plataforma es CDE o Databricks y tus pipelines batch y streaming viven sobre la misma plataforma, Spark te permite mantener un modelo de desarrollo muy similar entre ambos mundos.

El ahorro no está necesariamente en conseguir la menor latencia posible. El ahorro puede estar en no tener dos motores, dos plataformas, dos modelos operativos y dos conjuntos de habilidades completamente diferentes.

¿Dónde aparecen las diferencias con Flink?

Spark tiene soporte para operaciones stateful y ha evolucionado considerablemente en este aspecto. Sin embargo, cuando el problema requiere un control muy fino sobre el estado por clave, timers, procesamiento de eventos fuera de orden, lógica de procesamiento personalizada o patrones complejos de event-time, Flink ofrece un modelo más especializado.

La diferencia no es "Spark no tiene estado". La diferencia es: Flink ofrece un modelo de estado más granular y orientado al procesamiento de eventos.

Por ejemplo, Flink proporciona mecanismos como:

Esto resulta especialmente útil cuando la lógica de negocio depende de recordar y modificar información asociada a cada evento o clave.

Cuándo elegir Apache Flink

Flink fue diseñado alrededor del procesamiento de streams y ofrece un modelo especialmente fuerte para aplicaciones stateful. No es correcto reducir su ventaja simplemente a una cifra como "<5 ms".

La ventaja real está en su capacidad de realizar procesamiento continuo por evento con un control muy fino sobre estado, event-time, ventanas y recuperación ante fallos.

La latencia real dependerá de: tamaño de los eventos, paralelismo, número de operadores, particionamiento, red, serialización, backpressure, configuración de checkpoints, infraestructura y complejidad de la topología.

Por eso, en lugar de afirmar que Flink tiene una latencia fija de <5 ms, es más correcto decir que Flink está diseñado para escenarios de baja latencia y procesamiento continuo por evento.

Una de las mayores ventajas de Flink aparece cuando necesitas trabajar con estado complejo:

Kafka
  │
  ▼
Flink
  │
  ├── Keyed State
  ├── Timers
  ├── Event Time
  ├── Watermarks
  ├── Windows
  └── Process Functions
  │
  ▼
Resultado

Flink también permite manejar eventos fuera de orden mediante mecanismos de event-time y watermarks, algo especialmente importante en sistemas donde la llegada de los eventos no coincide necesariamente con el momento en que ocurrieron.

Exactly-once en Flink

Aquí también conviene hacer una precisión. Los checkpoints permiten que Flink mantenga consistencia del estado y pueda recuperarse ante fallos. Pero checkpoint no equivale automáticamente a end-to-end exactly-once. Para conseguir exactly-once de extremo a extremo, el source debe poder reproducir los datos y el sink debe soportar mecanismos transaccionales o idempotentes adecuados.

Por eso la comparación correcta es: exactly-once para el estado mediante checkpoints; end-to-end exactly-once depende de los sources y sinks utilizados.

El costo real de Flink

Flink tiene un costo operativo mayor cuando el equipo no está acostumbrado a administrar aplicaciones de streaming stateful. Hay que considerar: checkpoints, savepoints, paralelismo, state backend, backpressure, recuperación, administración del estado, escalamiento, observabilidad y evolución de jobs stateful.

Esto no significa que Flink sea necesariamente más caro. Significa que la complejidad operativa y el modelo de programación deben justificarse por las necesidades del negocio.

Si tu aplicación necesita latencias extremadamente bajas, procesamiento complejo por evento o un modelo de estado avanzado, esa complejidad puede estar completamente justificada.

Un ejemplo práctico

Imagina un sistema de monitoreo de transacciones bancarias.

Con Spark puedes procesar eventos en micro-batches:

Kafka
  │
  ▼
Spark Structured Streaming
  │
  ├── micro-batch
  ├── transformación
  ├── agregación
  └── detección
  │
  ▼
Resultado

Si el negocio acepta una latencia del orden de cientos de milisegundos o segundos, Spark puede ser perfectamente suficiente.

Ahora imagina que necesitas: detectar patrones complejos, mantener estado por cliente, reaccionar ante eventos individuales, utilizar timers, manejar eventos fuera de orden y reaccionar inmediatamente ante determinadas secuencias. En ese escenario Flink puede ofrecer un modelo más natural.

La pregunta entonces no es "¿Flink es más rápido que Spark?". La pregunta correcta es: ¿La latencia y complejidad adicional que introduce Spark son un problema real para mi negocio?

Si la respuesta es no, cambiar de motor puede agregar complejidad sin generar valor proporcional.

Cuándo Kafka Streams es suficiente

Kafka Streams no necesita un cluster de procesamiento independiente. Es una librería que se embebe en una aplicación, normalmente Java, que se conecta directamente a Kafka.

Kafka Topic
    │
    ▼
Kafka Streams Application
    │
    ├── Filter
    ├── Map
    ├── Join
    ├── Aggregate
    └── State Store
    │
    ▼
Kafka Topic

Esto es especialmente atractivo cuando toda la arquitectura ya está construida alrededor de Kafka. Kafka Streams utiliza el modelo de particionamiento de Kafka para escalar horizontalmente y ofrece state stores locales para operaciones stateful como agregaciones y joins. También proporciona exactly-once mediante las capacidades transaccionales de Kafka.

¿Dónde brilla Kafka Streams?

Kafka Streams es una excelente alternativa para: enriquecimiento de eventos, filtros, transformaciones, agregaciones, joins entre topics, procesamiento de eventos, aplicaciones Java orientadas a Kafka y microservicios event-driven.

Topic clientes
      │
      ├──────────┐
      │          │
      ▼          ▼
   Kafka Streams
      │
      ├── Join
      ├── Aggregate
      └── Enrichment
      │
      ▼
Topic resultado

Para este tipo de escenario, levantar un cluster Spark o Flink puede ser innecesario.

¿Y el procesamiento histórico?

Aquí hay una diferencia importante. Kafka Streams no es un motor batch generalista como Spark. Eso no significa que sea incapaz de reprocesar datos históricos — si los eventos históricos todavía están disponibles en Kafka, es posible volver a procesarlos desde offsets anteriores.

El problema aparece cuando necesitas realizar trabajos como: procesar terabytes históricos, ejecutar backfills, consultar múltiples fuentes, realizar transformaciones batch complejas, procesar archivos en Data Lake o ejecutar procesos SQL masivos. Para esos casos, Spark suele resultar mucho más natural.

Otra consideración es el lenguaje. Kafka Streams está fuertemente orientado a Java/Scala, mientras que Spark permite trabajar con Python, Scala, Java, R y SQL. Para un equipo completamente orientado a PySpark, esta diferencia puede ser significativa.

Matriz de decisión rápida

Usa esta guía para decidir en 30 segundos:

Si tu escenario es... Motor recomendado
Ecosistema Spark existente + batch + streaming unificado Spark Structured Streaming
Baja latencia + estado complejo + eventos fuera de orden Apache Flink
Solo Kafka + transformaciones simples + baja latencia Kafka Streams
Equipo Python + experiencia fuerte en Spark Spark Structured Streaming
Necesidad de Keyed State, Timers y lógica compleja por evento Apache Flink
Microservicio Java que lee de Kafka, procesa y escribe en Kafka Kafka Streams
Data Lake + procesos batch + streaming Spark Structured Streaming
Plataforma centrada completamente en Kafka Kafka Streams
Arquitectura CDP basada en CDE/Spark Spark Structured Streaming
Arquitectura Cloudera que soporte Flink + necesidad real de streaming complejo Apache Flink
Databricks + Data Lake + streaming Spark Structured Streaming

El factor humano

Hay una variable que no aparece en los benchmarks: tu equipo.

Si tu equipo escribe PySpark todos los días y sabe desarrollar, optimizar y depurar jobs de Spark, introducir Flink implica incorporar un nuevo modelo mental. No solamente hay que aprender otra API. También hay que entender state management, checkpoints, savepoints, backpressure, event-time, watermarks, paralelismo, recuperación, observabilidad y operación de aplicaciones stateful.

Por otro lado, si tu equipo ya tiene experiencia fuerte con sistemas distribuidos y streaming, Flink puede ser una excelente inversión cuando el negocio realmente necesita esas capacidades.

Y si todo tu stack es Kafka y tu equipo desarrolla principalmente aplicaciones Java, Kafka Streams puede reducir considerablemente la fricción operativa.

Por eso mi recomendación práctica sería: Spark por defecto, Flink por necesidad, Kafka Streams por cercanía a Kafka. No porque uno sea universalmente mejor, sino porque cada tecnología optimiza un problema diferente.

¿Cómo tomar la decisión?

Una forma sencilla de decidir es comenzar por la necesidad y no por la tecnología.

¿Necesitas streaming?
        │
        ▼
¿Ya tienes Spark?
        │
   ┌────┴────┐
   │         │
  Sí        No
   │         │
   ▼         ▼
¿Latencia   ¿Tu arquitectura
muy baja?   está centrada en Kafka?
   │         │
 ┌─┴─┐     ┌─┴─┐
No   Sí    Sí   No
 │    │     │    │
 ▼    ▼     ▼    ▼
Spark Flink Kafka  Evaluar
                  Spark/Flink

La arquitectura existente debe pesar tanto como la latencia.

Si ya tienes CDE + Spark + Airflow + Iceberg + Hive + Data Lake, incorporar Spark Structured Streaming puede ser mucho más sencillo operacionalmente.

Si tienes Kafka + Topics + Schema Registry + Microservicios + Java, Kafka Streams puede ser una opción natural.

Si tienes Kafka + Flink + Keyed State + Timers + Event Time + Eventos Complejos, entonces Flink empieza a tener una ventaja clara.

Cloudera Streaming: la plataforma integrada

Si trabajas en el ecosistema Cloudera, no eliges motores de streaming de forma aislada. Cloudera los empaqueta como una plataforma integrada llamada Cloudera Streaming, que reúne Apache Kafka y Apache Flink bajo un mismo paraguas operativo y de gobierno.

Los componentes principales son:

Cloudera Streaming

Cloudera Streams Messaging
  └── Apache Kafka (transporte resiliente)
  └── Schema Registry (schemas versionados)
  └── Surveyor (observabilidad en K8s)

Cloudera Streaming Analytics
  └── Apache Flink (procesamiento stateful)
  └── SQL Stream Builder (Flink SQL vía UI)

Cloudera Data Flow
  └── NiFi (ingesta segura)
  └── Kafka (tránsito)
  └── Flink (procesamiento en tiempo real)

Cloudera SDX
  └── Gobierno unificado: acceso, lineage, auditoría

Cloudera Streams Messaging proporciona Kafka como backbone de mensajería empresarial, con Schema Registry para gestionar esquemas de forma centralizada y versionada, y Surveyor para observabilidad en Kubernetes. Es la capa de transporte que garantiza durabilidad y resiliencia.

Cloudera Streaming Analytics es la capa de procesamiento basada en Apache Flink, e incluye SQL Stream Builder, una interfaz que permite a analistas y data scientists construir y gestionar jobs de Flink SQL sin escribir código Java. Esto amplía el rango de usuarios que pueden crear pipelines streaming.

Cloudera Data Flow (CFM) completa el ciclo combinando NiFi para ingesta segura, Kafka para tránsito y Flink para procesamiento en tiempo real. Y todo ello bajo Cloudera SDX, que unifica gobierno, acceso, lineage y auditoría a través de la plataforma.

Esto cambia la decisión. En Cloudera no eliges entre Spark y Flink como si fueran mutuamente excluyentes: tienes CDE para Spark (batch + streaming unificado) y Cloudera Streaming para Kafka + Flink (procesamiento stateful y baja latencia). Ambas conviven en la misma plataforma, bajo el mismo modelo de gobierno.

En la práctica

CDE para Spark Structured Streaming (batch + streaming unificado, PySpark, DataFrames) y Cloudera Streaming para Kafka + Flink (procesamiento complejo, baja latencia, SQL Stream Builder) no son competidores. Son dos herramientas del mismo stack que resuelven problemas diferentes.

La decisión no es solo técnica

Uno de los errores más comunes es seleccionar un motor exclusivamente por benchmark. Por ejemplo: "Flink tiene menor latencia, entonces debemos utilizar Flink." No necesariamente. Si tu negocio necesita 500 ms de latencia y Spark puede entregar 100 ms, entonces una arquitectura Flink podría estar resolviendo un problema que realmente no existe.

De la misma manera: "Ya tenemos Spark, entonces todo debe hacerse con Spark." Tampoco necesariamente. Si necesitas procesamiento de eventos con estado complejo, timers y una latencia muy baja, mantener Spark solamente por estandarización puede terminar generando una arquitectura menos adecuada.

La decisión debe considerar:

             ┌───────────────┐
             │   Negocio     │
             └───────┬───────┘
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
       Latencia    Estado     Volumen
          │          │          │
          └──────────┼──────────┘
                     ▼
              ┌─────────────┐
              │ Arquitectura│
              └──────┬──────┘
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
        Spark       Flink    Kafka Streams

Conclusión

Los tres motores son excelentes en lo que hacen.

Spark Structured Streaming te da una excelente integración entre batch y streaming y resulta especialmente atractivo cuando ya existe un ecosistema Spark, Data Lake y procesos desarrollados con DataFrames, SQL o PySpark.

Apache Flink ofrece un modelo especialmente potente para streaming stateful, event-time, ventanas y procesamiento complejo por evento. Es una opción especialmente interesante cuando la latencia y la complejidad del estado son requisitos reales del negocio.

Kafka Streams ofrece simplicidad y cercanía a Kafka. Cuando la aplicación es esencialmente un microservicio que consume eventos de Kafka, los procesa y vuelve a publicarlos en Kafka, puede ser innecesario incorporar un motor distribuido adicional.

La pregunta no es ¿cuál es el mejor motor de streaming?. La pregunta es ¿cuál es el mejor motor para mi stack, mi equipo y mi latencia?

Si respondes eso primero, la tecnología correcta se elige mucho más fácilmente.

Y existe una última consideración que suele olvidarse: la mejor arquitectura no es necesariamente la que tiene menor latencia. Es la que cumple los requisitos del negocio con la menor complejidad operativa razonable.

Tu motor de streaming es una pieza. La transformación es el tablero completo.

— Jorge. Data Engineer en Accenture, trabajando con streaming en Cloudera CDP y Databricks.

Fuentes

¿Te sirvió esta guía? Invítame un café ☕

☕ Invitar un café