Os problemas de desempenho mais comuns ao usar o Spark — Parte 2.
No meu último artigo, eu escrevi um pouco sobre os principais problemas de desempenho ao usar o Spark, bom esse artigo aqui será uma continuação do primeiro, nessa parte 2, vamos tratar sobre o que podemos fazer para evitar ou diminuir esses problemas.
Spill
O que podemos fazer para mitigar o Spill ?
- A maneira mais fácil e cara é alocar um cluster com mais memória por worker.
- Em caso de Spill causado por desvio, trate o desvio e o Spill será resolvido.
- Diminuir o tamanho de cada partição aumentando o número de partições, e como podemos fazer isso?
=> Gerenciando spark.sql.shuffle.partitions.
=> Ao repartir explicitamente.
=> Gerenciando spark.sql.files.maxPartitionBytes.
Para saber mais sobre esse
Skew
O que podemos fazer para mitigar o Skew?
- O primeiro problema que precisamos resolver é a distribuição desigual de registros em todas as partições, o que resulta em tarefas mais lentas.
- Mitigar o problema de RAM que causa erros de spill ou falta de memória pode apenas corrigir os sintomas, mas não a causa raiz.
Estratégias para corrigir o skew:
- Ative a execução da Adaptive Query se estiver usando o Spark 3, que equilibrará as partições para os nós automaticamente, o que é um recurso muito bom do Spark 3.
- Empregar uma skew-hint. Com as informações dessas hints, o Spark pode construir um plano de consulta melhor, que não sofra de distorção de dados.
- Salt a coluna distorcida com um número aleatório, criando uma melhor distribuição em cada partição com o custo de processamento extra.
Aqui você pode ler mais sobre o assunto: https://www.databricks.com/blog/2020/05/29/adaptive-query-execution-speeding-up-spark-sql-at-runtime.html
Shuffle
O que podemos fazer para atenuar os problemas de desempenho causados pelo Shuffle?
- Nossa estratégia número um é reduzir o I/O de rede usando menos e mais workers. Quanto maior a máquina e quanto menos máquinas tivermos, menos dados teremos para mover entre essas máquinas. Ainda incorreremos no I/O do disco, mas com menos máquinas, reduziremos significativamente o I/O da rede.
- A próxima estratégia é reduzir a quantidade de dados sendo embaralhados como um todo. Algumas das coisas que podemos fazer são: livrar-se das colunas que você não precisa, filtrar registros desnecessários, otimizar a ingestão de dados.
- Desnormalize os conjuntos de dados especificamente se o shuffle for causado por uma join.
- Se você estiver unindo tabelas, poderá empregar um BroadcastHashJoin, caso em que a menor das duas tabelas será redistribuída para os executores para evitar totalmente a operação de shuffle. Isso é controlado pela propriedade spark.sql.autoBroadcastJoinThreshold (a configuração padrão é 10 MB). Se a menor das duas tabelas atingir o limite de 10 MB, poderemos transmiti-la.
- Para joins, embaralhe previamente os dados com um conjunto de dados agrupado. O objetivo é eliminar a troca e classificação pré-embaralhando os dados. Os dados são agregados em N baldes e opcionalmente classificados e o resultado é salvo em uma tabela e disponível para leituras subsequentes.
Aqui voce pode ler mais sobre o assunto:
Storage
- Evitar manipulação de arquivos de partição com menos de 128 MB.
- Ao varrer diretórios, podemos ter uma longa lista de arquivos em um único diretório ou, no caso de conjuntos de dados altamente particionados, pastas de vários níveis. Para reduzir a quantidade de leitura, podemos registra o DataFrame como uma tabela.
- Dependendo do formato de arquivo usado, pode haver diferentes problemas de esquema. Por exemplo, usando JSON e CSV, todos os dados precisam ser lidos para inferir tipos de dados. Para o Parquet, apenas uma única leitura de arquivo é necessária, mas toda a lista de arquivos do Parquet precisa ser lida se precisarmos lidar com possíveis alterações de esquema ao longo do tempo. Para melhorar o desempenho, pode ser útil fornecer definições de esquema com antecedência.
Serialization
- Problemas de serialização podem surgir ao integrar bases de código com sistemas legados (por exemplo, Hadoop), bibliotecas de terceiros e estruturas personalizadas. Uma abordagem importante que podemos adotar para reduzir os problemas de serialização é evitar o uso de UDFs ou UDFs vetorizados (que agem como uma caixa preta para o Catalyst Optimizer).