A transformação digital do setor financeiro colocou o processamento de dados no centro das operações, com empresas como o Nubank aproveitando plataformas sofisticadas como o Spark. O Spark, um framework de computação distribuída de código aberto, oferece uma solução robusta para lidar com grandes volumes de dados.

No entanto, dominar suas complexidades não é uma tarefa simples. Continue lendo para entender melhor a mecânica do Spark, os desafios únicos de processamento de dados que o Nubank enfrenta e as estratégias que empregamos para superar essas complexidades.

Uma breve introdução ao Spark

O Spark é essencialmente um framework de computação distribuída, executando códigos em paralelo em várias máquinas. Desenvolvido principalmente para o processamento de Big Data, ele se destaca por ser totalmente open source.

Como o Spark funciona? 

A arquitetura do Spark é composta por vários componentes, entre os quais os seguintes são fundamentais:

  • Cluster: Um grupo de JVMs (Java Virtual Machines).
  • Driver: Responsável por interpretar o programa (consulta) do usuário, analisar, distribuir e agendar as tarefas entre os executores. Pense no driver como o cérebro das operações.
  • Executor:  São os que fazem o trabalho pesado. Eles processam segmentos específicos de dados, conhecidos como partições, executando o código designado pelo driver.

Conheça nossas oportunidades

Desafios de processamento de dados do Nubank com Spark

Com um portfólio de produtos em expansão e um crescimento rápido, o Nubank identificou a necessidade de uma melhor orquestração de comunicação para evitar que os clientes recebessem mensagens simultâneas sobre diferentes produtos.

Estabelecendo o cenário: Orquestrador de comunicação

Para resolver isso, foi criada uma infraestrutura dentro do nosso processo de ETL (Extract, Transform, Load). Essa infraestrutura era composta por módulos como:

  • Módulo de produto: Permite que os usuários adicionem novos produtos sobre os quais desejam comunicar.
  • Módulo de segmento de clientes: Facilita a comunicação com base em vários critérios, como idade ou renda.
  • Módulo de campanha: Especifica o tipo de comunicação, seja um e-mail ou uma notificação.

Os usuários adicionavam novos segmentos de clientes e campanhas a essa estrutura. Esses detalhes das campanhas eram então armazenados em conjuntos de dados. Para determinar qual segmento de cliente receberia qual campanha, era realizada uma operação de “join” entre segmentos e campanhas. Essa combinação foi chamada de “pipeline”.

The problem at hand

O problema em questão Inicialmente, os usuários adicionavam apenas alguns segmentos e campanhas para o mesmo produto. No entanto, em certas ocasiões, o número de segmentos e campanhas adicionados aumentou drasticamente, resultando em um aumento de dez vezes nos conjuntos de dados de campanhas. Isso foi acompanhado por um aumento significativo na criação de segmentos, que não eram persistidos nos conjuntos de dados.

Esse aumento inesperado levou ao aparecimento de mensagens de erro. No Databricks, uma popular ferramenta de análise baseada em Spark, o erro “driver stopped unexpectedly” começou a aparecer. No lado do ETL, foi sinalizado um erro de “timeout exception”.

Principais suspeitos: broadcast join ou falta de memória no driver

Duas suspeitas principais surgiram:

  1. Broadcast join: O Spark escolhe o tipo de operação join com base no tamanho dos dados. Quando há uma disparidade significativa entre os tamanhos dos conjuntos de dados, ele pode optar por um “broadcast join”. Se o Spark escolher esse tipo de join equivocadamente, pode exceder o tempo limite de broadcast pré-definido, causando o erro.
  2. Falta de memória no driver:  Isso pode ocorrer quando uma ação em uma consulta envia ao driver resultados que são grandes demais para sua memória, levando ao esgotamento da memória.

Utilizando o SparkUI no Databricks, foi possível discernir que os joins feitos entre segmentos e campanhas não eram do tipo broadcast, mas sim do tipo “sort merge”. Essa observação eliminou a hipótese do broadcast join.

Investigações adicionais mostraram que o processo parava durante a união de várias tabelas. Curiosamente, quando a consulta foi transferida para um cluster geral com mais memória, ela rodou sem problemas, indicando um problema de memória.

Ao combinar uma grande quantidade de conjuntos de dados, há o risco de sobrecarregar o driver. Para mitigar isso preventivamente, instituímos testes para garantir que os usuários não adicionem mais do que um certo número de segmentos – especificamente, 40. Esse número serve como um buffer antes que o sistema entre em colapso.

Fluxo de dados

No centro dos nossos desafios estão os enormes fluxos de dados provenientes dos eventos do aplicativo. Com mais de 59 milhões de usuários, registramos:

  • Mais de 1 bilhão de eventos disparados diariamente.
  • Um total de mais de 100 terabytes de dados de eventos que necessitam de processamento diário.

Alguns dos principais desafios relacionados aos eventos do aplicativo são:

  • Desduplicação de eventos: Frequentemente, cliques são registrados várias vezes e precisam ser desduplicados dentro do Spark.
  • Processamento diário de dados: Garantir que os dados mais recentes estejam sempre disponíveis para nossos analistas.
  • Exploração de dados: A vastidão dos dados torna a exploração complexa em nossas plataformas..

As soluções para esses problemas incluem:

  • Desduplicação: Ao garantir que os eventos sejam produzidos e entregues apenas uma vez (tanto dentro do aplicativo quanto no nível do Message Broker), a responsabilidade do Spark pela desduplicação é minimizada.
  • Processamento incremental: Em vez de processar todo o banco de dados diariamente, processar os dados de forma incremental – um dia de cada vez.
  • Filtragem de dados: Oferecer conjuntos de dados filtrados aos analistas pode agilizar o processamento de dados em plataformas como Databricks ou BigQuery.

Lidando com conjuntos de dados menores 

Por outro lado, também precisamos analisar conjuntos de dados menores, como pesquisas de satisfação dos clientes. O desafio aqui é diferente. Quando lidamos com tamanhos de dados modestos, como 20 MB, o uso do Spark pode parecer exagerado. No entanto, a centralização dos dados para uma análise abrangente na empresa justifica essa abordagem.

Desafios com CSVs:

  1. Garantia de esquema: Assegurar que a estrutura do CSV permaneça consistente.
  2. Rastreamento de dados e versionamento:  Manter o controle das versões dos dados, especialmente quando recebidos de forma ad hoc.
  3. Sobrecarga do Spark: A sobrecarga de usar o Spark para conjuntos de dados menores.

As soluções incluem:

  • Garantia de esquema em JSON: Se um CSV não estiver conforme o esquema garantido, não será processado.
  • Versionamento automático: Todas as versões dos dados são armazenadas automaticamente no Data Lake. Além dos dados, os esquemas também são versionados.

Explorando mais a fundo: desafios específicos do Spark

Ao lidar com tamanhos variados de dados, o Spark traz suas próprias complicações:

  1. Spill: Os dados são movidos para o disco se não couberem na RAM. Se a memória do disco acabar, o processamento pode ser interrompido.
  2. Skew: Isso ocorre quando uma partição de dados é significativamente maior que as outras. Em casos extremos, enquanto todas as outras partições já concluíram, uma ainda permanece processando.
  3. Shuffle: A movimentação de dados entre partições, especialmente durante operações como join ou groupby, pode ser demorada.

Para resolver esses problemas, utilizamos:

  • Otimização de parâmetros no Spark: As configurações padrão podem não ser suficientes, então é essencial otimizar com base na origem dos dados.
  • Partições de shuffle e maxPartitionBytes: Esses parâmetros podem ser ajustados para gerenciar melhor as partições de dados.

Adotando o Spark 3

A migração recente para o Spark 3 trouxe uma infinidade de parâmetros para otimização. Funcionalidades como o AQE ajudam a lidar automaticamente com joins desbalanceados. Há a capacidade inerente de alternar de sort-merge join para broadcast join durante a execução. O Spark 3 também automatiza a configuração das partições, garantindo tamanhos de partição ideais durante o processamento.

A jornada do Nubank no processamento de enormes volumes de dados usando Spark oferece um olhar esclarecedor sobre os desafios práticos e possíveis soluções para empresas que navegam na era do Big Data. Isso destaca a necessidade de agilidade, visão de futuro e uma abordagem proativa na gestão e otimização dos fluxos de trabalho de processamento de dados.

Com o Spark em constante evolução, como é evidente na transição para o Spark 3, as organizações podem antecipar ferramentas e recursos ainda mais aprimorados. Aprendendo com pioneiros como o Nubank, as empresas podem se posicionar melhor para aproveitar os benefícios do Spark e elevar suas estratégias de dados a novos patamares.

Confira o que compartilhamos sobre esse tema no Meetup a seguir:

Conheça nossas oportunidades