Background Image
INFORMACIÓN TÉCNICA

Diseño de una canalización ETL on-premise para 10 TB/día con una latencia inferior al segundo mediante Spark, Kafka y Hive

Headshot -  Manik Hossain
Manik Hossain
Senior Consultant

June 27, 2025 | 5 Minuto(s) de lectura

Procesar 10 TB de datos al día con latencias inferiores al segundo no es sólo territorio de la nube. He aquí cómo lo conseguimos in situ.

Diseñar una canalización ETL local de baja latencia y alto rendimiento que pueda procesar más de 8-10 TB de datos diarios parece un sueño nativo de la nube. Pero en Improvinghemos creado exactamente eso, aprovechando una sólida pila de código abierto centrada en Apache Kafka, Apache Spark, Scala/Python y Hive, todo ello ejecutado en nuestro centro de datos local.

En este artículo, le explicaré cómo hemos diseñado un sistema en tiempo real con:

  • Latencia de eventos inferior al segundo

  • Rendimiento de más de 100.000 eventos/segundo

  • Fiabilidad a escala

  • Uso de Scala/Python y herramientas de código abierto

La necesidad empresarial

En Improving, necesitábamos crear una canalización ETL de nivel empresarial para gestionar datos de telemetría no estructurados masivos (JSON, XML) procedentes de múltiples fuentes. Los requisitos eran

  • Latencia inferior a un segundo desde la ingesta de eventos hasta la disponibilidad de la consulta

  • Alta disponibilidad y tolerancia a fallos

  • Capacidad para escalar hasta 10 TB/día de datos de eventos sin procesar.

  • Flexibilidad de esquema para admitir esquemas IoT en evolución

Pila tecnológica (in situ)

Elegimos tecnologías probadas y escalables:

Asset - Image 1 - Designing an On-Prem ETL Pipeline for 10TB/day with Sub-Second Latency Using Spark, Kafka, and Hive

Diseño del sistema local:

Asset - Image 2 - Designing an On-Prem ETL Pipeline for 10TB/day with Sub-Second Latency Using Spark, Kafka, and Hive

Kafka: La columna vertebral en tiempo real

  • Temas particionados para el paralelismo (más de 100 particiones)

  • Compresión habilitada (LZ4)

  • Factor de replicación = 3 para tolerancia a fallos

  • Los productores envían eventos en formato Avro con Schema Registry

Diseño clave para baja latencia:

  • Kafka está ajustado para baja latencia de lote.ms = 1msy acks=1 para priorizar la velocidad (con redundancia en la capa de almacenamiento).

  • spark.streaming.backpressure.enabled=true

Spark Structured Streaming: Magia de latencia por debajo del segundo

Nuestros trabajos de streaming en PySpark fueron ajustados para micro-batching:

  • Tamaño de lote: Microlotes dinámicos, limitados a ~1 segundo de datos

  • Agregaciones con estado: activadas con marcas de agua

  • Puntos de control: Directorio de puntos de control basado en HDFS para exactamente una vez

Para evitar la sobrecarga de serialización, analizamos los mensajes utilizando fastavro en UDFs y escribimos la salida utilizando DataFrameWriter con formato parquet (ideal para Hive).

Hive: Almacenamiento escalable con búsquedas rápidas

  • Tablas externas particionadas (por dt_skey, proveedor)

  • Parquet comprimido con Snappy para optimizar el rendimiento de E/S y permitir lecturas columnares rápidas con predicate pushdown en Hive

  • Motor Tez optimizado con: Filtros Bloom, Ejecución vectorizada, Poda de particiones

Cada lote de Spark escribía directamente en HDFSy Hive recogía los nuevos datos mediante la reparación de particiones, por ejemplo, activando la función MSCK REPARAR TABLA o ALTER TABLE ADD PARTITION (activado por Airflow).

Optimizaciones clave

1. Reducción de la sobrecarga de microlotes

El ajuste de spark.sql.shuffle.partitions y la reutilización de serializadores mantuvo cada microlote por debajo de 50 ms.

Parámetros de ajuste de Spark: executor-cores=5 memoria de ejecutor=8G número de ejecutores = 50 spark.sql.shuffle.partitions=600 → optimizado en base al recuento de particiones de Kafka spark.memory.fraction=0.8 → permite más memoria para barajar y almacenar en caché.

2. Broadcast Joins para datos de referencia

Uno de los principales asesinos de rendimiento en sistemas distribuidos como Spark son las uniones de shuffle-heavy. Para mitigarlo, difundimos pequeños conjuntos de datos de referencia. Sustituimos las uniones aleatorias por uniones de difusión cuando las tablas de consulta pesaban menos de 100 MB.

broadcast_df = spark.read.parquet("/hdfs/ruta/ref_data").cache() ref_broadcast = F.broadcast(broadcast_df)

joined_df = streaming_df.join(ref_broadcast, "region_code", "left")

  • Spark envía los ref_datos a todos los ejecutores.

  • No es necesario barajar el gran conjunto de datos de transmisión.

  • Resultado: Cero shuffle, join más rápido, latencia por debajo del milisegundo.

3. Lecturas paralelas de Kafka

Cada partición de Kafka se asignó a un hilo ejecutor de Spark dedicado (sin cuellos de botella de partición conjunta).

Configuración de Kafka para un alto rendimiento - Particionamiento de temas: más de 100 particiones para distribuir la ingesta - Factor de replicación: 3(para failover) - Compresión: lz4 para una latencia más baja y una compactación decente - Tamaño de mensaje: limitado a 1MB para evitar bloqueos

4. Cero pausas de GC

Spark está configurado con G1GC y ajuste de memoria para evitar bloqueos de la JVM durante los picos de ingesta. Problema: Las pausas de recolección de basura y la presión de memoria ralentizan la finalización de microlotes.

Ejemplo Uso de G1GC y ajuste: -XX:+UseG1GC -XX:InitiatingHeapOccupancyPercent=35 -XX:+ExplicitGCInvokesConcurrent

5. 5. Gestión de la desviación de los datos

Utilización de salting y repartitioning para evitar tareas masivas de shuffle en tipos de eventos raros.

Ejemplo de salting y reparticionamiento: from pyspark.sql.functions import when, concat_ws, floor, rand

# Aplicar sal sólo a la clave sesgada salted_df = df.withColumn( "tipo_evento_salado", when(col("tipo_evento") == "ENTREGADO", concat_ws("_", col("tipo_evento"), floor(rand() * 10).cast("int")) ).otherwise(col("tipo_evento")) )

# Reparticionar para distribuir la carga de forma más uniforme repartitioned = salted_df.repartition(10, "event_type_salted")

6. Compactación de archivos pequeños (Hive + HDFS)

Parquet + Snappy como formato por defecto para todas las salidas batch/streaming de Spark. Escribir microlotes cada pocos milisegundos crea montones de pequeños archivos parquetque:

  • Ralentizan las consultas Hive

  • Sobrecarga HDFS NameNode

  • Causan sobrecarga de memoria

Optimización: - Ejecutar un trabajo de compactación diario/rodante usando Spark para fusionar archivos - Utilizar la lógica de partición y agrupación de Hive (evitar particiones demasiado pequeñas) - Combine la salida utilizando .coalesce(n) o .repartition(n) antes de escribir.

Supervisión y capacidad de observación

  • Kafka: monitorizado mediante exportadores Prometheus y alertas de retraso

  • Spark: integrado con Grafana para métricas de memoria/CPU del ejecutor

  • Retraso de extremo a extremo: medido desde la marca de tiempo de escritura en Kafka hasta la marca de tiempo de escritura en Hive, almacenado en la base de datos de métricas.

Hemos creado un panel de latencia para inspeccionar visualmente dónde se producen los cuellos de botella, útil para el ajuste proactivo.

Pruebas y análisis comparativos

Antes de la producción - Simulamos una ingesta de 10 TB/día utilizando productores Kafka. - Comparación con 3 años de datos históricos - Realizamos pruebas de estrés con eventos de ráfaga sintéticos (3 veces la carga normal).

Lecciones aprendidas

  • Pruebe siempre la latencia de extremo a extremo, no sólo el rendimiento por capa.

  • Hive puede funcionar en tiempo real si se particiona de forma inteligente y se escribe de forma eficiente.

  • On-prem no significa lento con el ajuste correcto, open-source puede rivalizar con las pilas nativas de la nube.

  • Spark Structured Streaming puede ofrecer absolutamente pipelines on-prem de baja latencia.

  • La estrategia de particionamiento importa más que la potencia de cálculo

  • El viejo Hive todavía puede brillar cuando se ajusta adecuadamente

  • Los mecanismos de contrapresión de Kafka salvan vidas en los picos de carga.

Conclusión

En Improving, creemos que la ingeniería del rendimiento es un deporte de equipo. Diseñar una canalización de 8-10 TB/día on-prem con una latencia inferior a un segundo era ambicioso, pero utilizando Kafka + Spark + Hive con un ajuste inteligente, conseguimos que funcionara.

Si estás construyendo ETL pipelines a gran escala o modernizando sistemas on-prem, no dudes en ponerte en contacto con nosotros.

Información técnica

Reflexiones más recientes

Explore las entradas de nuestro blog e inspírese con los líderes de opinión de todas nuestras empresas.
Thumbnail: Tech Insights
Información técnica

Diseño de una canalización ETL on-premise para 10 TB/día con una latencia inferior al segundo mediante Spark, Kafka y Hive

Cómo procesamos 10 TB/día con una latencia inferior al segundo en nuestras instalaciones, utilizando código abierto y Scala/Python.