La transformación digital del sector financiero ha colocado el procesamiento de datos en su núcleo, con empresas como Nubank aprovechando plataformas sofisticadas como Spark para sus operaciones. Spark, un marco de computación distribuida de código abierto, ofrece una solución robusta para manejar grandes volúmenes de datos.

Sin embargo, dominar sus complejidades no es una tarea sencilla. Sigue leyendo para profundizar en la mecánica de Spark, los desafíos únicos de procesamiento de datos que Nubank enfrenta y las estrategias que empleamos para navegar por estas complejidades.

Una breve introducción a Spark

Spark es esencialmente un marco de computación distribuida que ejecuta códigos en paralelo a través de varias máquinas. Diseñado principalmente para el procesamiento de Big Data, se destaca por ser completamente de código abierto.

¿Cómo funciona Spark?

La arquitectura de Spark se compone de varios componentes, de los cuales los siguientes son fundamentales:

  • Cluster: Un grupo de JVMs.
  • Driver: Responsable de interpretar el programa del usuario (consulta), analizar, distribuir y programar las tareas en los ejecutores. Puedes pensar en el driver como el cerebro detrás de las operaciones.
  • Executor: Estos realizan el trabajo pesado. Procesan segmentos específicos de datos, conocidos como particiones, ejecutando el código designado por el driver.

Descubre las oportunidades

Desafíos de procesamiento de datos de Nubank con Spark

Con una creciente variedad de productos y un rápido crecimiento, Nubank identificó la necesidad de una mejor orquestación de comunicaciones para evitar que los clientes recibieran comunicaciones simultáneas sobre varios productos.

Estableciendo el escenario: orquestador de comunicaciones

Para abordar esto, se creó una infraestructura dentro de nuestro proceso ETL (Extract, Transform, Load). Esta infraestructura estaba compuesta por módulos como:

  • Módulo de productos: Permitía a los usuarios añadir nuevos productos sobre los que querían comunicar.
  • Módulo de segmentación de clientes:Facilitaba la comunicación basada en diversos criterios, como edad o ingresos.
  • Módulo de campañas: Especificaba el tipo de comunicación, ya sea un correo electrónico o una notificación.

Los usuarios agregaban nuevos segmentos de clientes y campañas a esta estructura. Luego, los detalles de las campañas se almacenaban en conjuntos de datos. Para determinar qué segmento de clientes recibiría qué campaña, se realizaba una operación de join entre los segmentos y las campañas. Esta combinación se denominó como la pipeline.

El problema en cuestión

Inicialmente, los usuarios agregaban solo un puñado de segmentos y campañas para el mismo producto. Sin embargo, en ciertas ocasiones, la cantidad de segmentos y campañas añadidos se disparaba, lo que llevaba a un aumento de diez veces en los conjuntos de datos de campañas. Esto se acompañaba de un aumento significativo en la creación de segmentos, que no se persistían en los conjuntos de datos.

Este aumento inesperado provocó la aparición de mensajes de error. En Databricks, una herramienta de análisis popular basada en Spark, aparecía el error driver stopped unexpectedly (el controlador se detuvo inesperadamente). En el lado del proceso ETL, se señalaba un error de timeout exception (excepción por tiempo de espera).

Posibles culpables: broadcast join o falta de memoria en el driver

Surgieron dos sospechas principales:

  1. Broadcast join: Spark elige el tipo de operación de join basado en el tamaño de los datos. Cuando hay una disparidad significativa entre los tamaños de los conjuntos de datos, puede optar por un broadcast join. Si Spark elige erróneamente este tipo de join, podría exceder el tiempo de espera de broadcast preestablecido, causando el error.
  2. Falta de memoria en el driver: Esto puede ocurrir cuando una acción en una consulta envía resultados al driver que son demasiado grandes para su memoria, lo que lleva a un agotamiento de la memoria.

Usando SparkUI en Databricks, se discernió que los joins realizados entre segmentos y campañas no eran del tipo broadcast, sino del tipo sort merge. Esta observación eliminó la hipótesis del broadcast join.

Una investigación adicional mostró que el proceso se detenía durante la unión de varias tablas. Curiosamente, cuando se trasladó a un clúster general con más memoria, la consulta se ejecutó sin problemas, lo que insinuó un problema de memoria.

Al fusionar una multitud de conjuntos de datos, existe el riesgo de sobrecargar el driver. Para mitigar esto de manera preventiva, hemos instituido pruebas para asegurar que los usuarios no agreguen más de un cierto número de segmentos, específicamente 40. Este número actúa como un buffer antes de que el sistema falle.

Flujos de datos

En el corazón de nuestros desafíos están los colosales flujos de datos provenientes de los eventos de la aplicación. Con más de 59 millones de usuarios, registramos:

  • Más de 1,000 millones de eventos desencadenados diariamente.
  • Un agregado de más de 100 terabytes de datos de eventos que requieren procesamiento diario.

Algunos desafíos principales respecto a los eventos de la aplicación son:

  • Deduplicación de eventos: A menudo, los clics se registran múltiples veces y necesitan ser deduplicados dentro de Spark. 
  • Procesamiento diario de datos: Asegurarse de que los datos más recientes estén siempre disponibles para nuestros analistas.
  • Exploración de datos:La vastedad de los datos hace que la exploración sea intrincada en nuestras plataformas. 

Las soluciones a estos problemas incluyen:

  • Deduplicación:  Al garantizar que los eventos se produzcan y se entreguen solo una vez (tanto en la aplicación como a nivel del Message Broker), se minimiza la carga sobre Spark para la deduplicación. 
  • Procesamiento incremental: En lugar de procesar toda la base de datos diariamente, procesar los datos de manera incremental, día tras día.
  • Filtrado de datos: Ofrecer a los analistas conjuntos de datos filtrados puede acelerar el procesamiento de datos en plataformas como Databricks o BigQuery.

Manejo de conjuntos de datos más pequeños

Por otro lado, también necesitamos analizar conjuntos de datos más pequeños, como encuestas de satisfacción del cliente. El desafío aquí es diferente. Al tratar con tamaños de datos modestos, como 20 MB, Spark puede parecer excesivo. Sin embargo, centralizar los datos para un análisis a nivel de toda la empresa justifica este enfoque.

Desafíos con CSV:

  1. Garantía de esquema: Asegurar que la estructura del CSV se mantenga consistente.
  2. Linaje de datos y versionado:Hacer un seguimiento de las versiones de los datos, especialmente cuando se reciben de manera ad-hoc. 
  3. Sobrecarga de Spark:La sobrecarga de usar Spark para conjuntos de datos más pequeños.

Las soluciones incluyen:

  • Garantía de esquema en JSON:Si un CSV no cumple con el esquema garantizado, no se procesará.
  • Versionado automatizado: Todas las versiones de los datos se almacenan automáticamente en el Data Lake. Junto a los datos, los esquemas también son versionados.

Profundizando: desafíos específicos de Spark

Al tratar con tamaños de datos variados, Spark trae sus propias complicaciones:

  1. Spill: Los datos se mueven al disco si no caben en la RAM. Si el disco se queda sin memoria, el procesamiento puede terminar.
  2. Skew:  Esto ocurre cuando una partición de datos es significativamente más grande que las demás. En casos extremos, mientras todas las demás particiones han concluido, una sigue procesándose.
  3. Shuffle: Mover datos entre particiones, especialmente durante operaciones como join o groupby, puede llevar mucho tiempo.

Para resolver estos problemas, usamos:

  • Optimización de parámetros en Spark: Las configuraciones predeterminadas pueden no ser suficientes, por lo que la optimización basada en el origen de los datos es esencial.
  • Shuffle partitions y maxPartitionBytes: Estos parámetros pueden ajustarse para gestionar mejor las particiones de datos.

Adoptando Spark 3

La reciente migración a Spark 3 ha introducido una gran cantidad de parámetros para la optimización. Características como AQE (Adaptive Query Execution) ayudan a manejar automáticamente los skew joins. Existe una capacidad inherente para cambiar de sort-merge join a broadcast join durante el tiempo de ejecución. Spark 3 también automatiza la configuración de las particiones, asegurando tamaños óptimos de partición durante el procesamiento.

El recorrido de Nubank en el procesamiento de enormes cantidades de datos usando Spark ofrece una mirada reveladora a los desafíos prácticos y las posibles soluciones para las empresas que navegan en la era del Big Data. Subraya la necesidad de agilidad, previsión y un enfoque proactivo para gestionar y optimizar los flujos de trabajo de procesamiento de datos.

Con la evolución continua de Spark, como se evidencia en la transición a Spark 3, las organizaciones pueden esperar herramientas y características aún más refinadas. Al aprender de pioneros como Nubank, las empresas pueden posicionarse mejor para capitalizar los beneficios de Spark y llevar sus estrategias de datos a nuevas alturas.

Mira lo que compartimos sobre este tema en Meetup a continuación:

Descubre las oportunidades