DEV Community

Francisco Jaime da Silva
Francisco Jaime da Silva

Posted on

Otimizando Escrita e Performance em Ambientes com Delta Lake, MinIO e Spark

Quando lidamos com grandes volumes de dados em ambientes de Lakehouse, a eficiência na escrita e leitura faz toda a diferença. Usar o Delta Lake em conjunto com o Spark e armazenamentos como o MinIO abre possibilidades poderosas, mas também traz desafios: fragmentação de arquivos, lentidão em consultas e joins custosos. Neste artigo, compartilho estratégias práticas para lidar com esses problemas no dia a dia.

1. Otimizando escrita no Delta Lake com OPTIMIZE e Z-ORDER
Ao longo do tempo, tabelas Delta tendem a acumular muitos arquivos pequenos, o que degrada a performance das consultas. Para mitigar isso, o Delta Lake oferece o comando OPTIMIZE, que faz a compactação de arquivos em unidades maiores e mais eficientes para leitura.

OPTIMIZE:
Consolida vários arquivos pequenos em arquivos de tamanho ideal (normalmente entre 256MB e 1GB). Isso reduz o overhead de leitura e melhora a performance de scans completos.

Z-ORDER:
Quando combinado ao optimize, o Z-ORDER organiza fisicamente os dados em disco de acordo com colunas de alto filtro nas consultas. Exemplo típico:

*OPTIMIZE **ecommerce.transacoes **ZORDER BY *(cliente_id, data_compra);

Isso melhora bastante consultas seletivas, já que os dados relacionados ficam próximos fisicamente, reduzindo I/O desnecessário.

Boas práticas:
Usar Z-ORDER em colunas que mais aparecem nos filtros (WHERE, JOIN, GROUP BY).

Automatizar a execução de OPTIMIZE em janelas de baixa carga, já que o processo consome recursos.

  1. Reduzindo o problema de arquivos pequenos no MinIO Ao escrever dados no MinIO (ou S3), o Spark pode gerar muitos arquivos pequenos devido ao paralelismo natural da execução. Isso não apenas ocupa espaço, mas também prejudica a performance de leitura quando usamo o Delta Lake.

Estratégias para lidar com o problema:

Reparticionamento antes da escrita: Ajuste o número de partições antes do write para controlar quantos arquivos serão gerados:

df.repartition(50).write.format(“delta”).mode(“append”).save(caminho)

Aqui, 50 partições significa que a saída terá ~50 arquivos.

Coalesce para dados menores: Quando o volume for reduzido após um filtro, use coalesce() para gerar menos arquivos.

df.coalesce(1).write.format(“delta”).mode(“overwrite”).save(caminho)

Auto Optimize e Auto Compact (quando disponíveis): No Databricks, recursos automáticos podem gerenciar esse problema, mas em ambientes com MinIO puro, vale mais a pena automatizar um job de OPTIMIZE periódico.

Compactação pós-escrita:
Criar um job dedicado apenas para consolidar os arquivos pequenos, usando OPTIMIZE ou mesmo um processo customizado de merge.

3. Particionamento e paralelismo no Spark para joins grandes
Outra dor comum em grandes volumes são os joins custosos, que muitas vezes resultam em shuffles pesados. Existem boas práticas para aliviar esse cenário:

Particionamento estratégico:
Particionar tabelas do Delta Lake por colunas usadas com frequência em filtros e joins. Exemplo:

df.write.format(“delta”).partitionBy(“ano”, “mes”).save(caminho)

Isso ajuda o Spark a ler apenas os arquivos relevantes.

Broadcast join: Se uma das tabelas for pequena o suficiente, é muito mais eficiente transmitir a tabela inteira para os executores:

df_grande.join(broadcast(df_pequeno), “id”)

Configuração de paralelismo:
Ajustar o número de partições para o tamanho real do cluster:

spark.config(“spark.sql.shuffle.partitions”, 400)

Valores muito baixos causam gargalo.

Valores muito altos geram overhead de arquivos pequenos. A ideia é sempre calibrar com base no cluster e no volume processado.

Skew join handling: Em dados desbalanceados (ex.: muitos registros para um mesmo valor de chave), é comum que alguns executores fiquem sobrecarregados. O Spark possui configurações como:

spark.conf.set(“spark.sql.adaptive.skewJoin.enabled”, “true”)

Isso divide dinamicamente partições muito grandes, melhorando a distribuição de carga.

Conclusão
O trio Delta Lake + Spark + MinIO é extremamente poderoso, mas para alcançar alta performance é essencial cuidar da forma como os dados são gravados e lidos.

Use OPTIMIZE e Z-ORDER para melhorar consultas seletivas.

Lute contra arquivos pequenos ajustando particionamento e aplicando compactações periódicas.

Planeje particionamento e paralelismo para reduzir o custo de joins e operações de shuffle.

Com essas práticas aplicadas de forma contínua, a diferença de performance é notável: menos tempo de processamento, custos menores e consultas mais ágeis para os consumidores de dados.

Este artigo é um trecho adaptado do livro

Guia prático para construir um Data Lakehouse open source usando Minio , Delta Lake e Spark — Do Conceito à Prática com Ferramentas Open Source
Disponível em breve na Amazon.

Top comments (0)