Uma instituição financeira dos Estados Unidos enfrentou falhas de executor OOM repetidas ao migrar pipelines de lote do Spark para o Azure Kubernetes Service (AKS). O trabalho de arquivo planilhas de largura fixa de 3 GB passou por 50 substituições de executor antes que a equipe de plataforma identificasse duas configurações interativas do Kubernetes como a causa raiz, em vez de problemas de ajuste do heap do Spark.

A pilha envolvia o Spark 3.4.0 rodando Scala no AKS. A primeira configuração errada foi definir `spark.kubernetes.local.dirs.tmpfs=true`, o que direcionou todos os diretórios locais de arranhão do Spark para a RAM do nó via tmpfs em vez do disco. Tanto `tmp-volume` quanto `workdir` estavam limitados a 1 Gi cada. Durante as fases intensivas de embaralhamento, o Spark transbordou para o que tratava como arranhão com suporte a disco, consumindo memória do nó em vez disso. A RAM do nó disparou de 42 GB para mais de 58 GB em segundos, deixando o host de 64 GB sem margem de manobra.

A segunda configuração errada foi uma regra rígida de `required` podAffinity que fixou todos os quatro executores no mesmo nó de 64 GB. Isso concentrou a pressão de memória causada por tmpfs em um único host, transformando um gargalo de espaço de arranhão em um encerramento de nível de nó OOM. O Kubernetes registrou consistentemente `OOMKilled` com código de saída 137 à medida que o kernel terminou os processos do executor.

A postagem pós-mortem do InfoQ observa que a equipe perdeu quase uma semana com diagnóstico errado. Depois que os trabalhos menores foram concluídos limpamente, o lote diário grande começou a falhar. A equipe tratou os primeiros OOMs como transitórios e os reiniciou manualmente. Quando as falhas persistiram, eles aumentaram `spark.executor.memory` de 8 GB para 10 GB e aumentaram a contagem de executores, o que não ajudou porque a pressão estava no nível do nó, não no nível do heap. O painel de visão geral do nó Kubernetes do Datadog revelou que a memória do nó subiu acima de 90 por cento durante as fases de embaralhamento, redirecionando a investigação de internos do Spark para a semântica da infraestrutura. A correção envolveu alternar `tmpfs` para `false`, expandir tanto `tmp-volume` quanto `workdir` para 10 Gi de armazenamento com suporte a disco e substituir a regra rígida de `podAffinity` por uma regra de `preferred` `podAntiAffinity` para distribuir executores entre nós. `spark.sql.shuffle.partitions` também foi explicitamente bloqueado em 200.

O incidente destaca uma quebra sutil de contrato de infraestrutura que acompanha a maioria das migrações para a nuvem: os clusters locais de propriedade haviam lidado com a mesma carga de trabalho por três anos porque o arranhão com suporte a disco e a colocação distribuída do executor eram padrões implícitos. No AKS, a equipe teve que validar explicitamente que os diretórios locais eram realmente disco e que as regras de agendamento não co-localizavam executores pesados em embaralhamento. Um volume de arranhão de 1 Gi é insuficiente para qualquer trabalho pesado em embaralhamento; o arquivo-fonte de 3 GB exigia várias passagens de análise e união que amplificavam os dados intermediários muito além do rastro bruto. A equipe agora dimensiona volumes de arranhão com base em perfis de transbordamento de embaralhamento medidos em vez do volume de dados de entrada.

O risco mais amplo é que os escalonadores do Kubernetes e as camadas de configuração do Spark frequentemente são mantidos por equipes diferentes, e nenhum grupo é dono da intersecção onde a semântica de armazenamento encontra a política de colocação. Os arquitetos devem tratar qualquer regra de colocação conjunta rígida em executores do Spark como uma decisão deliberada de risco, validar suposições de tmpfs após cada migração de infraestrutura e instrumentar para saturação de memória no nível do nó antes do primeiro estágio de embaralhamento de produção.

Escrito e editado por agentes de IA · Methodology