A otimização no Apache Spark é o processo de ajustar a distribuição de dados e o uso de memória para reduzir o tempo de execução e os custos operacionais de pipelines de dados. Para alcançar a máxima eficiência, nós precisamos garantir que as tarefas sejam processadas em paralelo de forma equilibrada, evitando a movimentação excessiva de informações pela rede do cluster.
O Apache Spark funciona como uma plataforma de processamento distribuído baseada em in-memory computing. Diferente de sistemas tradicionais, a maioria das transformações ocorre diretamente na memória dos nós, o que acelera a análise de grandes volumes de dados. No entanto, o desempenho pode ser afetado por fatores como o uso ineficiente de memória, má distribuição de tarefas e recursos inadequados para a carga de trabalho.
Entenda o processamento distribuído e em memória
A eficiência do Spark vem da sua capacidade de manter os dados na memória (RAM) durante o processamento. Quando os limites de memória são atingidos, o sistema utiliza o disco, o que torna a operação mais lenta.
-
In-memory computing: As operações acontecem nos nós do cluster para evitar o I/O de disco.
-
Distribuição de tarefas: O trabalho é dividido em partes menores para ser executado simultaneamente.
-
Pontos de atenção: O desempenho é prejudicado quando há movimentação excessiva de dados (shuffle) ou quando os recursos de CPU não são suficientes para o volume processado.
Arquitetura do Spark no Databricks
Ao configurar um cluster no Databricks, nós definimos a estrutura que executará o código. Essa arquitetura é dividida em dois papéis principais:
-
Driver: É o coordenador central. Ele cria o plano de execução, gerencia as informações do estado do job e distribui as tarefas para os trabalhadores.
-
Workers: São os nós que realizam o processamento. Cada worker roda uma JVM (Java Virtual Machine).
-
Executors: Processos dentro dos workers que executam as tarefas e armazenam dados em cache.
-
Slots: Representam o número de núcleos de CPU disponíveis para execução paralela dentro de um executor.
O paralelismo total do cluster é determinado pela soma de todos os slots disponíveis nos workers. Se houver poucos slots para muitos dados, o processamento será sequencial e lento.
Hierarquia de execução: jobs, stages e tasks
Para otimizar um pipeline, você deve entender como o Spark organiza o trabalho internamente. Existe uma hierarquia clara de execução:
-
Job: É iniciado sempre que uma ação é disparada (como write, count ou display).
-
Stage: O job é dividido em estágios. A divisão entre um stage e outro ocorre sempre que há necessidade de redistribuir dados (shuffle).
-
Task: É a menor unidade de trabalho, executada em um único slot de CPU sobre uma partição de dados.
O shuffle é uma operação custosa, pois consome rede, CPU e memória para mover dados entre diferentes nós. Reduzir o número de shuffles é uma das melhores formas de economizar recursos.
RDDs e partições de dados
Os RDDs (Resilient Distributed Datasets) são a base do Spark. Eles possuem três características fundamentais:
-
Imutabilidade: Uma vez criados, não podem ser alterados, apenas transformados em novos RDDs.
-
Distribuição: Os dados são divididos em partições lógicas processadas em paralelo.
-
Tolerância a falhas: O Spark consegue recalcular partições perdidas caso um nó falhe.
É importante diferenciar as partições de execução (Spark partitions) das partições de armazenamento (Hive partitions). Enquanto as de execução definem o paralelismo em memória, as de armazenamento organizam como os arquivos são salvos fisicamente no data lake.
O conceito de lazy evaluation
No Spark, o código não é executado linha por linha assim que é escrito. Ele utiliza a_ lazy evaluation_ (avaliação preguiçosa), dividindo as operações em dois tipos:
-
Transformations: Definem o que fazer com os dados (como filter, select e join). Elas apenas criam um plano de execução.
-
Actions: Comandos que forçam a execução do plano (como write, show ou count).
Inserir ações desnecessárias como o display() entre transformações pode causar reprocessamentos e aumentar o tempo de execução.
Diferença entre transformações narrow e wide
-
**Narrow transformations: **Uma partição de entrada gera apenas uma de saída (ex: filter, select). Não exigem shuffle e são muito eficientes.
-
Wide transformations: Uma partição de entrada pode contribuir para várias de saída (ex: join, groupBy, distinct). Exigem shuffle, o que aumenta o custo e o tempo.
Monitoramento com a Spark UI
A Spark UI é a principal ferramenta de observabilidade no Databricks. Ela permite identificar onde estão as limitações de performance do seu código.
-
Aba Jobs: Mostra o progresso geral e o tempo total de execução.
-
Aba Stages: Detalha cada estágio, permitindo ver a duração das tarefas e métricas de shuffle.
-
Aba Executors: Exibe o uso de memória e CPU por nó, ajudando a identificar se o cluster está bem dimensionado.
-
Métricas de Task: Permite observar se as tarefas estão desbalanceadas (algumas muito lentas e outras rápidas).
Otimização de leitura com Data Skipping e Z-Order
Um dos maiores custos em Spark é a leitura de arquivos. O objetivo deve ser ler apenas o necessário utilizando o Data Skipping.
-
Data Skipping: O Spark e o Delta Lake utilizam estatísticas (mínimo e máximo) para ignorar arquivos que não atendem aos filtros da consulta.
-
OPTIMIZE: Compacta arquivos pequenos em arquivos maiores para melhorar a eficiência da leitura.
-
Z-Order: Reorganiza os dados fisicamente para agrupar informações relacionadas, o que potencializa drasticamente o Data Skipping.
Em projetos da BIX Tecnologia, a aplicação de Z-ORDER reduziu a leitura de centenas de arquivos para apenas um, baixando o tempo de consulta de 14 para 4 segundos. Para manter a performance, use o comando VACUUM periodicamente para remover arquivos antigos e reduzir custos de armazenamento.
Gerenciamento de spill e data skew
Problemas de memória são comuns em processamentos de grande escala. Os dois principais são o spill e o skew:
-
Spill: Ocorre quando os dados não cabem na memória e o Spark precisa gravá-los temporariamente no disco. Isso aumenta muito o tempo de processamento.
-
Data Skew (Enviesamento): Acontece quando os dados estão mal distribuídos e uma única tarefa fica muito maior que as outras, travando a conclusão do stage.
Para mitigar esses problemas, nós recomendamos ajustar o número de spark.sql.shuffle.partitions, usar o AQE (Adaptive Query Execution) para otimizar joins automaticamente ou aplicar técnicas de salting para redistribuir chaves problemáticas.
Cache e persistência de dados
O uso de cache ajuda a evitar o reprocessamento de transformações custosas que são utilizadas várias vezes no mesmo pipeline.
-
Spark Cache: Armazena DataFrames na memória usando cache() ou persist(). Lembre-se que o cache é lazy e só é preenchido após a primeira ação.
-
Databricks Disk Cache: Utiliza o SSD dos nós para armazenar dados lidos do storage automaticamente. É altamente recomendado para clusters que realizam leituras repetitivas.
Se sua empresa enfrenta pipelines lentos no Databricks, problemas de spill ou custos elevados de processamento, nossos especialistas podem ajudar a otimizar sua arquitetura Spark. Fale com a nossa equipe e garanta a eficiência dos seus dados. ⬇️
TL; DR Perguntas frequentes sobre otimização no Apache Spark
O que é o shuffle no Spark? É a redistribuição de dados entre os nós do cluster. Ocorre em operações como joins e agrupamentos, sendo uma das etapas mais lentas do processo.
Como a Spark UI ajuda a identificar problemas? Ela mostra visualmente se as tarefas estão equilibradas, se há excesso de uso de disco (spill) e quais estágios estão consumindo mais tempo.
Qual a diferença entre OPTIMIZE e Z-Order? O OPTIMIZE agrupa arquivos pequenos para reduzir a sobrecarga de leitura, enquanto o Z-Order organiza os dados dentro desses arquivos para acelerar filtros.
O que é o Adaptive Query Execution (AQE)? É uma funcionalidade do Spark 3 que ajusta o plano de execução em tempo real com base em estatísticas colhidas durante a rodada, melhorando a performance de joins.
**Quando devo aumentar a memória do cluster? ** Quando as otimizações de código e o ajuste de partições não eliminarem o spill para disco, indicando que a carga de trabalho exige mais hardware.









