<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: renanpyd</title>
    <description>The latest articles on DEV Community by renanpyd (@renanpyd).</description>
    <link>https://dev.to/renanpyd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F687401%2F52339473-a6e4-4292-b2f6-f7286506a222.jpeg</url>
      <title>DEV Community: renanpyd</title>
      <link>https://dev.to/renanpyd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/renanpyd"/>
    <language>en</language>
    <item>
      <title>Como aprendi Apache Spark — Parte 7: Memória — Executors, Driver, Cache, Spill e OutOfMemory</title>
      <dc:creator>renanpyd</dc:creator>
      <pubDate>Sat, 24 Oct 2026 16:10:33 +0000</pubDate>
      <link>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-7-memoria-executors-driver-cache-spill-e-outofmemory-3e93</link>
      <guid>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-7-memoria-executors-driver-cache-spill-e-outofmemory-3e93</guid>
      <description>&lt;p&gt;Nos artigos anteriores construímos uma visão cada vez mais completa do Apache Spark.&lt;/p&gt;

&lt;p&gt;Passamos por:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data
 ↓
Partitions
 ↓
Tasks
 ↓
Executors
 ↓
Shuffle
 ↓
Joins
 ↓
Spark SQL
 ↓
Catalyst
 ↓
Physical Plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas existe um recurso atravessando praticamente todas essas etapas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;memória.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Quando comecei a estudar problemas de memória no Spark, era tentador resumir tudo a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OutOfMemory
     ↓
aumentar RAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hoje eu vejo esse raciocínio como incompleto.&lt;/p&gt;

&lt;p&gt;Um problema de memória pode estar relacionado a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Driver
Executors
Partitions
Shuffle
Broadcast
Cache
Serialization
Garbage Collection
Python workers
Off-heap/native memory
Memory Overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E aumentar memória sem descobrir qual desses componentes está causando o problema pode apenas tornar um Job caro antes de ele falhar novamente.&lt;/p&gt;

&lt;p&gt;A pergunta correta não é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quanto de memória devo adicionar?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quem está consumindo memória, por quê, e em qual parte da arquitetura?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Memória em uma aplicação Spark
&lt;/h2&gt;

&lt;p&gt;Vamos voltar à arquitetura básica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 DRIVER
                    │
                    ▼
             Cluster Manager
                    │
          ┌─────────┼─────────┐
          ▼         ▼         ▼
      Executor 1 Executor 2 Executor 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos dois contextos que precisam ser diferenciados imediatamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Driver Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa distinção é fundamental.&lt;/p&gt;

&lt;p&gt;Porque:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Driver OOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor OOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;podem ter causas completamente diferentes.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Driver Memory
&lt;/h2&gt;

&lt;p&gt;O Driver coordena a aplicação.&lt;/p&gt;

&lt;p&gt;Ele mantém informações relacionadas a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aplicação;&lt;/li&gt;
&lt;li&gt;Jobs;&lt;/li&gt;
&lt;li&gt;Stages;&lt;/li&gt;
&lt;li&gt;Tasks;&lt;/li&gt;
&lt;li&gt;scheduling;&lt;/li&gt;
&lt;li&gt;metadados;&lt;/li&gt;
&lt;li&gt;planos de execução;&lt;/li&gt;
&lt;li&gt;resultados retornados ao Driver;&lt;/li&gt;
&lt;li&gt;estado necessário para coordenar a computação.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  DRIVER
                     │
      ┌──────────────┼──────────────┐
      ▼              ▼              ▼
   Planning       Scheduling     Metadata
      │              │              │
      └──────────────┼──────────────┘
                     ▼
                  Control
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso significa que o Driver não deveria ser tratado como destino para todo o dataset distribuído.&lt;/p&gt;

&lt;p&gt;Esse ponto parece óbvio.&lt;/p&gt;

&lt;p&gt;Até alguém executar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. O perigo do &lt;code&gt;collect()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O processamento acontece de maneira distribuída e o resultado retornado é pequeno:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;123456789
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora compare com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;collect()&lt;/code&gt; solicita que todos os registros do DataFrame sejam retornados ao Driver.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor 1 ──────┐
Executor 2 ──────┤
Executor 3 ──────┼──► DRIVER
Executor 4 ──────┤
Executor 5 ──────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o dataset possuir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;500 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e o Driver tiver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;temos um problema bastante previsível.&lt;/p&gt;

&lt;p&gt;O Spark é distribuído.&lt;/p&gt;

&lt;p&gt;Mas acabamos de pedir:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Traga tudo para uma única máquina.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. &lt;code&gt;collect()&lt;/code&gt; não é ruim — o contexto é que importa
&lt;/h2&gt;

&lt;p&gt;Não existe problema em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;quando sabemos que o resultado será pequeno.&lt;/p&gt;

&lt;p&gt;Também existem cenários legítimos em que precisamos trazer pequenas quantidades de dados ao Driver.&lt;/p&gt;

&lt;p&gt;O problema é usar &lt;code&gt;collect()&lt;/code&gt; sem compreender:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Distributed Dataset
        ↓
      Driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pergunta antes de um &lt;code&gt;collect()&lt;/code&gt; deveria ser:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Qual o volume máximo que pode voltar para o Driver?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa pergunta evita muitos problemas.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Outros caminhos para sobrecarregar o Driver
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;collect()&lt;/code&gt; é o exemplo mais conhecido, mas não é o único.&lt;/p&gt;

&lt;p&gt;Problemas de Driver também podem aparecer com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resultados excessivamente grandes;&lt;/li&gt;
&lt;li&gt;estruturas locais gigantes;&lt;/li&gt;
&lt;li&gt;excesso de metadados;&lt;/li&gt;
&lt;li&gt;quantidade extrema de Tasks;&lt;/li&gt;
&lt;li&gt;operações que materializam dados no processo do Driver;&lt;/li&gt;
&lt;li&gt;código Python executado localmente;&lt;/li&gt;
&lt;li&gt;determinadas operações de visualização ou conversão.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toPandas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;merece atenção.&lt;/p&gt;

&lt;p&gt;O resultado precisa caber na memória do processo que recebe esse DataFrame local.&lt;/p&gt;

&lt;p&gt;Ou seja:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark DataFrame distribuído
          ↓
       toPandas()
          ↓
DataFrame local
          ↓
      DRIVER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se os dados forem grandes, temos novamente uma concentração de memória.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Executor Memory
&lt;/h2&gt;

&lt;p&gt;Agora vamos para os Executors.&lt;/p&gt;

&lt;p&gt;Eles executam as Tasks.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor
   │
   ├── Task 1
   ├── Task 2
   ├── Task 3
   └── Task 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Durante a execução, memória pode ser necessária para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;processamento;&lt;/li&gt;
&lt;li&gt;agregações;&lt;/li&gt;
&lt;li&gt;joins;&lt;/li&gt;
&lt;li&gt;sorting;&lt;/li&gt;
&lt;li&gt;shuffle;&lt;/li&gt;
&lt;li&gt;estruturas intermediárias;&lt;/li&gt;
&lt;li&gt;cache;&lt;/li&gt;
&lt;li&gt;dados serializados;&lt;/li&gt;
&lt;li&gt;execução de código;&lt;/li&gt;
&lt;li&gt;componentes fora do heap JVM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É aqui que começam vários problemas clássicos.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Uma partição vira trabalho para uma Task
&lt;/h2&gt;

&lt;p&gt;Da Parte 3:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Em uma determinada Stage, cada partição normalmente corresponde ao trabalho de uma Task.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Então existe uma conexão direta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition
    ↓
Task
    ↓
Memory pressure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition 1 → 200 MB
Partition 2 → 180 MB
Partition 3 → 220 MB
Partition 4 → 18 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Task responsável pela quarta partição terá um perfil completamente diferente das demais.&lt;/p&gt;

&lt;p&gt;Podemos observar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1 → OK
Task 2 → OK
Task 3 → OK
Task 4 → Memory Pressure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Talvez o problema não seja:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Executor com pouca memória.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Talvez seja:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Uma partição absurdamente maior que as outras.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  8. Particionamento e memória são inseparáveis
&lt;/h2&gt;

&lt;p&gt;Imagine um dataset de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 TB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;distribuído em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simplificando muito:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;≈ 100 GB por partition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1000 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teríamos algo mais próximo de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;≈ 1 GB por partition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso não significa que uma Task precise manter toda a partição simultaneamente em memória.&lt;/p&gt;

&lt;p&gt;Mas o exemplo mostra uma relação importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;O tamanho e a distribuição das partições influenciam diretamente a pressão de memória das Tasks.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É por isso que simplesmente aumentar &lt;code&gt;spark.executor.memory&lt;/code&gt; pode esconder a causa real.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Execution Memory
&lt;/h2&gt;

&lt;p&gt;Durante operações como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark precisa de memória para executar estruturas intermediárias.&lt;/p&gt;

&lt;p&gt;Também em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou em determinados joins.&lt;/p&gt;

&lt;p&gt;Podemos pensar conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor Memory
      │
      ├── Execution
      │      │
      │      ├── Shuffle
      │      ├── Join
      │      ├── Sort
      │      └── Aggregation
      │
      └── Storage
             │
             ├── Cached Data
             └── Persisted Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Spark moderno, execution e storage participam de um modelo de gerenciamento de memória integrado.&lt;/p&gt;

&lt;p&gt;Isso é importante porque a memória disponível não deve ser imaginada como caixas totalmente independentes e rígidas em todos os momentos.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Storage Memory
&lt;/h2&gt;

&lt;p&gt;Agora imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando o DataFrame é materializado, Spark pode manter partições para reutilização.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor
   │
   ├── Execution Memory
   │
   └── Storage Memory
            │
            ├── Partition A
            ├── Partition B
            └── Partition C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso pode ser extremamente útil.&lt;/p&gt;

&lt;p&gt;Se o mesmo dataset caro for utilizado várias vezes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expensive computation
       ↓
     CACHE
       ↓
 ┌─────┼─────┐
 ▼     ▼     ▼
Use 1 Use 2 Use 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;evitamos recalcular repetidamente a mesma linhagem.&lt;/p&gt;

&lt;p&gt;Mas cache também custa memória.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. &lt;code&gt;cache()&lt;/code&gt; não significa "deixe rápido"
&lt;/h2&gt;

&lt;p&gt;Esse é outro anti-pattern comum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ser utilizado como se significasse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;optimize(df)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não significa.&lt;/p&gt;

&lt;p&gt;Imagine um DataFrame de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;800 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;que será utilizado apenas uma vez.&lt;/p&gt;

&lt;p&gt;Fazer cache pode simplesmente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;consumir memória;&lt;/li&gt;
&lt;li&gt;provocar eviction;&lt;/li&gt;
&lt;li&gt;aumentar pressão sobre o Executor;&lt;/li&gt;
&lt;li&gt;competir com execution memory;&lt;/li&gt;
&lt;li&gt;não trazer benefício relevante.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cache faz mais sentido quando existe &lt;strong&gt;reuso&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transformacao_cara&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;resultado_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;resultado_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;produto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resultado_c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ACTIVE&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora existe um motivo claro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expensive Base
     ↓
   CACHE
     ↓
 ┌────┼────┐
 ▼    ▼    ▼
 A    B    C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  12. Cache também é Lazy
&lt;/h2&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso não significa necessariamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Neste instante, todas as partições foram computadas e armazenadas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A materialização ocorre quando uma Action força a computação.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cache()
   ↓
mark for persistence
   ↓
count()
   ↓
compute partitions
   ↓
store according to persistence strategy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lazy evaluation continua presente.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. &lt;code&gt;persist()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Além de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;podemos trabalhar com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e escolher estratégias de persistência adequadas ao contexto.&lt;/p&gt;

&lt;p&gt;Conceitualmente, a persistência pode envolver combinações de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory
Disk
Serialization
Replication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A escolha depende de fatores como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tamanho;&lt;/li&gt;
&lt;li&gt;frequência de reuso;&lt;/li&gt;
&lt;li&gt;custo de recomputação;&lt;/li&gt;
&lt;li&gt;memória disponível;&lt;/li&gt;
&lt;li&gt;tolerância a I/O.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Novamente, não existe uma configuração universalmente melhor.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. E quando a memória não é suficiente?
&lt;/h2&gt;

&lt;p&gt;Agora chegamos a algo que apareceu na Parte 4:&lt;/p&gt;

&lt;h1&gt;
  
  
  Spill
&lt;/h1&gt;

&lt;p&gt;Imagine uma Task realizando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aggregation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou processamento relacionado a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shuffle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ela utiliza estruturas em memória.&lt;/p&gt;

&lt;p&gt;Mas a quantidade necessária ultrapassa o que pode ser mantido eficientemente ali.&lt;/p&gt;

&lt;p&gt;Então parte dos dados pode ser enviada para disco.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task
 ↓
Memory
 ↓
Memory pressure
 ↓
Spill
 ↓
Disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Disk
 ↓
Read
 ↓
Continue processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso permite que determinadas operações continuem mesmo quando tudo não cabe simultaneamente em memória.&lt;/p&gt;

&lt;p&gt;Mas existe um preço.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Spill troca memória por I/O
&lt;/h2&gt;

&lt;p&gt;Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory
  ↓
CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory
  ↓
Disk Write
  ↓
Disk Read
  ↓
CPU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disco normalmente é muito mais lento do que memória.&lt;/p&gt;

&lt;p&gt;Então grandes volumes de spill podem transformar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job lento
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo sem ocorrer um OutOfMemory.&lt;/p&gt;

&lt;p&gt;Essa é uma lição importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Uma aplicação pode estar sofrendo de pressão de memória sem necessariamente falhar por OOM.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ela pode simplesmente ficar muito mais lenta.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Shuffle + Memory + Spill
&lt;/h2&gt;

&lt;p&gt;Agora conectamos vários artigos.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partitions
     ↓
Partial Aggregation
     ↓
Shuffle Write
     ↓
Network
     ↓
Shuffle Read
     ↓
Aggregation
     ↓
Memory pressure
     ↓
Spill
     ↓
Disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora um simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ganha uma interpretação completamente diferente.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Data Skew volta novamente
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 = 200 MB
P2 = 210 MB
P3 = 190 MB
P4 = 15 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Durante uma agregação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 → OK
P2 → OK
P3 → OK
P4 → Spill → Spill → Spill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou pior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P4 → OutOfMemory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A reação inicial pode ser:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Aumentar executor memory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Talvez funcione.&lt;/p&gt;

&lt;p&gt;Mas talvez a pergunta correta seja:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Por que existe uma partição de 15 GB quando as demais possuem aproximadamente 200 MB?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Estamos novamente diante de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data Skew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A causa é distribuição.&lt;/p&gt;

&lt;p&gt;O sintoma aparece na memória.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. OutOfMemory não é um único problema
&lt;/h2&gt;

&lt;p&gt;Quando alguém diz:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spark deu OOM."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ainda sabemos muito pouco.&lt;/p&gt;

&lt;p&gt;Precisamos perguntar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Onde?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Driver OOM?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor OOM?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou pressão fora do heap/container?&lt;/p&gt;

&lt;p&gt;Também precisamos perguntar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Em qual Stage?

Em qual operação?

Em todas as Tasks?

Em uma única Task?

Depois de um Shuffle?

Durante Broadcast?

Durante collect()?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essas perguntas mudam completamente a investigação.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Um Driver OOM pode indicar problema de arquitetura
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sobre um dataset enorme.&lt;/p&gt;

&lt;p&gt;Podemos aumentar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark.driver.memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;16 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;32 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;64 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Talvez o Job finalmente funcione.&lt;/p&gt;

&lt;p&gt;Mas a pergunta continua:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Por que estamos trazendo um dataset distribuído gigantesco para um único processo?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Aumentar memória resolveu o sintoma.&lt;/p&gt;

&lt;p&gt;Talvez não tenha resolvido o design.&lt;/p&gt;




&lt;h2&gt;
  
  
  20. Executor OOM também pode esconder outro problema
&lt;/h2&gt;

&lt;p&gt;Agora imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor OOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A reação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark.executor.memory
8 GB → 16 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas talvez exista:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uma partition = 40 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;join skewed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;broadcast excessivo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou várias Tasks concorrentes disputando recursos.&lt;/p&gt;

&lt;p&gt;Ou consumo significativo fora do heap principal.&lt;/p&gt;

&lt;p&gt;Então:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Executor OOM não significa automaticamente que o Executor precisa apenas de mais heap.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  21. &lt;code&gt;spark.executor.memory&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Uma configuração conhecida é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark.executor.memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ela define a quantidade de memória heap disponibilizada ao processo Executor JVM.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--executor-memory&lt;/span&gt; 8g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas existe uma armadilha conceitual importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A memória total necessária por um Executor não é necessariamente igual ao valor de &lt;code&gt;spark.executor.memory&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Existe memória utilizada fora desse heap.&lt;/p&gt;

&lt;p&gt;E isso nos leva diretamente ao tema do material original que estudei:&lt;/p&gt;

&lt;h1&gt;
  
  
  Memory Overhead
&lt;/h1&gt;




&lt;h2&gt;
  
  
  22. O que é Memory Overhead?
&lt;/h2&gt;

&lt;p&gt;Uma aplicação Spark não utiliza memória apenas para o heap principal da JVM.&lt;/p&gt;

&lt;p&gt;Podemos pensar de forma simplificada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor Container / Process Environment
│
├── JVM Heap
│
│    └── spark.executor.memory
│
└── Additional / Non-Heap Memory
     │
     ├── JVM overhead
     ├── native memory
     ├── Python processes
     ├── direct buffers
     └── other runtime overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É essa segunda região que torna &lt;code&gt;memoryOverhead&lt;/code&gt; tão importante.&lt;/p&gt;

&lt;p&gt;Dependendo do ambiente e do workload, um Executor pode morrer mesmo quando aparentemente existe heap suficiente.&lt;/p&gt;




&lt;h2&gt;
  
  
  23. Um exemplo conceitual
&lt;/h2&gt;

&lt;p&gt;Imagine uma configuração:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor Heap
8 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso não significa necessariamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor total
=
8 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O processo também precisa de espaço para outras necessidades.&lt;/p&gt;

&lt;p&gt;Podemos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Container / Pod / Executor allocation

┌───────────────────────────────┐
│                               │
│ JVM Heap                      │
│ spark.executor.memory         │
│                               │
├───────────────────────────────┤
│                               │
│ Memory Overhead               │
│                               │
│ Native / Python / Direct /    │
│ JVM non-heap / other          │
│                               │
└───────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se a região adicional for insuficiente, o processo pode exceder os limites impostos pelo gerenciador de recursos.&lt;/p&gt;




&lt;h2&gt;
  
  
  24. Por que isso aparece muito com PySpark?
&lt;/h2&gt;

&lt;p&gt;Em PySpark existe uma característica importante.&lt;/p&gt;

&lt;p&gt;Nosso código Python não transforma a JVM inteira em Python.&lt;/p&gt;

&lt;p&gt;Existe interação entre:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JVM
 ↕
Python worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor JVM
     │
     ├── Spark execution
     │
     └── Python worker
             │
             └── Python code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esses processos também consomem memória.&lt;/p&gt;

&lt;p&gt;Dependendo do workload, bibliotecas e operações utilizadas, isso pode se tornar relevante para o consumo total do Executor.&lt;/p&gt;

&lt;p&gt;Portanto, em PySpark precisamos olhar além de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JVM Heap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  25. Memory Overhead no material histórico
&lt;/h2&gt;

&lt;p&gt;Uma das anotações que motivou este capítulo tratava especificamente de problemas de &lt;code&gt;memoryOverhead&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Esse tipo de problema continua conceitualmente relevante.&lt;/p&gt;

&lt;p&gt;O que mudou ao longo dos anos foram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;versões do Spark;&lt;/li&gt;
&lt;li&gt;nomes e comportamento de determinadas configurações;&lt;/li&gt;
&lt;li&gt;resource managers;&lt;/li&gt;
&lt;li&gt;defaults;&lt;/li&gt;
&lt;li&gt;cálculo automático de overhead;&lt;/li&gt;
&lt;li&gt;integração com containers;&lt;/li&gt;
&lt;li&gt;Kubernetes;&lt;/li&gt;
&lt;li&gt;execução Python;&lt;/li&gt;
&lt;li&gt;mecanismos internos de memória.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por isso não considero uma boa prática pegar uma configuração antiga e simplesmente reproduzi-la hoje.&lt;/p&gt;

&lt;p&gt;O conceito continua válido.&lt;/p&gt;

&lt;p&gt;A configuração precisa ser analisada no contexto da versão e do ambiente atuais.&lt;/p&gt;




&lt;h2&gt;
  
  
  26. Heap vs. Off-Heap vs. Overhead
&lt;/h2&gt;

&lt;p&gt;Uma simplificação útil é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROCESS MEMORY
     │
     ├── JVM Heap
     │
     ├── JVM Non-Heap
     │
     ├── Direct / Native Memory
     │
     ├── Python Worker Memory
     │
     └── Runtime Overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é mais próximo da realidade do que pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;memory = spark.executor.memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando um container é encerrado por ultrapassar limite de memória, precisamos descobrir &lt;strong&gt;qual parte do consumo cresceu&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  27. Garbage Collection
&lt;/h2&gt;

&lt;p&gt;Agora vamos entrar em outro sintoma importante:&lt;/p&gt;

&lt;h1&gt;
  
  
  GC
&lt;/h1&gt;

&lt;p&gt;A JVM gerencia automaticamente memória de objetos.&lt;/p&gt;

&lt;p&gt;Objetos são criados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Objects
 ↓
Heap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois deixam de ser necessários.&lt;/p&gt;

&lt;p&gt;O Garbage Collector precisa recuperar esse espaço.&lt;/p&gt;

&lt;p&gt;Em um cenário saudável:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application Work ███████████████████

GC               █
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas sob forte pressão podemos encontrar algo mais próximo de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application ███████

GC          █████████████
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A aplicação passa tempo demais tentando gerenciar memória em vez de processar dados.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  28. GC alto é sintoma
&lt;/h2&gt;

&lt;p&gt;Assim como OOM, GC excessivo não deveria gerar automaticamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Aumente a heap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Talvez tenhamos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;objetos demais;&lt;/li&gt;
&lt;li&gt;partições grandes;&lt;/li&gt;
&lt;li&gt;agregações pesadas;&lt;/li&gt;
&lt;li&gt;estruturas intermediárias enormes;&lt;/li&gt;
&lt;li&gt;cache excessivo;&lt;/li&gt;
&lt;li&gt;serialização inadequada;&lt;/li&gt;
&lt;li&gt;concorrência excessiva por Executor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Precisamos descobrir por que a memória está sob pressão.&lt;/p&gt;




&lt;h2&gt;
  
  
  29. Mais cores por Executor também afetam memória
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor
8 cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependendo da configuração, várias Tasks podem executar simultaneamente nesse Executor.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor Memory
      │
 ┌────┼────┬────┬────┬────┐
 ▼    ▼    ▼    ▼    ▼    ▼
T1   T2   T3   T4   T5   ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada Task pode precisar de memória.&lt;/p&gt;

&lt;p&gt;Então existe uma relação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mais Tasks concorrentes por Executor
              ↓
mais competição pela memória disponível
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso mostra por que tuning não deveria analisar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;executor memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;isoladamente.&lt;/p&gt;

&lt;p&gt;Precisamos olhar para:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor Memory
      +
Executor Cores
      +
Partition Size
      +
Task Behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  30. Executors gigantes também não são automaticamente melhores
&lt;/h2&gt;

&lt;p&gt;Poderíamos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 Executor
64 cores
256 GB RAM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parece poderoso.&lt;/p&gt;

&lt;p&gt;Mas Executors gigantes podem trazer trade-offs relacionados a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concorrência;&lt;/li&gt;
&lt;li&gt;garbage collection;&lt;/li&gt;
&lt;li&gt;blast radius de falha;&lt;/li&gt;
&lt;li&gt;utilização;&lt;/li&gt;
&lt;li&gt;scheduling;&lt;/li&gt;
&lt;li&gt;memória compartilhada entre muitas Tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No outro extremo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;64 Executors
1 core
4 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;também possui trade-offs.&lt;/p&gt;

&lt;p&gt;Não existe um tamanho universal.&lt;/p&gt;

&lt;p&gt;Precisamos equilibrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cores
memory
parallelism
partition size
workload
cluster manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  31. Broadcast também consome memória
&lt;/h2&gt;

&lt;p&gt;Na Parte 5 vimos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;broadcast&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;grande&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pequena&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A estratégia pode evitar um shuffle grande.&lt;/p&gt;

&lt;p&gt;Mas existe um custo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Small Table
    ↓
Broadcast
 ┌──┼──┐
 ▼  ▼  ▼
E1 E2 E3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada Executor precisa conseguir trabalhar com a estrutura transmitida.&lt;/p&gt;

&lt;p&gt;Se tentarmos broadcast de algo grande demais, podemos criar pressão significativa de memória.&lt;/p&gt;

&lt;p&gt;Novamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Network optimization
        ↕
Memory cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sistemas distribuídos são cheios de trade-offs.&lt;/p&gt;




&lt;h2&gt;
  
  
  32. Cache também compete por recursos
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df_b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df_c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df_d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pode parecer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quanto mais cache, melhor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache A
Cache B
Cache C
Cache D
     ↓
Storage Pressure
     ↓
Eviction / recomputation / resource competition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache deveria ser uma decisão baseada em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reuse frequency
×
recomputation cost
×
available resources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e não um reflexo automático.&lt;/p&gt;




&lt;h2&gt;
  
  
  33. &lt;code&gt;unpersist()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Se um dataset não será mais reutilizado, podemos liberar sua persistência:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unpersist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso comunica:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Não preciso mais manter essas partições persistidas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Em pipelines longos isso pode ser importante.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Phase A
 ↓
cache intermediate
 ↓
reuse several times
 ↓
phase completed
 ↓
unpersist
 ↓
free resources for Phase B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gerenciamento de memória também faz parte do design do pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  34. O Spark UI volta a ser essencial
&lt;/h2&gt;

&lt;p&gt;Se existe problema de memória, não quero começar alterando configurações.&lt;/p&gt;

&lt;p&gt;Quero observar.&lt;/p&gt;

&lt;p&gt;No Spark UI e métricas relacionadas, procuro sinais como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task Duration
GC Time
Shuffle Read
Shuffle Write
Spill
Storage
Executor behavior
Failed Tasks
Skew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também observo logs.&lt;/p&gt;

&lt;p&gt;A pergunta é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;O que aconteceu antes da falha?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  35. Um padrão de investigação
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job falhou
   ↓
Executor lost
   ↓
Memory-related error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meu raciocínio começaria assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qual Stage?
    ↓
Qual operação?
    ↓
Join?
Aggregation?
Sort?
UDF?
Cache?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Todas as Tasks falharam?
        ↓
ou apenas algumas?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se apenas algumas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Possível skew?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qual tamanho das partitions?
Quanto shuffle?
Quanto spill?
Quanto GC?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Só então:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Heap insuficiente?
Overhead insuficiente?
Particionamento ruim?
Broadcast inadequado?
Concorrência excessiva?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  36. Caso 1 — Driver morrendo
&lt;/h2&gt;

&lt;p&gt;Sintoma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Driver OutOfMemory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;120 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Driver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diagnóstico provável:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Distributed Data
      ↓
collect()
      ↓
Single Process
      ↓
OOM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solução arquitetural:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Não coletar o dataset inteiro.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Talvez:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou continuar o processamento distribuído.&lt;/p&gt;

&lt;p&gt;A solução principal não é necessariamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8 GB → 128 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  37. Caso 2 — Uma Task morre repetidamente
&lt;/h2&gt;

&lt;p&gt;Sintoma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage 12

199 Tasks → OK
1 Task    → FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é extremamente informativo.&lt;/p&gt;

&lt;p&gt;Talvez tenhamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data Skew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou uma partição muito maior.&lt;/p&gt;

&lt;p&gt;Se aumentarmos memória de todos os Executors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cluster cost ↑
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para resolver uma única partição anormal, talvez estejamos atacando o lugar errado.&lt;/p&gt;

&lt;p&gt;Precisamos investigar a distribuição.&lt;/p&gt;




&lt;h2&gt;
  
  
  38. Caso 3 — Todas as Tasks sofrem Spill
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1 → heavy spill
Task 2 → heavy spill
Task 3 → heavy spill
Task 4 → heavy spill
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora talvez tenhamos um problema sistêmico.&lt;/p&gt;

&lt;p&gt;Possíveis causas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partitions muito grandes
Memory insuficiente
Join pesado
Sort pesado
Aggregation pesada
Task concurrency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse cenário, ajustar recursos pode realmente fazer parte da solução.&lt;/p&gt;

&lt;p&gt;Mas ainda precisamos medir.&lt;/p&gt;




&lt;h2&gt;
  
  
  39. Caso 4 — OOM depois de Broadcast
&lt;/h2&gt;

&lt;p&gt;Imagine que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job funcionava
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e alguém adicionou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dim&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora Executors começam a morrer.&lt;/p&gt;

&lt;p&gt;Pergunta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qual o tamanho real de dim?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Talvez:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"small table"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tenha crescido.&lt;/p&gt;

&lt;p&gt;Ontem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hoje:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;6 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma otimização válida no passado pode se tornar um problema quando os dados mudam.&lt;/p&gt;

&lt;p&gt;Isso é importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Performance depende também da evolução dos dados.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  40. Caso 5 — PySpark e memória externa ao heap
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark.executor.memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;aparentemente confortável.&lt;/p&gt;

&lt;p&gt;Mas containers continuam sendo encerrados por memória.&lt;/p&gt;

&lt;p&gt;Agora precisamos olhar para:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JVM Heap
     +
Python workers
     +
Native memory
     +
Direct buffers
     +
Other overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É exatamente nesse tipo de cenário que compreender &lt;code&gt;memoryOverhead&lt;/code&gt; deixa de ser detalhe e passa a ser parte do diagnóstico.&lt;/p&gt;




&lt;h2&gt;
  
  
  41. Não ajuste &lt;code&gt;memoryOverhead&lt;/code&gt; às cegas
&lt;/h2&gt;

&lt;p&gt;Se um Executor morre por ultrapassar limites externos ao heap, aumentar overhead pode ser apropriado.&lt;/p&gt;

&lt;p&gt;Mas ainda precisamos perguntar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Por que esse consumo existe?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pode ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python;&lt;/li&gt;
&lt;li&gt;bibliotecas nativas;&lt;/li&gt;
&lt;li&gt;buffers;&lt;/li&gt;
&lt;li&gt;comportamento da JVM;&lt;/li&gt;
&lt;li&gt;volume de dados;&lt;/li&gt;
&lt;li&gt;operação específica.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A configuração correta depende do workload e do ambiente.&lt;/p&gt;

&lt;p&gt;Por isso evito recomendações como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sempre configure X GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sempre use Y%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sem contexto.&lt;/p&gt;

&lt;p&gt;Defaults e comportamentos também evoluem entre versões.&lt;/p&gt;




&lt;h2&gt;
  
  
  42. Memória em Kubernetes
&lt;/h2&gt;

&lt;p&gt;Em ambientes containerizados, a questão fica ainda mais visível.&lt;/p&gt;

&lt;p&gt;Podemos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kubernetes Pod
      │
      └── Executor
            │
            ├── JVM Heap
            ├── Non-Heap
            ├── Native
            ├── Python
            └── Overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Pod possui limites.&lt;/p&gt;

&lt;p&gt;Não basta a JVM achar que possui heap suficiente.&lt;/p&gt;

&lt;p&gt;O consumo total do processo e componentes associados precisa respeitar os recursos disponíveis.&lt;/p&gt;

&lt;p&gt;Esse é um dos motivos pelos quais problemas modernos de Spark precisam ser analisados também no nível da infraestrutura.&lt;/p&gt;




&lt;h2&gt;
  
  
  43. Memória em YARN
&lt;/h2&gt;

&lt;p&gt;Em ambientes YARN, existe raciocínio semelhante.&lt;/p&gt;

&lt;p&gt;O Executor executa dentro de recursos controlados pelo gerenciador do cluster.&lt;/p&gt;

&lt;p&gt;Historicamente, muitos erros de &lt;code&gt;memoryOverhead&lt;/code&gt; ficaram conhecidos justamente em ambientes onde o container ultrapassava seu limite mesmo quando a heap configurada parecia aceitável.&lt;/p&gt;

&lt;p&gt;O princípio permanece:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JVM Heap
≠
Total Process Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  44. A configuração depende do ambiente
&lt;/h2&gt;

&lt;p&gt;Hoje Spark pode rodar em diferentes contextos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Standalone
YARN
Kubernetes
Managed Platforms
Cloud Services
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso significa que recomendações de memória precisam considerar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spark version;&lt;/li&gt;
&lt;li&gt;cluster manager;&lt;/li&gt;
&lt;li&gt;container limits;&lt;/li&gt;
&lt;li&gt;JVM;&lt;/li&gt;
&lt;li&gt;Python;&lt;/li&gt;
&lt;li&gt;workload;&lt;/li&gt;
&lt;li&gt;plataforma gerenciada.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uma configuração copiada de um artigo antigo pode estar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;correta historicamente
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inadequada atualmente
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ao mesmo tempo.&lt;/p&gt;

&lt;p&gt;Esse é exatamente o tipo de cuidado que estou aplicando ao revisitar meu material antigo nesta série.&lt;/p&gt;




&lt;h2&gt;
  
  
  45. Um modelo mental melhor para OOM
&lt;/h2&gt;

&lt;p&gt;Em vez de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OOM
 ↓
More Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;prefiro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    OOM
                     │
          ┌──────────┴──────────┐
          ▼                     ▼
       Driver?               Executor?
          │                     │
          ▼                     ▼
      collect()?          Which Stage?
      toPandas()?              │
      metadata?                ▼
                         Which Operation?
                               │
                    ┌──────────┼──────────┐
                    ▼          ▼          ▼
                  Skew      Shuffle    Broadcast
                    │          │          │
                    └──────────┼──────────┘
                               ▼
                         Partition Size
                               │
                               ▼
                      Heap or Overhead?
                               │
                               ▼
                         Root Cause
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse modelo evita tratar todos os erros de memória como iguais.&lt;/p&gt;




&lt;h2&gt;
  
  
  46. Performance tuning começa com observabilidade
&lt;/h2&gt;

&lt;p&gt;Imagine dois engenheiros.&lt;/p&gt;

&lt;p&gt;O primeiro vê:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OutOfMemoryError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e imediatamente altera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark.executor.memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O segundo investiga:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage
Tasks
Partition distribution
Shuffle
Spill
GC
Executor logs
Physical plan
Data volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O segundo talvez leve alguns minutos a mais antes de alterar a configuração.&lt;/p&gt;

&lt;p&gt;Mas a chance de resolver a causa real é muito maior.&lt;/p&gt;

&lt;p&gt;Essa é uma diferença importante entre:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;configuration tuning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;performance engineering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  47. Um checklist para problemas de memória
&lt;/h2&gt;

&lt;p&gt;Quando encontro um problema de memória em Spark, quero responder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;O erro aconteceu no Driver ou Executor?&lt;/li&gt;
&lt;li&gt;Qual Stage estava executando?&lt;/li&gt;
&lt;li&gt;Qual operação estava acontecendo?&lt;/li&gt;
&lt;li&gt;Existe &lt;code&gt;collect()&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Existe &lt;code&gt;toPandas()&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Existe broadcast?&lt;/li&gt;
&lt;li&gt;Existe cache excessivo?&lt;/li&gt;
&lt;li&gt;As partições estão equilibradas?&lt;/li&gt;
&lt;li&gt;Existe data skew?&lt;/li&gt;
&lt;li&gt;Qual o tamanho das maiores Tasks?&lt;/li&gt;
&lt;li&gt;Existe Shuffle?&lt;/li&gt;
&lt;li&gt;Quanto?&lt;/li&gt;
&lt;li&gt;Existe Spill?&lt;/li&gt;
&lt;li&gt;Quanto?&lt;/li&gt;
&lt;li&gt;Quanto tempo está sendo gasto em GC?&lt;/li&gt;
&lt;li&gt;Todas as Tasks sofrem ou apenas algumas?&lt;/li&gt;
&lt;li&gt;Existem Python workers?&lt;/li&gt;
&lt;li&gt;Existe consumo nativo relevante?&lt;/li&gt;
&lt;li&gt;O limite do container está sendo ultrapassado?&lt;/li&gt;
&lt;li&gt;O problema é heap ou overhead?&lt;/li&gt;
&lt;li&gt;A configuração corresponde à versão e ao cluster manager utilizados?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Só depois:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Qual configuração preciso alterar?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  48. O que eu evitaria fazer automaticamente
&lt;/h2&gt;

&lt;p&gt;Se o Job estiver lento ou falhando, eu evitaria começar por:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dobrar executor memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dobrar driver memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aumentar memoryOverhead arbitrariamente
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cachear tudo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reduzir o número de partitions sem medir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aumentar o cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Todas essas ações podem ser válidas.&lt;/p&gt;

&lt;p&gt;Mas nenhuma delas deveria substituir diagnóstico.&lt;/p&gt;




&lt;h2&gt;
  
  
  49. Memória, performance e custo
&lt;/h2&gt;

&lt;p&gt;Em cloud, existe ainda outra dimensão.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor Memory
8 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e simplesmente dobramos para:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;16 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em dezenas ou centenas de Executors.&lt;/p&gt;

&lt;p&gt;Talvez o Job fique estável.&lt;/p&gt;

&lt;p&gt;Mas talvez tenhamos acabado de aumentar recursos para compensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data Skew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repartition ruim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;join inadequado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cache desnecessário
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Então temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problema arquitetural
       ↓
Mais infraestrutura
       ↓
Maior custo
       ↓
Problema mascarado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Performance tuning também é FinOps.&lt;/p&gt;




&lt;h2&gt;
  
  
  50. A conexão entre tudo que vimos até aqui
&lt;/h2&gt;

&lt;p&gt;Agora podemos montar uma visão muito mais completa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    DATA
                      │
                      ▼
                 PARTITIONS
                      │
                      ▼
                    TASKS
                      │
                      ▼
                  EXECUTORS
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
       Memory       CPU        Network
          │                       │
          ▼                       ▼
      Aggregation              Shuffle
          │                       │
          └───────────┬───────────┘
                      ▼
                    Spill
                      │
                      ▼
                     Disk
                      │
                      ▼
                 Performance
                      │
                      ▼
                    Cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora adicione:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data Skew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e podemos gerar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Uneven Partition
      ↓
Huge Task
      ↓
Memory Pressure
      ↓
Spill / GC
      ↓
Slow Task
      ↓
Straggler
      ↓
Slow Stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso mostra por que os capítulos anteriores estavam conectados.&lt;/p&gt;




&lt;h2&gt;
  
  
  51. A principal lição deste capítulo
&lt;/h2&gt;

&lt;p&gt;Se eu pudesse resumir memória no Spark em uma frase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Problemas de memória frequentemente são sintomas de como os dados e a computação foram distribuídos.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nem sempre.&lt;/p&gt;

&lt;p&gt;Às vezes realmente precisamos de mais memória.&lt;/p&gt;

&lt;p&gt;Mas antes de aumentar recursos, quero saber:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quem está consumindo?
        ↓
Onde?
        ↓
Durante qual operação?
        ↓
Por quê?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Só então:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Architecture
     +
Configuration
     +
Resources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;entram na solução.&lt;/p&gt;




&lt;h2&gt;
  
  
  52. Como penso hoje
&lt;/h2&gt;

&lt;p&gt;Meu modelo mental deixou de ser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark OOM
   ↓
Increase Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e passou a ser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark OOM
   ↓
Driver or Executor?
   ↓
Heap or Overhead?
   ↓
Which Stage?
   ↓
Which Task?
   ↓
Which Operation?
   ↓
Partition Size?
   ↓
Skew?
   ↓
Shuffle?
   ↓
Spill?
   ↓
Broadcast?
   ↓
Cache?
   ↓
Python / Native Memory?
   ↓
Root Cause
   ↓
Then Tune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse raciocínio é muito mais útil do que decorar valores de configuração.&lt;/p&gt;




&lt;h2&gt;
  
  
  Próximo artigo
&lt;/h2&gt;

&lt;p&gt;Agora entendemos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partitions
    ↓
Tasks
    ↓
Memory
    ↓
Shuffle
    ↓
Spill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas existe outro conceito fundamental que apareceu desde os primeiros artigos:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lineage.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Quando uma partição é perdida, como Spark sabe reconstruí-la?&lt;/p&gt;

&lt;p&gt;Por que RDD significa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Resilient Distributed Dataset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;?&lt;/p&gt;

&lt;p&gt;Como Spark obtém tolerância a falhas sem simplesmente replicar toda computação intermediária?&lt;/p&gt;

&lt;p&gt;E o que acontece quando a linhagem fica grande demais?&lt;/p&gt;

&lt;p&gt;No próximo artigo vamos explorar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RDD Lineage;&lt;/li&gt;
&lt;li&gt;DAG;&lt;/li&gt;
&lt;li&gt;fault tolerance;&lt;/li&gt;
&lt;li&gt;recomputation;&lt;/li&gt;
&lt;li&gt;cache vs. recomputation;&lt;/li&gt;
&lt;li&gt;checkpoint;&lt;/li&gt;
&lt;li&gt;persistência;&lt;/li&gt;
&lt;li&gt;falha de Executor;&lt;/li&gt;
&lt;li&gt;recuperação de partições;&lt;/li&gt;
&lt;li&gt;por que Spark é "resilient";&lt;/li&gt;
&lt;li&gt;e como essas ideias evoluíram nas APIs modernas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vamos partir deste modelo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source
  ↓
Map
  ↓
Filter
  ↓
Join
  ↓
Aggregate
  ↓
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para enxergar algo mais profundo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              LINEAGE
                 │
                 ▼
Source → Transform → Transform → Shuffle → Result
  │          │          │
  └──────────┴──────────┘
             │
      information needed
       for recomputation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa é uma das ideias fundamentais que fizeram o Spark ser Spark.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;← Parte 6:&lt;/strong&gt; Spark SQL e DataFrames — do código ao plano de execução&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Próximo:&lt;/strong&gt; Parte 8 — RDD Lineage e tolerância a falhas: como o Spark se recupera quando algo dá errado&lt;/p&gt;

</description>
      <category>cluster</category>
      <category>spark</category>
      <category>python</category>
      <category>bigdata</category>
    </item>
    <item>
      <title>Como aprendi Apache Spark — Parte 6: Spark SQL e DataFrames — do código ao plano de execução</title>
      <dc:creator>renanpyd</dc:creator>
      <pubDate>Sat, 24 Oct 2026 16:07:14 +0000</pubDate>
      <link>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-6-spark-sql-e-dataframes-do-codigo-ao-plano-de-execucao-46lg</link>
      <guid>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-6-spark-sql-e-dataframes-do-codigo-ao-plano-de-execucao-46lg</guid>
      <description>&lt;p&gt;Nos primeiros artigos desta série, construímos o Spark de baixo para cima.&lt;/p&gt;

&lt;p&gt;Começamos com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RDD
 ↓
Partitions
 ↓
Tasks
 ↓
Executors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois entendemos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transformations
      ↓
Lazy Evaluation
      ↓
Jobs
      ↓
Stages
      ↓
Shuffle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E no artigo anterior chegamos aos joins distribuídos e a uma pergunta importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quando escrevemos uma transformação de alto nível, como o Spark decide o que realmente executar?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa pergunta nos leva a uma das maiores evoluções da plataforma:&lt;/p&gt;

&lt;h1&gt;
  
  
  Spark SQL e DataFrames
&lt;/h1&gt;

&lt;p&gt;Foi aqui que minha visão sobre Spark começou a mudar.&lt;/p&gt;

&lt;p&gt;Spark deixou de parecer apenas uma API para manipular coleções distribuídas e passou a mostrar algo muito mais interessante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;um engine capaz de compreender a estrutura da computação e otimizar sua execução.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa diferença é fundamental.&lt;/p&gt;

&lt;p&gt;Com RDDs, dizemos muito sobre &lt;strong&gt;como&lt;/strong&gt; executar uma transformação.&lt;/p&gt;

&lt;p&gt;Com DataFrames e SQL, podemos expressar principalmente &lt;strong&gt;o que queremos obter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;E quanto mais o Spark entende nossa intenção, mais oportunidades ele tem para otimizar o caminho até o resultado.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Antes dos DataFrames: o mundo dos RDDs
&lt;/h1&gt;

&lt;p&gt;Nos primeiros anos do Spark, RDDs eram a principal abstração da plataforma.&lt;/p&gt;

&lt;p&gt;Podíamos escrever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/vendas.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;rdd&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para nós, esse código possui significado.&lt;/p&gt;

&lt;p&gt;Sabemos que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x[1] = estado
x[3] = valor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas para o Spark, boa parte dessa lógica pode ser simplesmente código arbitrário executado sobre objetos.&lt;/p&gt;

&lt;p&gt;Ele não necessariamente possui a mesma compreensão semântica que teria se soubesse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;coluna = valor
tipo = decimal
condição = valor &amp;gt; 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa diferença é enorme.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Estrutura muda tudo
&lt;/h1&gt;

&lt;p&gt;Agora imagine os mesmos dados representados por um DataFrame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------+---------+------+-------+
|order_id|estado   |produto|valor |
+--------+---------+------+-------+
|1001    |SP       |A     |1500.0 |
|1002    |RJ       |B     |800.0  |
|1003    |MG       |C     |2200.0 |
+--------+---------+------+-------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora Spark conhece algo parecido com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schema

order_id → integer
estado   → string
produto  → string
valor    → double
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E podemos escrever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor &amp;gt; 1000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora o engine consegue entender muito mais sobre nossa intenção.&lt;/p&gt;

&lt;p&gt;Ele sabe que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;valor&lt;/code&gt; é uma coluna;&lt;/li&gt;
&lt;li&gt;existe uma condição sobre essa coluna;&lt;/li&gt;
&lt;li&gt;apenas &lt;code&gt;estado&lt;/code&gt; e &lt;code&gt;valor&lt;/code&gt; serão necessários posteriormente;&lt;/li&gt;
&lt;li&gt;existe um schema;&lt;/li&gt;
&lt;li&gt;determinadas regras de otimização podem ser aplicadas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essa informação abre uma enorme oportunidade para otimização.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. De RDD para DataFrame
&lt;/h1&gt;

&lt;p&gt;Uma forma útil de enxergar a evolução é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RDD
│
├── objetos distribuídos
├── transformations
├── actions
├── partitions
└── controle de baixo nível
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataFrame
│
├── dados estruturados
├── schema
├── expressões
├── plano lógico
└── otimização automática
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso não significa que DataFrames deixaram de utilizar computação distribuída.&lt;/p&gt;

&lt;p&gt;Por baixo deles continuam existindo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partitions
Tasks
Stages
Executors
Shuffle
Memory
Network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que mudou foi a abstração.&lt;/p&gt;

&lt;p&gt;Podemos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataFrame API
      ↓
Logical Plan
      ↓
Optimizer
      ↓
Physical Plan
      ↓
Distributed Execution
      ↓
Tasks
      ↓
Executors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa camada intermediária é extremamente poderosa.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Spark SQL
&lt;/h1&gt;

&lt;p&gt;O nome Spark SQL pode causar uma interpretação limitada.&lt;/p&gt;

&lt;p&gt;Pode parecer apenas:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Uma maneira de executar SQL no Spark."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mas Spark SQL é muito mais do que isso.&lt;/p&gt;

&lt;p&gt;Ele representa o módulo de processamento de dados estruturados do Spark.&lt;/p&gt;

&lt;p&gt;Podemos escrever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;receita&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vendas&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ano&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2026&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou utilizar DataFrame API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ano = 2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essas duas formas podem chegar a planos de execução equivalentes ou muito semelhantes.&lt;/p&gt;

&lt;p&gt;Isso acontece porque ambas alimentam a mesma infraestrutura de processamento estruturado.&lt;/p&gt;

&lt;p&gt;Podemos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SQL
 ─────────┐
          │
          ▼
       Spark SQL
          ▲
          │
DataFrame API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark SQL
    ↓
Logical Plan
    ↓
Optimization
    ↓
Physical Plan
    ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  5. SparkSession: a entrada moderna
&lt;/h1&gt;

&lt;p&gt;Historicamente, quem estudou versões antigas do Spark encontrou objetos como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SparkContext
SQLContext
HiveContext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com a evolução da plataforma, &lt;code&gt;SparkSession&lt;/code&gt; tornou-se o principal ponto de entrada para trabalhar com dados estruturados.&lt;/p&gt;

&lt;p&gt;Em PySpark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;

&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SparkSQLExample&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A partir dela podemos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/vendas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;executar SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    SELECT *
    FROM vendas
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;criar DataFrames:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dados&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;schema&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e ainda acessar o contexto Spark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sparkContext&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma forma de enxergar essa evolução é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark antigo

SparkContext
SQLContext
HiveContext
     ↓

Spark moderno

SparkSession
     ↓
DataFrame / SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  6. Schema: Spark precisa entender os dados
&lt;/h1&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"1001,SP,1500.00"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem estrutura, temos apenas uma sequência de caracteres.&lt;/p&gt;

&lt;p&gt;Agora defina:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order_id → integer
estado   → string
valor    → double
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos semântica.&lt;/p&gt;

&lt;p&gt;Em PySpark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IntegerType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;StringType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DoubleType&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StructType&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nc"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;IntegerType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;StringType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;StructField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;DoubleType&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora Spark possui informações sobre os tipos.&lt;/p&gt;

&lt;p&gt;Isso permite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validação estrutural;&lt;/li&gt;
&lt;li&gt;expressões tipadas;&lt;/li&gt;
&lt;li&gt;planejamento;&lt;/li&gt;
&lt;li&gt;otimizações;&lt;/li&gt;
&lt;li&gt;representação mais eficiente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Schema não é apenas documentação.&lt;/p&gt;

&lt;p&gt;Ele faz parte da maneira como o engine entende os dados.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Schema inference ou schema explícito?
&lt;/h1&gt;

&lt;p&gt;Podemos pedir para determinadas fontes inferirem o schema.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inferSchema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/vendas.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é conveniente.&lt;/p&gt;

&lt;p&gt;Mas em pipelines de produção, frequentemente existe valor em definir explicitamente o schema.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/vendas.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora o contrato esperado está claro.&lt;/p&gt;

&lt;p&gt;Isso pode ajudar com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;previsibilidade;&lt;/li&gt;
&lt;li&gt;qualidade;&lt;/li&gt;
&lt;li&gt;evolução do pipeline;&lt;/li&gt;
&lt;li&gt;controle de tipos;&lt;/li&gt;
&lt;li&gt;detecção de problemas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A escolha depende do contexto, mas existe uma lição importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Schema é parte do contrato de dados.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  8. DataFrame é declarativo
&lt;/h1&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor &amp;gt; 1000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nós não estamos dizendo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pegue exatamente este byte
 ↓
coloque nesta estrutura
 ↓
execute esta instrução
 ↓
mova para esta máquina
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Estamos descrevendo uma transformação.&lt;/p&gt;

&lt;p&gt;Algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quero registros onde valor &amp;gt; 1000

e

quero apenas estado e valor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark pode então decidir como executar isso.&lt;/p&gt;

&lt;p&gt;Essa separação entre:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHAT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HOW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;é uma das ideias centrais dos sistemas declarativos.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Lazy Evaluation continua existindo
&lt;/h1&gt;

&lt;p&gt;DataFrames não abandonaram o conceito de lazy evaluation.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/vendas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;filtrado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor &amp;gt; 1000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;selecionado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filtrado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Até aqui estamos principalmente construindo uma descrição da computação.&lt;/p&gt;

&lt;p&gt;Então:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;selecionado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;temos uma Action.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;read
 ↓
filter
 ↓
select
 ↓
show()
 ↓
ACTION
 ↓
EXECUTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa execução tardia é importante porque permite que Spark observe o conjunto de transformações antes de executá-las.&lt;/p&gt;

&lt;p&gt;E então possa otimizá-las.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Do código ao plano
&lt;/h1&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor &amp;gt; 1000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos imaginar inicialmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataFrame Operations
        ↓
Unresolved Logical Plan
        ↓
Analyzed Logical Plan
        ↓
Optimized Logical Plan
        ↓
Physical Plans
        ↓
Selected Physical Plan
        ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vamos abrir cada etapa.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. Unresolved Logical Plan
&lt;/h1&gt;

&lt;p&gt;Quando escrevemos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;vendas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark inicialmente recebe referências a nomes.&lt;/p&gt;

&lt;p&gt;Algo conceitualmente semelhante a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project
├── customer_id
└── valor
    ↓
  vendas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas ainda precisa verificar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existe customer_id?

Existe valor?

De qual relação elas vêm?

Quais são os tipos?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse plano ainda precisa ser resolvido.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Analysis
&lt;/h1&gt;

&lt;p&gt;Durante a análise, Spark utiliza informações do catálogo e schemas para resolver referências.&lt;/p&gt;

&lt;p&gt;Podemos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"customer_id"
      ↓
Analyzer
      ↓
vendas.customer_id
      ↓
LongType
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se escrevermos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;vendas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coluna_que_nao_existe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;o Spark não consegue resolver corretamente essa referência.&lt;/p&gt;

&lt;p&gt;A análise transforma uma descrição ainda incompleta em um plano semanticamente compreendido.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. Logical Plan
&lt;/h1&gt;

&lt;p&gt;Depois de resolver as referências, temos uma representação lógica da consulta.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project
    estado
    valor
       │
       ▼
Filter
    valor &amp;gt; 1000
       │
       ▼
Relation
    vendas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observe que ainda estamos falando sobre:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;o que precisa acontecer&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;e não exatamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;qual algoritmo físico executará cada etapa&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa separação é fundamental.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. Catalyst Optimizer
&lt;/h1&gt;

&lt;p&gt;Agora chegamos a uma das partes mais interessantes:&lt;/p&gt;

&lt;h1&gt;
  
  
  Catalyst
&lt;/h1&gt;

&lt;p&gt;Catalyst é o framework de otimização de consultas utilizado pelo Spark SQL.&lt;/p&gt;

&lt;p&gt;Ele pode aplicar regras ao plano lógico.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logical Plan
     ↓
Optimization Rules
     ↓
Optimized Logical Plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essas regras podem transformar a consulta em outra forma equivalente, mas potencialmente mais eficiente.&lt;/p&gt;

&lt;p&gt;Duas otimizações são especialmente intuitivas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Predicate Pushdown
Projection Pruning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vamos entendê-las.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Predicate Pushdown
&lt;/h1&gt;

&lt;p&gt;Imagine um dataset de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 TB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;mas queremos apenas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ano = 2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma execução ruim seria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read 5 TB
    ↓
Load everything
    ↓
Filter ano = 2026
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se a fonte e o formato suportarem o comportamento adequado, o filtro pode ser levado para mais perto da leitura.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Filter
   ↓
Data Source
   ↓
Read only relevant data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em vez de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATA
 ↓
READ EVERYTHING
 ↓
FILTER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;podemos ter algo mais próximo de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FILTER INFORMATION
        ↓
DATA SOURCE
        ↓
REDUCED READ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso pode diminuir drasticamente o volume de dados processado.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. Partition Pruning
&lt;/h1&gt;

&lt;p&gt;Imagine um Data Lake organizado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/data/vendas/

ano=2024/
ano=2025/
ano=2026/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ano = 2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se Spark consegue utilizar a informação de particionamento, não precisa necessariamente ler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ano=2024
ano=2025
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/data/vendas/
│
├── ano=2024  ✕
├── ano=2025  ✕
└── ano=2026  ✓
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é &lt;strong&gt;partition pruning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Observe como isso se conecta à Parte 3.&lt;/p&gt;

&lt;p&gt;Naquele artigo distinguimos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;partições de execução
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;particionamento de armazenamento
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora vemos por que essa distinção importa.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. Projection Pruning
&lt;/h1&gt;

&lt;p&gt;Imagine uma tabela com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 colunas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas nossa consulta precisa apenas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não faz sentido carregar e processar 200 colunas se apenas duas serão necessárias, quando a fonte e o plano permitem evitar isso.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 columns
     ↓
Projection Pruning
     ↓
2 required columns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Novamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Não leia nem mova dados que você não precisa.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa ideia apareceu em shuffle, joins e agora aparece no próprio planejamento da consulta.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. Columnar formats tornam isso ainda mais poderoso
&lt;/h1&gt;

&lt;p&gt;Agora pense em formatos colunares como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parquet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id
order_id
product_id
description
category
state
country
value
tax
discount
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se precisamos apenas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id
value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;um formato colunar pode permitir que a engine leia apenas as colunas relevantes.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ROW FORMAT

[row completa]
[row completa]
[row completa]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COLUMNAR

customer_id → [ ... ]
value       → [ ... ]
state       → [ ... ]
product     → [ ... ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora podemos selecionar fisicamente apenas partes necessárias do dataset em determinados cenários.&lt;/p&gt;

&lt;p&gt;Isso conecta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark SQL
+
Parquet
+
Projection Pruning
+
Predicate Pushdown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em uma arquitetura muito eficiente.&lt;/p&gt;




&lt;h1&gt;
  
  
  19. Optimized Logical Plan
&lt;/h1&gt;

&lt;p&gt;Depois das regras de otimização, podemos sair de algo conceitualmente parecido com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read everything
      ↓
Filter
      ↓
Select
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para algo mais eficiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Determine required columns
      ↓
Push applicable filters
      ↓
Read less data
      ↓
Continue execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado lógico é o mesmo.&lt;/p&gt;

&lt;p&gt;O custo físico pode ser muito diferente.&lt;/p&gt;

&lt;p&gt;Esse é um dos grandes benefícios de utilizar uma abstração que o engine consegue compreender.&lt;/p&gt;




&lt;h1&gt;
  
  
  20. Physical Plan
&lt;/h1&gt;

&lt;p&gt;Até agora falamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Filter
Project
Join
Aggregate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas ainda precisamos transformar isso em operações executáveis.&lt;/p&gt;

&lt;p&gt;É aí que chegamos ao:&lt;/p&gt;

&lt;h1&gt;
  
  
  Physical Plan
&lt;/h1&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;clientes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O plano lógico diz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JOIN
customer_id = customer_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas o plano físico precisa decidir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BroadcastHashJoin?

SortMergeJoin?

Outra estratégia disponível?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa é a ponte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logical Plan
      ↓
WHAT
      ↓
Physical Plan
      ↓
HOW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foi exatamente essa diferença que vimos na Parte 5.&lt;/p&gt;




&lt;h1&gt;
  
  
  21. &lt;code&gt;explain()&lt;/code&gt; é nossa janela para o optimizer
&lt;/h1&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ano = 2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos executar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;formatted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extended&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependendo do modo, podemos observar diferentes níveis de informação sobre os planos.&lt;/p&gt;

&lt;p&gt;Nosso objetivo não é decorar cada operador.&lt;/p&gt;

&lt;p&gt;É conseguir responder perguntas.&lt;/p&gt;




&lt;h1&gt;
  
  
  22. O que procuro no &lt;code&gt;explain()&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Quando olho um plano, quero identificar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scan
 ↓
Filter
 ↓
Project
 ↓
Exchange?
 ↓
Aggregate
 ↓
Join?
 ↓
Sort?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Principalmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Existe Exchange?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;porque isso pode indicar redistribuição de dados.&lt;/p&gt;

&lt;p&gt;Também quero saber:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;O filtro chegou perto da leitura?

As colunas desnecessárias foram removidas?

Qual estratégia de join foi escolhida?

Existe shuffle?

Existe sort?

Existe broadcast?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O plano começa a contar a história da execução.&lt;/p&gt;




&lt;h1&gt;
  
  
  23. Um exemplo mental
&lt;/h1&gt;

&lt;p&gt;Temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ano = 2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma visão ingênua seria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read
 ↓
Filter
 ↓
Select
 ↓
GroupBy
 ↓
Sum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas uma visão mais próxima do engine pode ser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parquet Scan
      │
      ├── required columns
      │
      └── pushed filters
      ↓
Partial Aggregation
      ↓
Exchange
      ↓
Final Aggregation
      ↓
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora enxergamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Storage Optimization
        +
Distributed Aggregation
        +
Shuffle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;no mesmo pipeline.&lt;/p&gt;




&lt;h1&gt;
  
  
  24. Agregação em duas etapas
&lt;/h1&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma implementação distribuída inteligente não precisa necessariamente enviar cada registro bruto pela rede antes de somar.&lt;/p&gt;

&lt;p&gt;Podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor A

SP 10
SP 20
RJ 30

     ↓

SP 30
RJ 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor B

SP 50
RJ 20
MG 40

     ↓

SP 50
RJ 20
MG 40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SP 30 ──┐
        ├──► SP 80
SP 50 ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso reduz o volume movimentado.&lt;/p&gt;

&lt;p&gt;É o mesmo princípio que encontramos anteriormente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Faça o máximo de redução local possível antes de movimentar dados.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  25. DataFrame vs. Python UDF
&lt;/h1&gt;

&lt;p&gt;Agora aparece uma consequência importante.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor_com_imposto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark entende a expressão:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valor * 1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora imagine encapsular lógica desnecessariamente em uma função Python arbitrária.&lt;/p&gt;

&lt;p&gt;O engine pode ter menos capacidade de compreender e otimizar internamente essa lógica, além de poder existir custo adicional associado à execução entre runtimes dependendo do tipo de UDF utilizado.&lt;/p&gt;

&lt;p&gt;Isso leva a uma recomendação importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quando possível, prefira funções nativas e expressões do Spark SQL antes de recorrer a UDFs personalizadas.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Não porque UDFs sejam "proibidas".&lt;/p&gt;

&lt;p&gt;Mas porque expressões nativas normalmente dão ao engine muito mais informação e oportunidades de otimização.&lt;/p&gt;




&lt;h1&gt;
  
  
  26. SQL vs. DataFrame API: qual é mais rápido?
&lt;/h1&gt;

&lt;p&gt;Uma pergunta comum:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SQL é mais rápido que DataFrame?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ou:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DataFrame é mais rápido que SQL?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A resposta interessante é que ambas podem alimentar o mesmo engine e resultar em planos equivalentes.&lt;/p&gt;

&lt;p&gt;Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor &amp;gt; 1000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vendas&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se o plano resultante for equivalente, não existe uma vantagem automática simplesmente por escolher uma sintaxe ou outra.&lt;/p&gt;

&lt;p&gt;Uma pergunta melhor é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Qual plano o Spark gerou?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Por isso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;é mais interessante do que discutir sintaxe isoladamente.&lt;/p&gt;




&lt;h1&gt;
  
  
  27. Tungsten
&lt;/h1&gt;

&lt;p&gt;Catalyst trata fortemente da otimização de planos.&lt;/p&gt;

&lt;p&gt;Mas Spark também evoluiu muito na maneira como executa computação.&lt;/p&gt;

&lt;p&gt;Um nome importante nessa evolução é:&lt;/p&gt;

&lt;h1&gt;
  
  
  Project Tungsten
&lt;/h1&gt;

&lt;p&gt;Tungsten introduziu uma série de melhorias focadas em eficiência de execução.&lt;/p&gt;

&lt;p&gt;Entre os objetivos estavam:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;melhor gerenciamento de memória;&lt;/li&gt;
&lt;li&gt;representação mais eficiente dos dados;&lt;/li&gt;
&lt;li&gt;redução de overhead de objetos;&lt;/li&gt;
&lt;li&gt;melhor uso de CPU;&lt;/li&gt;
&lt;li&gt;geração de código.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Podemos pensar em duas camadas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Catalyst
   ↓
Como otimizar o plano?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tungsten
   ↓
Como executar esse plano eficientemente?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa divisão não precisa ser entendida como uma fronteira rígida da implementação atual, mas é um excelente modelo histórico e conceitual.&lt;/p&gt;




&lt;h1&gt;
  
  
  28. O custo dos objetos
&lt;/h1&gt;

&lt;p&gt;Em aplicações JVM tradicionais, representar milhões ou bilhões de registros como grandes estruturas de objetos pode gerar overhead significativo.&lt;/p&gt;

&lt;p&gt;Imagine conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object
├── header
├── references
├── fields
└── alignment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiplique por:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.000.000.000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;de registros.&lt;/p&gt;

&lt;p&gt;O overhead começa a importar.&lt;/p&gt;

&lt;p&gt;Uma das grandes evoluções do Spark foi trabalhar com representações internas muito mais eficientes para dados estruturados.&lt;/p&gt;

&lt;p&gt;Isso ajuda em:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memória;&lt;/li&gt;
&lt;li&gt;cache;&lt;/li&gt;
&lt;li&gt;CPU;&lt;/li&gt;
&lt;li&gt;garbage collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Em Big Data, detalhes de representação se tornam problemas de arquitetura.&lt;/p&gt;




&lt;h1&gt;
  
  
  29. Whole-Stage Code Generation
&lt;/h1&gt;

&lt;p&gt;Outra otimização importante é conhecida como:&lt;/p&gt;

&lt;h1&gt;
  
  
  Whole-Stage Code Generation
&lt;/h1&gt;

&lt;p&gt;Imagine uma sequência:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scan
 ↓
Filter
 ↓
Project
 ↓
Aggregate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma execução ingênua poderia tratar cada operador como uma camada separada, passando dados entre várias abstrações.&lt;/p&gt;

&lt;p&gt;Com geração de código, Spark pode combinar partes compatíveis do pipeline e gerar código especializado para executar várias operações de maneira eficiente.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Operator
   ↓
Operator
   ↓
Operator
   ↓
Operator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pode se tornar algo mais próximo de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generated Execution Pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso reduz overhead e melhora utilização da CPU em diversos workloads.&lt;/p&gt;




&lt;h1&gt;
  
  
  30. Catalyst + Tungsten
&lt;/h1&gt;

&lt;p&gt;Agora podemos montar um modelo mais completo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            SQL / DataFrame API
                    │
                    ▼
               Logical Plan
                    │
                    ▼
                 Catalyst
                    │
                    ▼
          Optimized Logical Plan
                    │
                    ▼
              Physical Plan
                    │
                    ▼
          Efficient Execution
                    │
             Tungsten concepts
                    │
                    ▼
                  Tasks
                    │
                    ▼
                Executors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa arquitetura ajuda a explicar por que DataFrames se tornaram tão importantes.&lt;/p&gt;

&lt;p&gt;Não estamos apenas utilizando uma API mais bonita.&lt;/p&gt;

&lt;p&gt;Estamos dando ao engine informações suficientes para otimizar a computação.&lt;/p&gt;




&lt;h1&gt;
  
  
  31. Mas o plano inicial ainda pode estar errado
&lt;/h1&gt;

&lt;p&gt;Até aqui parece que Spark consegue decidir tudo antes da execução.&lt;/p&gt;

&lt;p&gt;Mas existe um problema:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Estimativas nem sempre correspondem à realidade.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine que Spark espera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition A = 200 MB
Partition B = 200 MB
Partition C = 200 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas durante a execução encontra:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition A = 180 MB
Partition B = 210 MB
Partition C = 12 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos skew.&lt;/p&gt;

&lt;p&gt;O plano foi criado com determinada expectativa.&lt;/p&gt;

&lt;p&gt;A realidade é diferente.&lt;/p&gt;

&lt;p&gt;É aqui que entra uma das evoluções mais importantes do Spark moderno.&lt;/p&gt;




&lt;h1&gt;
  
  
  32. Adaptive Query Execution
&lt;/h1&gt;

&lt;h1&gt;
  
  
  AQE
&lt;/h1&gt;

&lt;p&gt;Adaptive Query Execution permite que Spark adapte partes do plano utilizando informações coletadas durante a execução.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial Plan
     ↓
Execution starts
     ↓
Runtime Statistics
     ↓
AQE
     ↓
Adapted Plan
     ↓
Continue Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa é uma mudança importante de filosofia.&lt;/p&gt;

&lt;p&gt;Antes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plan
 ↓
Execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plan
 ↓
Execute
 ↓
Observe
 ↓
Adapt
 ↓
Continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O engine passa a responder à realidade encontrada durante o processamento.&lt;/p&gt;




&lt;h1&gt;
  
  
  33. AQE e Shuffle Partitions
&lt;/h1&gt;

&lt;p&gt;Imagine que planejamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 shuffle partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;mas depois de filtros o dataset ficou muito menor.&lt;/p&gt;

&lt;p&gt;Talvez várias dessas partições sejam pequenas demais.&lt;/p&gt;

&lt;p&gt;AQE pode ajudar a combinar partições após observar o tamanho real do shuffle.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 tiny partitions
        ↓
       AQE
        ↓
fewer, larger partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso pode reduzir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;número de Tasks;&lt;/li&gt;
&lt;li&gt;scheduling overhead;&lt;/li&gt;
&lt;li&gt;small partitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É exatamente o problema que discutimos na Parte 3, agora tratado de forma adaptativa.&lt;/p&gt;




&lt;h1&gt;
  
  
  34. AQE e Join Strategy
&lt;/h1&gt;

&lt;p&gt;Imagine que inicialmente Spark não consegue concluir que determinado dataset será pequeno.&lt;/p&gt;

&lt;p&gt;Mas depois de um filtro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Table B

Original: 50 GB
     ↓
Filter
     ↓
Runtime: 5 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora uma estratégia diferente de join pode fazer sentido.&lt;/p&gt;

&lt;p&gt;Em determinados cenários, AQE pode adaptar a estratégia física utilizando informações reais coletadas durante a execução.&lt;/p&gt;

&lt;p&gt;Isso conecta diretamente a Parte 5 com este artigo.&lt;/p&gt;




&lt;h1&gt;
  
  
  35. AQE e Skew
&lt;/h1&gt;

&lt;p&gt;Também vimos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition 1 → 200 MB
Partition 2 → 210 MB
Partition 3 → 15 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa partição enorme pode gerar uma Task extremamente lenta.&lt;/p&gt;

&lt;p&gt;AQE possui mecanismos que podem ajudar em determinados cenários de skew, dividindo trabalho de maneira mais adequada.&lt;/p&gt;

&lt;p&gt;Isso não significa que skew deixou de ser um problema.&lt;/p&gt;

&lt;p&gt;Significa que o engine moderno possui mais ferramentas para lidar com ele.&lt;/p&gt;




&lt;h1&gt;
  
  
  36. O Spark moderno é cada vez mais adaptativo
&lt;/h1&gt;

&lt;p&gt;Agora nossa visão evoluiu bastante.&lt;/p&gt;

&lt;p&gt;Começamos a série pensando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RDD
 ↓
Transformation
 ↓
Action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora estamos pensando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataFrame / SQL
       ↓
Logical Plan
       ↓
Catalyst
       ↓
Optimized Plan
       ↓
Physical Plan
       ↓
Execution
       ↓
Runtime Statistics
       ↓
AQE
       ↓
Adapted Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso mostra o quanto Spark evoluiu.&lt;/p&gt;




&lt;h1&gt;
  
  
  37. Um pipeline completo
&lt;/h1&gt;

&lt;p&gt;Vamos juntar vários conceitos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;spark_sum&lt;/span&gt;

&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spark-sql-example&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;vendas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/vendas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;estados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/estados&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;vendas_2026&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;col&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ano&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vendas_2026&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;estados&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;spark_sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;receita&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;formatted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora não quero enxergar apenas o código.&lt;/p&gt;

&lt;p&gt;Quero enxergar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PARQUET SCAN
      ↓
PARTITION / FILTER PRUNING
      ↓
COLUMN PRUNING
      ↓
REDUCED DATASET
      │
      │
      ├──────────────┐
      │              │
      ▼              ▼
LARGE SIDE      SMALL DIMENSION
      │              │
      │          BROADCAST
      │              │
      └───────┬──────┘
              ▼
      BROADCAST HASH JOIN
              ↓
      PARTIAL AGGREGATION
              ↓
           SHUFFLE
              ↓
       FINAL AGGREGATION
              ↓
            RESULT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é uma leitura muito mais próxima de Engenharia de Dados distribuída.&lt;/p&gt;




&lt;h1&gt;
  
  
  38. E o Spark UI?
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;explain()&lt;/code&gt; mostra o plano.&lt;/p&gt;

&lt;p&gt;Spark UI mostra o que aconteceu durante a execução.&lt;/p&gt;

&lt;p&gt;Essa distinção é muito útil.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;explain()
    ↓
O que Spark planejou?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark UI
    ↓
O que aconteceu?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combinando os dois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PLAN
  +
RUNTIME
  =
TROUBLESHOOTING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No plano podemos identificar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;joins;&lt;/li&gt;
&lt;li&gt;exchanges;&lt;/li&gt;
&lt;li&gt;scans;&lt;/li&gt;
&lt;li&gt;filters;&lt;/li&gt;
&lt;li&gt;aggregates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Spark UI podemos investigar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duração;&lt;/li&gt;
&lt;li&gt;Tasks;&lt;/li&gt;
&lt;li&gt;shuffle;&lt;/li&gt;
&lt;li&gt;spill;&lt;/li&gt;
&lt;li&gt;skew;&lt;/li&gt;
&lt;li&gt;memória;&lt;/li&gt;
&lt;li&gt;executors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essa combinação é extremamente poderosa.&lt;/p&gt;




&lt;h1&gt;
  
  
  39. Um método de troubleshooting
&lt;/h1&gt;

&lt;p&gt;Quando um pipeline DataFrame/SQL está lento, gosto deste raciocínio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PIPELINE LENTO
      ↓
explain()
      ↓
Qual plano físico?
      ↓
Existe Exchange?
      ↓
Qual Join?
      ↓
Qual Scan?
      ↓
Filtros foram aplicados?
      ↓
Colunas desnecessárias?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois vou para runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark UI
    ↓
Qual Stage está lento?
    ↓
Quanto Shuffle?
    ↓
Existe Spill?
    ↓
Existe Skew?
    ↓
Como estão as Tasks?
    ↓
Como estão os Executors?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E só depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Alterar configuração
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso evita o anti-pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job lento
   ↓
Google
   ↓
copiar spark.conf aleatória
   ↓
reiniciar
   ↓
torcer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  40. Configuração não substitui arquitetura
&lt;/h1&gt;

&lt;p&gt;Podemos encontrar centenas de parâmetros Spark.&lt;/p&gt;

&lt;p&gt;Mas muitas vezes o maior ganho vem de algo muito mais fundamental.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTES

Read 5 TB
   ↓
Join 5 TB
   ↓
Filter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEPOIS

Read
 ↓
Prune
 ↓
Filter
 ↓
500 GB
 ↓
Join
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTES

Large
  ↓
Shuffle
  ↓
Large
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEPOIS

Large
  +
Small Broadcast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nenhuma configuração de memória substitui completamente uma estratégia melhor.&lt;/p&gt;




&lt;h1&gt;
  
  
  41. Por que DataFrames mudaram tanto o Spark
&lt;/h1&gt;

&lt;p&gt;Agora podemos responder.&lt;/p&gt;

&lt;p&gt;Com código arbitrário sobre RDDs, Spark possui menos conhecimento sobre a semântica de determinadas operações.&lt;/p&gt;

&lt;p&gt;Com DataFrames e SQL, ele consegue trabalhar com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schema
Expressions
Columns
Predicates
Joins
Aggregations
Statistics
Logical Plans
Physical Plans
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso permite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Optimization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E quanto mais informação o engine possui, maior sua capacidade de escolher uma execução eficiente.&lt;/p&gt;

&lt;p&gt;Essa é uma lição que vai além do Spark:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Abstrações de alto nível podem gerar execução de baixo nível mais eficiente quando fornecem informação suficiente ao otimizador.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  42. RDDs ainda importam?
&lt;/h1&gt;

&lt;p&gt;Sim.&lt;/p&gt;

&lt;p&gt;Mesmo que grande parte do desenvolvimento moderno utilize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataFrames
Spark SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;entender RDDs continua sendo extremamente útil.&lt;/p&gt;

&lt;p&gt;Porque eles ensinam os fundamentos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partitions
Transformations
Actions
Lazy Evaluation
Lineage
Shuffle
Distributed Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem esses conceitos, podemos utilizar DataFrames sem entender por que determinada operação ficou lenta.&lt;/p&gt;

&lt;p&gt;A evolução não foi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RDD errado
   ↓
DataFrame certo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fundamentos distribuídos
        ↓
Abstrações estruturadas
        ↓
Mais oportunidades de otimização
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  43. O que mudou na minha forma de enxergar Spark
&lt;/h1&gt;

&lt;p&gt;No começo, eu via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hoje eu tento enxergar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataFrame Expression
       ↓
Logical Plan
       ↓
Catalyst
       ↓
Optimized Plan
       ↓
Physical Plan
       ↓
Partial Aggregation
       ↓
Exchange
       ↓
Shuffle
       ↓
Final Aggregation
       ↓
Tasks
       ↓
Executors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa mudança de perspectiva é uma das coisas mais importantes que aprender Spark me trouxe.&lt;/p&gt;




&lt;h1&gt;
  
  
  44. A principal lição deste capítulo
&lt;/h1&gt;

&lt;p&gt;Se eu tivesse que resumir Spark SQL em uma única ideia, seria:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quanto mais o Spark entende sobre a estrutura e a intenção da computação, mais oportunidades ele possui para otimizar sua execução.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É por isso que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;importa.&lt;/p&gt;

&lt;p&gt;É por isso que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataFrames
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;importam.&lt;/p&gt;

&lt;p&gt;É por isso que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Catalyst
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;importa.&lt;/p&gt;

&lt;p&gt;E é por isso que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;explain()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;deveria fazer parte da caixa de ferramentas de quem trabalha seriamente com Spark.&lt;/p&gt;

&lt;p&gt;Nosso modelo mental agora é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                SQL / DataFrame
                       │
                       ▼
                 Logical Plan
                       │
                       ▼
                    Catalyst
                       │
                       ▼
               Optimized Plan
                       │
                       ▼
                Physical Plan
                       │
                       ▼
              Efficient Execution
                       │
                       ▼
               Distributed Tasks
                       │
                       ▼
              Runtime Statistics
                       │
                       ▼
                      AQE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora já não estamos apenas executando transformações.&lt;/p&gt;

&lt;p&gt;Estamos trabalhando com um &lt;strong&gt;query engine distribuído e adaptativo&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Próximo artigo
&lt;/h1&gt;

&lt;p&gt;Existe um assunto que apareceu várias vezes nesta série:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Falamos sobre memória em:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Executors;&lt;/li&gt;
&lt;li&gt;Driver;&lt;/li&gt;
&lt;li&gt;cache;&lt;/li&gt;
&lt;li&gt;broadcast;&lt;/li&gt;
&lt;li&gt;shuffle;&lt;/li&gt;
&lt;li&gt;spill;&lt;/li&gt;
&lt;li&gt;partições;&lt;/li&gt;
&lt;li&gt;joins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mas ainda não abrimos essa caixa.&lt;/p&gt;

&lt;p&gt;No próximo artigo vamos entender:&lt;/p&gt;

&lt;h1&gt;
  
  
  Memória no Apache Spark
&lt;/h1&gt;

&lt;p&gt;Vamos explorar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memória do Driver;&lt;/li&gt;
&lt;li&gt;memória dos Executors;&lt;/li&gt;
&lt;li&gt;execution memory;&lt;/li&gt;
&lt;li&gt;storage memory;&lt;/li&gt;
&lt;li&gt;cache e persist;&lt;/li&gt;
&lt;li&gt;garbage collection;&lt;/li&gt;
&lt;li&gt;spill para disco;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spark.executor.memory&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;memory overhead;&lt;/li&gt;
&lt;li&gt;por que simplesmente aumentar RAM nem sempre resolve;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OutOfMemoryError&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;diferença entre problemas de Driver e Executor;&lt;/li&gt;
&lt;li&gt;como partições influenciam memória;&lt;/li&gt;
&lt;li&gt;e como diagnosticar problemas usando Spark UI e métricas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vamos conectar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partitions
     ↓
Tasks
     ↓
Memory
     ↓
Shuffle
     ↓
Spill
     ↓
Disk
     ↓
Performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É um tema que merece um capítulo próprio porque muitos problemas atribuídos ao "Spark lento" são, na verdade, problemas de &lt;strong&gt;distribuição, memória ou pressão causada pelo plano de execução&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.toCOLE_AQUI_A_URL_DA_PARTE_5"&gt;← Parte 5: Joins — Shuffle, Broadcast e estratégias de execução&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Próximo:&lt;/strong&gt; Parte 7 — Memória no Apache Spark: Executors, Driver, Cache, Spill e OutOfMemory&lt;/p&gt;

</description>
      <category>pyspark</category>
      <category>dataengineering</category>
      <category>bigdata</category>
      <category>cluster</category>
    </item>
    <item>
      <title>Como aprendi Apache Spark — Parte 5: Joins — Shuffle, Broadcast e estratégias de execução</title>
      <dc:creator>renanpyd</dc:creator>
      <pubDate>Sat, 24 Oct 2026 16:00:38 +0000</pubDate>
      <link>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-5-joins-shuffle-broadcast-e-estrategias-de-execucao-5b3</link>
      <guid>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-5-joins-shuffle-broadcast-e-estrategias-de-execucao-5b3</guid>
      <description>&lt;p&gt;No artigo anterior, vimos uma das operações mais importantes para entender performance em Apache Spark:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shuffle.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Chegamos a um princípio que vale para boa parte dos sistemas distribuídos:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mover dados entre máquinas custa caro.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Agora vamos aplicar esse princípio a uma operação presente em praticamente qualquer pipeline de Engenharia de Dados:&lt;/p&gt;

&lt;h1&gt;
  
  
  Join
&lt;/h1&gt;

&lt;p&gt;Em SQL, um join parece simples:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```sql id="fyy18i"&lt;br&gt;
SELECT *&lt;br&gt;
FROM pedidos p&lt;br&gt;
JOIN clientes c&lt;br&gt;
    ON p.customer_id = c.customer_id&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Em PySpark, também:



```python id="nhg3yr"
resultado = pedidos.join(
    clientes,
    "customer_id"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Mas existe uma pergunta muito mais interessante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Como esse join será executado fisicamente em um cluster distribuído?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa pergunta muda tudo.&lt;/p&gt;

&lt;p&gt;Dependendo do tamanho das tabelas, distribuição das chaves, estatísticas disponíveis e estratégia escolhida pelo Spark, duas operações &lt;code&gt;join()&lt;/code&gt; visualmente semelhantes podem produzir comportamentos completamente diferentes.&lt;/p&gt;

&lt;p&gt;Podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="7lczd6"&lt;br&gt;
Shuffle&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


ou:



```text id="2xg2di"
Broadcast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Podemos encontrar:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="2a8l5m"&lt;br&gt;
SortMergeJoin&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


ou:



```text id="uk2e5x"
BroadcastHashJoin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;E podemos transformar um join aparentemente simples em um dos maiores gargalos do pipeline.&lt;/p&gt;

&lt;p&gt;Vamos entender o que realmente acontece.&lt;/p&gt;


&lt;h1&gt;
  
  
  1. Por que Join é diferente em um sistema distribuído?
&lt;/h1&gt;

&lt;p&gt;Imagine duas tabelas.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pedidos
&lt;/h2&gt;



&lt;p&gt;```text id="0q1h4k"&lt;/p&gt;
&lt;h2&gt;
  
  
  order_id | customer_id | value
&lt;/h2&gt;

&lt;p&gt;1001     | 10          | 250&lt;br&gt;
1002     | 20          | 100&lt;br&gt;
1003     | 10          | 500&lt;br&gt;
1004     | 30          | 150&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


## Clientes



```text id="nqdk1c"
customer_id | name
-------------------
10          | Ana
20          | Carlos
30          | Maria
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Em um banco tradicional, podemos imaginar os dados disponíveis para o mecanismo responsável pela consulta.&lt;/p&gt;

&lt;p&gt;Mas em Spark, &lt;code&gt;pedidos&lt;/code&gt; pode estar distribuído assim:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="02nkpq"&lt;br&gt;
Executor A&lt;br&gt;
├── customer 10&lt;br&gt;
├── customer 20&lt;br&gt;
└── customer 10&lt;/p&gt;

&lt;p&gt;Executor B&lt;br&gt;
├── customer 30&lt;br&gt;
├── customer 50&lt;br&gt;
└── customer 70&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Enquanto `clientes` pode estar:



```text id="hnq8xk"
Executor A
├── customer 30
└── customer 40

Executor B
├── customer 10
└── customer 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Para realizar:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="ffty06"&lt;br&gt;
pedidos.join(&lt;br&gt;
    clientes,&lt;br&gt;
    "customer_id"&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


o Spark precisa encontrar uma maneira de colocar registros compatíveis em condições de serem comparados.

E é aí que começa a estratégia de execução.

---

# 2. A pergunta fundamental de um Join distribuído

Para juntar:



```text id="8i71ne"
pedido.customer_id = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;com:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="lhv1yi"&lt;br&gt;
cliente.customer_id = 10&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


esses registros precisam estar disponíveis para a mesma operação de join.

Uma possibilidade seria redistribuir os dois datasets pela chave:



```text id="g7e79d"
PEDIDOS                      CLIENTES

P1 ────────┐              ┌──────── P1
P2 ────────┼──► SHUFFLE ◄─┼──────── P2
P3 ────────┘              └──────── P3

                  ↓

        Partition customer hash 1
        Partition customer hash 2
        Partition customer hash 3

                  ↓

                 JOIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Agora registros com chaves compatíveis podem chegar à mesma partição.&lt;/p&gt;

&lt;p&gt;Funciona.&lt;/p&gt;

&lt;p&gt;Mas talvez seja caro.&lt;/p&gt;

&lt;p&gt;Se temos:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="49jbyc"&lt;br&gt;
Pedidos  = 2 TB&lt;br&gt;
Clientes = 500 GB&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


podemos acabar movimentando volumes enormes pela rede.

Por isso Spark possui diferentes estratégias.

---

# 3. O plano lógico não é o plano físico

Esse é um conceito muito importante.

Quando escrevemos:



```python id="tvlc3g"
resultado = pedidos.join(
    clientes,
    "customer_id"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;estamos descrevendo &lt;strong&gt;o que queremos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Não necessariamente &lt;strong&gt;como executar&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Podemos pensar em camadas:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="ukjxlk"&lt;br&gt;
Código&lt;br&gt;
   ↓&lt;br&gt;
Logical Plan&lt;br&gt;
   ↓&lt;br&gt;
Optimized Logical Plan&lt;br&gt;
   ↓&lt;br&gt;
Physical Plan&lt;br&gt;
   ↓&lt;br&gt;
Execution&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Essa separação é poderosa.

Nós declaramos:

&amp;gt; Junte pedidos e clientes por `customer_id`.

O Spark pode analisar diferentes maneiras de executar essa operação.

---

# 4. Catalyst Optimizer

Uma peça fundamental dessa arquitetura é o:

# Catalyst Optimizer

O Catalyst é o framework de otimização utilizado pelo Spark SQL.

De maneira simplificada, podemos pensar:



```text id="61pwwd"
SQL / DataFrame API
        ↓
   Logical Plan
        ↓
   Analysis
        ↓
Optimization Rules
        ↓
Optimized Logical Plan
        ↓
Physical Planning
        ↓
Physical Plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Isso significa que escrever:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="o8z96c"&lt;br&gt;
df.filter("value &amp;gt; 100")&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


ou uma expressão equivalente em SQL não implica simplesmente executar cada chamada literalmente na ordem em que aparece no código.

Spark constrói e otimiza um plano.

Esse modelo é uma das razões pelas quais DataFrames e Spark SQL se tornaram abstrações tão importantes.

---

# 5. Como enxergar o plano: `explain()`

Uma das ferramentas mais importantes para entender Spark é:



```python id="c0xpn1"
df.explain()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="7i1e8z"&lt;br&gt;
resultado = pedidos.join(&lt;br&gt;
    clientes,&lt;br&gt;
    "customer_id"&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;resultado.explain()&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Também podemos pedir mais detalhes:



```python id="95nrdw"
resultado.explain("formatted")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="m3pspj"&lt;br&gt;
resultado.explain("extended")&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Dependendo do plano, podemos encontrar termos como:



```text id="2o21jc"
BroadcastHashJoin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="5o65zv"&lt;br&gt;
SortMergeJoin&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Além de operadores como:



```text id="7aoxfm"
Exchange
HashAggregate
Filter
Project
Scan
Sort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A partir desse momento, &lt;code&gt;explain()&lt;/code&gt; deixa de ser apenas uma ferramenta de curiosidade.&lt;/p&gt;

&lt;p&gt;Ele passa a responder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;O que o Spark pretende fazer com meu código?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  6. Primeiro cenário: duas tabelas grandes
&lt;/h1&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="zh5drk"&lt;br&gt;
pedidos  = 2 TB&lt;br&gt;
clientes = 800 GB&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Nenhuma das duas é pequena.

Distribuí-las integralmente para cada Executor seria inviável.

Uma estratégia possível envolve reorganizar os dois lados pela chave de join.

Conceitualmente:



```text id="c03bti"
PEDIDOS
   │
   ▼
Shuffle by customer_id
   │
   └──────────────┐
                  │
                  ▼
                 JOIN
                  ▲
                  │
   ┌──────────────┘
   │
Shuffle by customer_id
   ▲
   │
CLIENTES
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Esse tipo de operação pode movimentar enormes quantidades de dados.&lt;/p&gt;

&lt;p&gt;É aqui que encontramos estratégias como o:&lt;/p&gt;
&lt;h1&gt;
  
  
  Sort Merge Join
&lt;/h1&gt;


&lt;h1&gt;
  
  
  7. Sort Merge Join
&lt;/h1&gt;

&lt;p&gt;O &lt;strong&gt;Sort Merge Join&lt;/strong&gt; é uma estratégia importante para joins entre datasets grandes quando as condições permitem sua utilização.&lt;/p&gt;

&lt;p&gt;Uma visão simplificada:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="1ru96w"&lt;br&gt;
TABLE A&lt;br&gt;
   │&lt;br&gt;
Shuffle&lt;br&gt;
   │&lt;br&gt;
Sort&lt;br&gt;
   │&lt;br&gt;
   └──────────┐&lt;br&gt;
              ▼&lt;br&gt;
            Merge&lt;br&gt;
              ▲&lt;br&gt;
   ┌──────────┘&lt;br&gt;
   │&lt;br&gt;
Sort&lt;br&gt;
   │&lt;br&gt;
Shuffle&lt;br&gt;
   │&lt;br&gt;
TABLE B&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Os dados são organizados de forma compatível pela chave de join e posteriormente combinados.

Imagine:



```text id="kl6s69"
TABLE A

1
1
2
3
5
7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="h8y3ik"&lt;br&gt;
TABLE B&lt;/p&gt;

&lt;p&gt;1&lt;br&gt;
2&lt;br&gt;
2&lt;br&gt;
3&lt;br&gt;
6&lt;br&gt;
7&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Como ambos os lados estão ordenados pela chave, o algoritmo pode avançar pelas estruturas realizando o merge.

Em grandes datasets distribuídos, essa estratégia pode ser muito mais apropriada do que tentar colocar uma das tabelas inteira em memória em todos os Executors.

Mas existe um custo importante:



```text id="dky8x5"
Shuffle
+
Sort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Portanto, novamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O join não é apenas uma comparação entre duas colunas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Existe toda uma arquitetura distribuída por trás dele.&lt;/p&gt;


&lt;h1&gt;
  
  
  8. Segundo cenário: uma tabela enorme e outra pequena
&lt;/h1&gt;

&lt;p&gt;Agora imagine:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="gl6khe"&lt;br&gt;
EVENTOS&lt;br&gt;
2 TB&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


e:



```text id="vvz0t7"
PAISES
5 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Queremos:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="i5ukm8"&lt;br&gt;
eventos.join(&lt;br&gt;
    paises,&lt;br&gt;
    "country_id"&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Será que faz sentido redistribuir:



```text id="lggczd"
2 TB
+
5 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;pela rede?&lt;/p&gt;

&lt;p&gt;Talvez exista uma estratégia muito melhor.&lt;/p&gt;

&lt;p&gt;Em vez de reorganizar a tabela gigante, podemos enviar a tabela pequena para os Executors.&lt;/p&gt;

&lt;p&gt;É aqui que aparece:&lt;/p&gt;
&lt;h1&gt;
  
  
  Broadcast Join
&lt;/h1&gt;


&lt;h1&gt;
  
  
  9. Broadcast Hash Join
&lt;/h1&gt;

&lt;p&gt;A ideia é simples e extremamente poderosa.&lt;/p&gt;

&lt;p&gt;Temos:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="d30q2m"&lt;br&gt;
                SMALL TABLE&lt;br&gt;
                    │&lt;br&gt;
          ┌─────────┼─────────┐&lt;br&gt;
          ▼         ▼         ▼&lt;br&gt;
      Executor 1 Executor 2 Executor 3&lt;br&gt;
          │         │         │&lt;br&gt;
          ▼         ▼         ▼&lt;br&gt;
       local      local      local&lt;br&gt;
        join       join       join&lt;br&gt;
          ▲         ▲         ▲&lt;br&gt;
          │         │         │&lt;br&gt;
        Large Dataset Partitions&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Em vez de movimentar a tabela grande para reorganizá-la completamente, distribuímos uma cópia da tabela pequena para os Executors.

Agora cada Executor consegue realizar o join localmente sobre suas partições da tabela grande.

Conceitualmente:



```text id="u11cf9"
Large Table

P1 ───► Executor 1 + Small Table
P2 ───► Executor 2 + Small Table
P3 ───► Executor 3 + Small Table
P4 ───► Executor 4 + Small Table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Isso pode evitar um grande shuffle do lado maior.&lt;/p&gt;


&lt;h1&gt;
  
  
  10. Por que Broadcast pode ser tão rápido?
&lt;/h1&gt;

&lt;p&gt;Compare dois cenários.&lt;/p&gt;
&lt;h2&gt;
  
  
  Shuffle Join
&lt;/h2&gt;



&lt;p&gt;```text id="9rmkqr"&lt;br&gt;
Large Table A&lt;br&gt;
      │&lt;br&gt;
      ▼&lt;br&gt;
   SHUFFLE&lt;br&gt;
      │&lt;br&gt;
      └──────────┐&lt;br&gt;
                 ▼&lt;br&gt;
                JOIN&lt;br&gt;
                 ▲&lt;br&gt;
      ┌──────────┘&lt;br&gt;
      │&lt;br&gt;
   SHUFFLE&lt;br&gt;
      ▲&lt;br&gt;
      │&lt;br&gt;
Large Table B&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Agora:

## Broadcast Join



```text id="v6c9kp"
Small Table
     │
 Broadcast
 ┌───┼───┐
 ▼   ▼   ▼

E1  E2  E3
▲   ▲   ▲
│   │   │
Large Table partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Não precisamos reorganizar toda a tabela grande apenas para executar o join.&lt;/p&gt;

&lt;p&gt;Se a diferença de tamanho for significativa, o ganho pode ser enorme.&lt;/p&gt;


&lt;h1&gt;
  
  
  11. Fazendo Broadcast explicitamente
&lt;/h1&gt;

&lt;p&gt;Em PySpark podemos indicar:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="h83p3u"&lt;br&gt;
from pyspark.sql.functions import broadcast&lt;/p&gt;

&lt;p&gt;resultado = pedidos.join(&lt;br&gt;
    broadcast(clientes),&lt;br&gt;
    "customer_id"&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Isso informa ao Spark que queremos tratar `clientes` como lado broadcast.

Depois podemos verificar:



```python id="f1ks36"
resultado.explain("formatted")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;E procurar no plano físico algo relacionado a:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="09wcti"&lt;br&gt;
BroadcastHashJoin&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Mas existe uma regra importante:

&amp;gt; **Não force broadcast apenas porque parece mais rápido.**

Precisamos entender o tamanho real dos dados.

---

# 12. Broadcast usa memória

Se transmitimos uma tabela para os Executors, cada Executor precisa conseguir utilizá-la.

Imagine:



```text id="hssq4n"
Tabela broadcast = 5 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Provavelmente temos um cenário muito diferente de:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="bwy8p7"&lt;br&gt;
Tabela broadcast = 8 GB&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Agora imagine dezenas de Executors.

O conceito de:



```text id="du4bcu"
"tabela pequena"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;é relativo ao ambiente.&lt;/p&gt;

&lt;p&gt;Precisamos considerar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memória disponível;&lt;/li&gt;
&lt;li&gt;representação dos dados;&lt;/li&gt;
&lt;li&gt;concorrência;&lt;/li&gt;
&lt;li&gt;número de Executors;&lt;/li&gt;
&lt;li&gt;tamanho real após materialização;&lt;/li&gt;
&lt;li&gt;workload executado simultaneamente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Portanto:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="5a7hly"&lt;br&gt;
Broadcast&lt;br&gt;
   ≠&lt;br&gt;
mágica&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Ele troca um tipo de custo por outro.

---

# 13. Broadcast automático

Spark pode escolher automaticamente estratégias de broadcast quando possui informações suficientes e o dataset atende às condições necessárias.

Conceitualmente:



```text id="dkx76e"
Join
 │
 ├── Table A: grande
 │
 └── Table B: pequena
          │
          ▼
      Optimizer
          │
          ▼
BroadcastHashJoin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Isso é importante porque mostra novamente a diferença entre:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="0il50n"&lt;br&gt;
código que escrevemos&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


e:



```text id="3ukcrf"
plano que Spark executa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;O otimizador pode tomar decisões baseado em estatísticas e configurações.&lt;/p&gt;

&lt;p&gt;Mas isso nos leva a outro ponto.&lt;/p&gt;


&lt;h1&gt;
  
  
  14. Estatísticas importam
&lt;/h1&gt;

&lt;p&gt;Como o otimizador pode escolher uma estratégia se não conhece os dados?&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="wql0qb"&lt;br&gt;
Table A&lt;br&gt;
size = ?&lt;/p&gt;

&lt;p&gt;Table B&lt;br&gt;
size = ?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Sem informações adequadas, algumas decisões ficam mais difíceis.

Agora:



```text id="hs72hc"
Table A
≈ 2 TB

Table B
≈ 8 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Existe uma informação extremamente relevante para planejamento.&lt;/p&gt;

&lt;p&gt;Por isso estatísticas e metadados são importantes em engines modernas.&lt;/p&gt;

&lt;p&gt;O otimizador precisa responder perguntas como:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="0sgh2p"&lt;br&gt;
Qual dataset é menor?&lt;/p&gt;

&lt;p&gt;Quantas linhas existem?&lt;/p&gt;

&lt;p&gt;Qual a cardinalidade?&lt;/p&gt;

&lt;p&gt;Como os dados estão distribuídos?&lt;/p&gt;

&lt;p&gt;Existe uma estratégia mais barata?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Essa é uma diferença importante entre simplesmente executar código e trabalhar com um **query optimizer**.

---

# 15. O que significa `Exchange` no plano?

Ao analisar:



```python id="dflp99"
df.explain("formatted")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;podemos encontrar operadores chamados:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="dkf3oh"&lt;br&gt;
Exchange&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Esse termo merece atenção.

Em muitos planos, `Exchange` indica que Spark precisa redistribuir dados.

Em outras palavras:



```text id="2zdpkx"
Exchange
    ↓
possível movimentação
    ↓
Shuffle / repartitioning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Por exemplo, podemos encontrar algo conceitualmente semelhante a:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xykwpj"&lt;br&gt;
SortMergeJoin&lt;br&gt;
:- Sort&lt;br&gt;
:  +- Exchange hashpartitioning(customer_id)&lt;br&gt;
:&lt;br&gt;
+- Sort&lt;br&gt;
   +- Exchange hashpartitioning(customer_id)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Isso já conta uma história:



```text id="x7lf6m"
Left
 ↓
Exchange
 ↓
Sort
 ┐
 ├── Join
 ┘
Sort
 ↑
Exchange
 ↑
Right
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Agora conseguimos enxergar onde existe movimentação de dados.&lt;/p&gt;


&lt;h1&gt;
  
  
  16. Compare dois planos
&lt;/h1&gt;

&lt;p&gt;Imagine o primeiro:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="65uh56"&lt;br&gt;
SortMergeJoin&lt;br&gt;
├── Exchange&lt;br&gt;
│   └── Table A&lt;br&gt;
│&lt;br&gt;
└── Exchange&lt;br&gt;
    └── Table B&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Temos movimentação dos dois lados.

Agora outro:



```text id="edct6z"
BroadcastHashJoin
├── Large Table
│
└── BroadcastExchange
    └── Small Table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A história mudou.&lt;/p&gt;

&lt;p&gt;Em vez de redistribuir toda a tabela grande, Spark transmite a menor.&lt;/p&gt;

&lt;p&gt;Só de olhar o plano físico já começamos a formular hipóteses de performance.&lt;/p&gt;


&lt;h1&gt;
  
  
  17. Nem todo Join é igualdade
&lt;/h1&gt;

&lt;p&gt;Até agora usamos:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="f32oqr"&lt;br&gt;
df1.join(&lt;br&gt;
    df2,&lt;br&gt;
    "customer_id"&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


que representa um join por igualdade.

Mas podemos ter condições diferentes:



```python id="0ad0tq"
df1.join(
    df2,
    df1.valor &amp;gt; df2.limite
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A estratégia física disponível depende também da condição do join.&lt;/p&gt;

&lt;p&gt;Algoritmos baseados em hash, por exemplo, funcionam particularmente bem para determinados joins de igualdade.&lt;/p&gt;

&lt;p&gt;Isso significa que:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O tipo da condição também influencia as estratégias que o Spark pode considerar.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  18. Tipos de Join continuam importantes
&lt;/h1&gt;

&lt;p&gt;Do ponto de vista lógico, continuamos tendo:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="p9z0z4"&lt;br&gt;
INNER&lt;br&gt;
LEFT&lt;br&gt;
RIGHT&lt;br&gt;
FULL&lt;br&gt;
LEFT SEMI&lt;br&gt;
LEFT ANTI&lt;br&gt;
CROSS&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Por exemplo:



```python id="tw5sfr"
pedidos.join(
    clientes,
    "customer_id",
    "left"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Mas existe uma distinção importante:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="7ujx51"&lt;br&gt;
TIPO LÓGICO&lt;/p&gt;

&lt;p&gt;INNER&lt;br&gt;
LEFT&lt;br&gt;
RIGHT&lt;br&gt;
...&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


não é a mesma coisa que:



```text id="ml4t0r"
ESTRATÉGIA FÍSICA

BroadcastHashJoin
SortMergeJoin
ShuffleHashJoin
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Uma coisa responde:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Qual resultado quero?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A outra:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Como Spark vai produzi-lo?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa distinção é fundamental.&lt;/p&gt;


&lt;h1&gt;
  
  
  19. LEFT SEMI JOIN: um recurso muito útil
&lt;/h1&gt;

&lt;p&gt;Imagine que queremos apenas pedidos de clientes ativos.&lt;/p&gt;

&lt;p&gt;Podemos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="p02s5c"&lt;br&gt;
pedidos.join(&lt;br&gt;
    clientes_ativos,&lt;br&gt;
    "customer_id",&lt;br&gt;
    "left_semi"&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


O resultado contém registros do lado esquerdo que possuem correspondência no lado direito.

Não precisamos trazer todas as colunas de `clientes_ativos`.

Conceitualmente:



```text id="zt5quf"
PEDIDOS

1
2
3
4
5

CLIENTES ATIVOS

1
3
5

RESULTADO

1
3
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Isso pode expressar melhor a intenção quando queremos apenas verificar existência.&lt;/p&gt;


&lt;h1&gt;
  
  
  20. LEFT ANTI JOIN
&lt;/h1&gt;

&lt;p&gt;Agora imagine que queremos encontrar pedidos cujo cliente não existe em determinada referência.&lt;/p&gt;

&lt;p&gt;Podemos utilizar:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="ic8f00"&lt;br&gt;
pedidos.join(&lt;br&gt;
    clientes,&lt;br&gt;
    "customer_id",&lt;br&gt;
    "left_anti"&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Conceitualmente:



```text id="c3fl7n"
PEDIDOS

1
2
3
4
5

CLIENTES

1
2
4

LEFT ANTI

3
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Esse tipo de operação é muito útil em:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;validação;&lt;/li&gt;
&lt;li&gt;qualidade de dados;&lt;/li&gt;
&lt;li&gt;reconciliação;&lt;/li&gt;
&lt;li&gt;identificação de registros órfãos;&lt;/li&gt;
&lt;li&gt;pipelines de ingestão.&lt;/li&gt;
&lt;/ul&gt;


&lt;h1&gt;
  
  
  21. O inimigo volta: Data Skew
&lt;/h1&gt;

&lt;p&gt;Agora imagine um join por:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="hts9yz"&lt;br&gt;
customer_id&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


com esta distribuição:



```text id="qsz9qk"
customer_id = 1     → 500 milhões
customer_id = 2     → 1 milhão
customer_id = 3     → 900 mil
customer_id = 4     → 800 mil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Depois de particionar pela chave:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="xqovnp"&lt;br&gt;
Partition customer=1&lt;/p&gt;

&lt;p&gt;████████████████████████████████████████&lt;/p&gt;

&lt;p&gt;Outras partitions&lt;/p&gt;

&lt;p&gt;█&lt;br&gt;
█&lt;br&gt;
█&lt;br&gt;
█&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Temos novamente:



```text id="c5tjlk"
Task 1 → 30 minutos
Task 2 → 25 segundos
Task 3 → 24 segundos
Task 4 → 27 segundos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;O cluster inteiro pode ficar esperando a Task associada à chave dominante.&lt;/p&gt;

&lt;p&gt;Joins e skew são uma combinação especialmente perigosa.&lt;/p&gt;


&lt;h1&gt;
  
  
  22. Um caso clássico: valores &lt;code&gt;NULL&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Existe outro detalhe que merece investigação em datasets reais.&lt;/p&gt;

&lt;p&gt;Imagine milhões de registros com:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="8atwub"&lt;br&gt;
customer_id = NULL&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Dependendo da operação, semântica e tratamento aplicado, valores ausentes podem gerar comportamento indesejado ou simplesmente representar dados que nem deveriam participar daquele processamento.

Talvez possamos filtrar previamente:



```python id="p7oxb5"
pedidos_validos = pedidos.filter(
    "customer_id IS NOT NULL"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;se isso fizer sentido para a regra de negócio.&lt;/p&gt;

&lt;p&gt;A regra principal continua:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Não mova dados que não precisam participar da operação.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  23. Salting: uma estratégia contra Skew
&lt;/h1&gt;

&lt;p&gt;Em alguns cenários de skew severo, podemos utilizar uma técnica conhecida como:&lt;/p&gt;
&lt;h1&gt;
  
  
  Salting
&lt;/h1&gt;

&lt;p&gt;Imagine uma chave dominante:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="2m69vg"&lt;br&gt;
customer_id = 123&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


responsável por uma quantidade enorme de registros.

Em vez de enviar tudo para uma única chave física:



```text id="i15k8p"
123
123
123
123
123
123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;podemos criar variações:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="8t2gzf"&lt;br&gt;
123_0&lt;br&gt;
123_1&lt;br&gt;
123_2&lt;br&gt;
123_3&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


distribuindo parte do trabalho.

Conceitualmente:



```text id="qtdc9k"
ANTES

123 ███████████████████████████████


DEPOIS

123_0 ███████
123_1 ███████
123_2 ███████
123_3 ███████
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Depois precisamos tratar corretamente o outro lado do join e eventualmente recombinar resultados.&lt;/p&gt;

&lt;p&gt;É uma técnica poderosa.&lt;/p&gt;

&lt;p&gt;Mas aumenta complexidade.&lt;/p&gt;

&lt;p&gt;Portanto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Salting é uma ferramenta para um problema específico, não uma configuração padrão.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  24. AQE e Skew Join
&lt;/h1&gt;

&lt;p&gt;Aqui o Spark moderno oferece uma evolução importante.&lt;/p&gt;

&lt;p&gt;Com:&lt;/p&gt;
&lt;h1&gt;
  
  
  Adaptive Query Execution
&lt;/h1&gt;

&lt;p&gt;o Spark pode observar estatísticas durante a execução e adaptar determinadas decisões.&lt;/p&gt;

&lt;p&gt;Isso inclui mecanismos relacionados a skew em determinados tipos de join.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="cb1d88"&lt;br&gt;
Plano inicial&lt;br&gt;
     ↓&lt;br&gt;
Shuffle acontece&lt;br&gt;
     ↓&lt;br&gt;
Spark observa tamanho real&lt;br&gt;
     ↓&lt;br&gt;
Detecta partition muito grande&lt;br&gt;
     ↓&lt;br&gt;
AQE&lt;br&gt;
     ↓&lt;br&gt;
Adapta execução&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Isso pode reduzir a necessidade de algumas otimizações manuais.

Mas novamente:

&amp;gt; O AQE ajuda muito, mas não transforma modelagem ruim em arquitetura boa.

Precisamos continuar entendendo nossos dados.

---

# 25. Join Order importa

Imagine três tabelas:



```text id="fw9hwh"
A = 2 TB
B = 500 GB
C = 5 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Queremos:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="97eblm"&lt;br&gt;
A JOIN B JOIN C&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Existem diferentes maneiras de executar essas operações.

Por exemplo:



```text id="28grjw"
(A JOIN B) JOIN C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;ou determinadas reorganizações equivalentes permitidas pela semântica da consulta.&lt;/p&gt;

&lt;p&gt;O custo pode variar muito dependendo de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;seletividade;&lt;/li&gt;
&lt;li&gt;cardinalidade;&lt;/li&gt;
&lt;li&gt;filtros;&lt;/li&gt;
&lt;li&gt;tamanho intermediário;&lt;/li&gt;
&lt;li&gt;estratégias disponíveis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Engines com otimização baseada em custo podem usar estatísticas para avaliar alternativas.&lt;/p&gt;

&lt;p&gt;Esse é outro motivo pelo qual metadados e estatísticas são tão importantes.&lt;/p&gt;


&lt;h1&gt;
  
  
  26. Filter Before Join
&lt;/h1&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="27f3fk"&lt;br&gt;
pedidos = 2 TB&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Mas só precisamos:



```text id="0v4owq"
ano = 2026
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Se isso representa apenas:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="dw31bq"&lt;br&gt;
200 GB&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


é muito melhor reduzir o dataset antes da operação cara.

Por exemplo:



```python id="b6qvxw"
pedidos_2026 = pedidos.filter(
    "ano = 2026"
)

resultado = pedidos_2026.join(
    clientes,
    "customer_id"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="mdpt6m"&lt;br&gt;
2 TB&lt;br&gt;
 ↓&lt;br&gt;
FILTER&lt;br&gt;
 ↓&lt;br&gt;
200 GB&lt;br&gt;
 ↓&lt;br&gt;
JOIN&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


em vez de:



```text id="fckdd6"
2 TB
 ↓
JOIN
 ↓
FILTER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;O otimizador pode aplicar determinadas otimizações automaticamente, mas o princípio continua importante para nosso raciocínio.&lt;/p&gt;


&lt;h1&gt;
  
  
  27. Select Before Join
&lt;/h1&gt;

&lt;p&gt;Imagine que &lt;code&gt;pedidos&lt;/code&gt; possui:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="krvza0"&lt;br&gt;
150 colunas&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


mas precisamos apenas:



```text id="b7b6f4"
order_id
customer_id
value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Podemos trabalhar explicitamente com:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```python id="mru7mx"&lt;br&gt;
pedidos_reduzidos = pedidos.select(&lt;br&gt;
    "order_id",&lt;br&gt;
    "customer_id",&lt;br&gt;
    "value"&lt;br&gt;
)&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


e depois:



```python id="0sbrt3"
resultado = pedidos_reduzidos.join(
    clientes,
    "customer_id"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Novamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Evite carregar e movimentar dados que não serão utilizados.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Esse princípio simples aparece repetidamente porque funciona.&lt;/p&gt;


&lt;h1&gt;
  
  
  28. Evite duplicação acidental
&lt;/h1&gt;

&lt;p&gt;Joins também podem causar um problema lógico extremamente sério.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="1odv0g"&lt;br&gt;
LEFT&lt;/p&gt;

&lt;p&gt;customer_id=10&lt;br&gt;
1 registro&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


e:



```text id="3k0a4h"
RIGHT

customer_id=10
5 registros
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Depois do join:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="8a3nkr"&lt;br&gt;
1 × 5 = 5 registros&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Agora imagine:



```text id="xclds1"
LEFT

customer_id=10
100 registros
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="1ivxyy"&lt;br&gt;
RIGHT&lt;/p&gt;

&lt;p&gt;customer_id=10&lt;br&gt;
100 registros&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Dependendo da semântica do join:



```text id="8fn6kn"
100 × 100
=
10.000 combinações
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Isso pode gerar explosão de cardinalidade.&lt;/p&gt;

&lt;p&gt;Às vezes o problema atribuído ao Spark não é Spark.&lt;/p&gt;

&lt;p&gt;É o modelo de dados.&lt;/p&gt;


&lt;h1&gt;
  
  
  29. Sempre conheça a cardinalidade da chave
&lt;/h1&gt;

&lt;p&gt;Antes de um join importante, gosto de pensar:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="7gg0hc"&lt;br&gt;
A chave é única no lado direito?&lt;/p&gt;

&lt;p&gt;É única no lado esquerdo?&lt;/p&gt;

&lt;p&gt;É 1:1?&lt;/p&gt;

&lt;p&gt;1:N?&lt;/p&gt;

&lt;p&gt;N:N?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Compare:



```text id="yqokny"
1 : 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;com:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="o01zdg"&lt;br&gt;
N : N&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


O segundo pode multiplicar drasticamente a quantidade de registros.

Essa pergunta é tanto de performance quanto de qualidade de dados.

---

# 30. Um Join pode produzir mais dados do que recebe

Imagine:



```text id="2ud5v5"
Table A
100 milhões de rows

Table B
50 milhões de rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Não podemos assumir que o resultado terá:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="vz5qmu"&lt;br&gt;
≤ 150 milhões&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Um many-to-many pode produzir muito mais.

Por exemplo:



```text id="5xcm4h"
Key X

A → 10.000 rows
B → 20.000 rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Apenas essa chave pode produzir:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="dhg7jk"&lt;br&gt;
10.000 × 20.000&lt;/p&gt;

&lt;p&gt;= 200.000.000 combinações&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Esse tipo de explosão pode destruir um pipeline.

Por isso conhecer os dados é tão importante quanto conhecer a API.

---

# 31. Cache antes do Join?

Outra otimização aplicada muitas vezes automaticamente por desenvolvedores é:



```python id="yutl80"
df.cache()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;antes de um join.&lt;/p&gt;

&lt;p&gt;Mas cache não elimina automaticamente:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="3i9qbt"&lt;br&gt;
Shuffle&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


nem:



```text id="tcnm7h"
Skew
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;nem:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="i89a3b"&lt;br&gt;
Join explosion&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Cache pode ser extremamente útil quando um dataset caro será reutilizado.

Mas:



```text id="4b2vge"
cache()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;não significa:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="l0fp1u"&lt;br&gt;
makeEverythingFast()&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Ele também consome recursos.

Como sempre:

&amp;gt; Precisamos saber qual problema estamos tentando resolver.

---

# 32. Um workflow de investigação

Se um join estiver lento, eu começaria assim:



```text id="sff5zz"
JOIN LENTO
    ↓
explain()
    ↓
Qual estratégia física?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="g51bzq"&lt;br&gt;
SortMergeJoin?&lt;br&gt;
BroadcastHashJoin?&lt;br&gt;
Outra estratégia?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Então:



```text id="ahzcy6"
Existe Exchange?
      ↓
Quanto Shuffle?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="d8ceg9"&lt;br&gt;
As partitions estão equilibradas?&lt;br&gt;
      ↓&lt;br&gt;
Existe skew?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Depois:



```text id="1fzt0n"
Qual tamanho dos datasets?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;E:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="9ccetm"&lt;br&gt;
Qual cardinalidade da chave?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Só depois começaria a alterar configurações.

---

# 33. Um exemplo completo

Imagine:



```python id="9vhlm2"
from pyspark.sql import SparkSession
from pyspark.sql.functions import broadcast

spark = (
    SparkSession.builder
    .appName("join-example")
    .getOrCreate()
)

pedidos = spark.read.parquet(
    "/data/pedidos"
)

clientes = spark.read.parquet(
    "/data/clientes"
)

pedidos_filtrados = (
    pedidos
    .filter("ano = 2026")
    .select(
        "order_id",
        "customer_id",
        "value"
    )
)

resultado = pedidos_filtrados.join(
    broadcast(clientes),
    "customer_id",
    "left"
)

resultado.explain("formatted")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Não quero que a leitura desse código seja apenas:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="phoyxp"&lt;br&gt;
read&lt;br&gt;
filter&lt;br&gt;
select&lt;br&gt;
join&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Quero que a leitura mental seja:



```text id="s3ldgr"
READ PEDIDOS
      ↓
Predicate / Filter
      ↓
Projection
      ↓
Menos dados
      ↓
Large side
      │
      ├───────────────┐
      │               │
      │               ▼
      │       BroadcastHashJoin
      │               ▲
      │               │
      └───── Small CLIENTES
                  ↓
              Broadcast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Essa é uma forma muito mais poderosa de pensar Spark.&lt;/p&gt;


&lt;h1&gt;
  
  
  34. O que eu procuro no &lt;code&gt;explain()&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Quando analiso um join, quero responder:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="35nhu6"&lt;br&gt;
Qual operador físico foi escolhido?&lt;/p&gt;

&lt;p&gt;Existe Broadcast?&lt;/p&gt;

&lt;p&gt;Existe Exchange?&lt;/p&gt;

&lt;p&gt;Existe Sort?&lt;/p&gt;

&lt;p&gt;Quantos Shuffles aparecem?&lt;/p&gt;

&lt;p&gt;Existem filtros antes do Join?&lt;/p&gt;

&lt;p&gt;As projeções foram aplicadas?&lt;/p&gt;

&lt;p&gt;Qual lado está sendo Broadcast?&lt;/p&gt;

&lt;p&gt;O plano parece coerente com o tamanho dos dados?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Não precisamos decorar cada linha do plano.

Precisamos aprender a reconhecer padrões.

---

# 35. API simples, execução complexa

Esse é um dos aspectos que mais gosto no Spark.

Podemos escrever:



```python id="3a48w7"
df1.join(
    df2,
    "id"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Uma linha.&lt;/p&gt;

&lt;p&gt;Mas por trás dela podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="dspvhm"&lt;br&gt;
Logical Plan&lt;br&gt;
     ↓&lt;br&gt;
Analyzer&lt;br&gt;
     ↓&lt;br&gt;
Catalyst&lt;br&gt;
     ↓&lt;br&gt;
Optimization&lt;br&gt;
     ↓&lt;br&gt;
Statistics&lt;br&gt;
     ↓&lt;br&gt;
Physical Planning&lt;br&gt;
     ↓&lt;br&gt;
Exchange&lt;br&gt;
     ↓&lt;br&gt;
Shuffle&lt;br&gt;
     ↓&lt;br&gt;
Sort&lt;br&gt;
     ↓&lt;br&gt;
Join&lt;br&gt;
     ↓&lt;br&gt;
Tasks&lt;br&gt;
     ↓&lt;br&gt;
Executors&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Uma API simples esconde um sistema distribuído bastante sofisticado.

E é justamente por isso que entender apenas a sintaxe não é suficiente.

---

# 36. A principal lição deste capítulo

Quando vejo:



```python id="p0zx0k"
A.join(B, "id")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;minha primeira pergunta não é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Qual é a sintaxe do join?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Qual estratégia física o Spark vai usar para juntar esses dados?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="t8pvdl"&lt;br&gt;
Qual tamanho de A?&lt;/p&gt;

&lt;p&gt;Qual tamanho de B?&lt;/p&gt;

&lt;p&gt;Qual cardinalidade?&lt;/p&gt;

&lt;p&gt;Existe skew?&lt;/p&gt;

&lt;p&gt;Existe filtro possível?&lt;/p&gt;

&lt;p&gt;Existe broadcast?&lt;/p&gt;

&lt;p&gt;Haverá shuffle?&lt;/p&gt;

&lt;p&gt;Quanto dado será movimentado?&lt;/p&gt;

&lt;p&gt;Qual plano físico foi escolhido?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Essa mudança de mentalidade é fundamental para trabalhar com Spark em escala.

---

# 37. O modelo mental final

Podemos resumir a decisão assim:



```text id="ozngmm"
                    JOIN
                      │
                      ▼
              Qual o tamanho?
                      │
          ┌───────────┴───────────┐
          │                       │
     Um lado pequeno         Ambos grandes
          │                       │
          ▼                       ▼
      Broadcast?              Shuffle
          │                       │
          ▼                       ▼
 BroadcastHashJoin        SortMergeJoin
          │                       │
          └───────────┬───────────┘
                      ▼
                Verificar Skew
                      │
                      ▼
               Verificar Plano
                      │
                      ▼
                 Spark UI
                      │
                      ▼
                Medir resultado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Não é uma árvore de decisão absoluta.&lt;/p&gt;

&lt;p&gt;É um modelo para raciocinar.&lt;/p&gt;

&lt;p&gt;E essa diferença é importante.&lt;/p&gt;


&lt;h1&gt;
  
  
  Próximo artigo
&lt;/h1&gt;

&lt;p&gt;Até agora falamos várias vezes sobre:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="2fkcb5"&lt;br&gt;
Spark decide...&lt;br&gt;
Spark otimiza...&lt;br&gt;
Spark escolhe...&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Mas quem está tomando essas decisões?

No próximo artigo vamos abrir a caixa-preta do:

# Spark SQL

e entender a evolução:



```text id="j1ry29"
RDD
 ↓
DataFrame
 ↓
Spark SQL
 ↓
Logical Plan
 ↓
Catalyst Optimizer
 ↓
Physical Plan
 ↓
Tungsten
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vamos explorar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;por que DataFrames mudaram a forma de trabalhar com Spark;&lt;/li&gt;
&lt;li&gt;SparkSession;&lt;/li&gt;
&lt;li&gt;schemas;&lt;/li&gt;
&lt;li&gt;SQL vs. DataFrame API;&lt;/li&gt;
&lt;li&gt;Catalyst Optimizer;&lt;/li&gt;
&lt;li&gt;logical plans;&lt;/li&gt;
&lt;li&gt;physical plans;&lt;/li&gt;
&lt;li&gt;predicate pushdown;&lt;/li&gt;
&lt;li&gt;projection pruning;&lt;/li&gt;
&lt;li&gt;Tungsten;&lt;/li&gt;
&lt;li&gt;Whole-Stage Code Generation;&lt;/li&gt;
&lt;li&gt;AQE;&lt;/li&gt;
&lt;li&gt;e por que APIs declarativas permitem que o Spark faça otimizações que seriam muito mais difíceis com código arbitrário.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É a ponte entre o &lt;strong&gt;Spark que eu comecei estudando com RDDs&lt;/strong&gt; e o &lt;strong&gt;Spark moderno usado em plataformas de dados&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.toCOLE_AQUI_A_URL_DA_PARTE_4"&gt;← Parte 4: Shuffle — onde a performance começa a ficar cara&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Próximo:&lt;/strong&gt; Parte 6 — Spark SQL e DataFrames: como o Catalyst transforma código em execução distribuída&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>bigdata</category>
      <category>spark</category>
      <category>cluster</category>
    </item>
    <item>
      <title>Como aprendi Apache Spark — Parte 4: Shuffle — onde a performance começa a ficar avançada</title>
      <dc:creator>renanpyd</dc:creator>
      <pubDate>Fri, 23 Oct 2026 14:41:03 +0000</pubDate>
      <link>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-4-shuffle-onde-a-performance-comeca-a-ficar-avancada-1e4g</link>
      <guid>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-4-shuffle-onde-a-performance-comeca-a-ficar-avancada-1e4g</guid>
      <description>&lt;p&gt;No artigo anterior, vimos que existe uma relação fundamental no Apache Spark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dados
  ↓
Partitions
  ↓
Tasks
  ↓
Parallelism
  ↓
Performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também vimos que nem sempre os dados permanecem nas mesmas partições durante todo o processamento.&lt;/p&gt;

&lt;p&gt;Operações como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;podem exigir uma redistribuição dos dados.&lt;/p&gt;

&lt;p&gt;E é nesse momento que aparece um dos conceitos mais importantes para entender performance no Spark:&lt;/p&gt;

&lt;h1&gt;
  
  
  Shuffle
&lt;/h1&gt;

&lt;p&gt;Quando comecei a entender o que realmente acontecia durante um shuffle, vários problemas de performance que antes pareciam aleatórios começaram a fazer sentido.&lt;/p&gt;

&lt;p&gt;Porque shuffle não significa apenas:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spark reorganizou algumas linhas."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Em um ambiente distribuído, reorganizar dados pode significar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;serializar
    ↓
escrever
    ↓
transferir pela rede
    ↓
ler
    ↓
desserializar
    ↓
agrupar
    ↓
continuar processando
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E tudo isso tem custo.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. O que é Shuffle?
&lt;/h1&gt;

&lt;p&gt;Vamos começar com um exemplo simples.&lt;/p&gt;

&lt;p&gt;Imagine que temos dados distribuídos entre três partições:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition 1

customer=A
customer=B
customer=C


Partition 2

customer=A
customer=D
customer=B


Partition 3

customer=C
customer=A
customer=D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora queremos contar quantos registros existem para cada cliente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para calcular:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → 3
B → 2
C → 2
D → 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark precisa reunir registros relacionados à mesma chave.&lt;/p&gt;

&lt;p&gt;Mas os registros de &lt;code&gt;customer=A&lt;/code&gt;, por exemplo, estão espalhados entre diferentes partições.&lt;/p&gt;

&lt;p&gt;Temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 → A
P2 → A
P3 → A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em algum momento esses dados precisam ser redistribuídos.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTES

Partition 1     Partition 2     Partition 3

A               A               C
B               D               A
C               B               D


                 ↓
              SHUFFLE
                 ↓


DEPOIS

Partition A     Partition B     Partition C

A               B               C
A               B               C
A               D               D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A distribuição exata depende da estratégia e do particionamento utilizados, mas a ideia central é esta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Shuffle redistribui dados entre partições para permitir que uma etapa posterior da computação seja executada.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  2. Por que Shuffle é caro?
&lt;/h1&gt;

&lt;p&gt;Se todos os dados estivessem dentro de um único processo, reorganizar registros seria relativamente simples.&lt;/p&gt;

&lt;p&gt;Mas Spark é distribuído.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Machine A
Executor 1

Machine B
Executor 2

Machine C
Executor 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora precisamos mover dados de uma máquina para outra:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor 1 ──────────► Executor 2
     │                      ▲
     │                      │
     ▼                      │
Executor 3 ─────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso pode envolver:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU;&lt;/li&gt;
&lt;li&gt;serialização;&lt;/li&gt;
&lt;li&gt;memória;&lt;/li&gt;
&lt;li&gt;disco;&lt;/li&gt;
&lt;li&gt;rede;&lt;/li&gt;
&lt;li&gt;desserialização;&lt;/li&gt;
&lt;li&gt;gerenciamento de buffers;&lt;/li&gt;
&lt;li&gt;sincronização entre etapas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Portanto, shuffle pode transformar uma operação aparentemente simples em uma operação distribuída relativamente cara.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Narrow vs. Wide Dependencies
&lt;/h1&gt;

&lt;p&gt;Na Parte 3 vimos rapidamente a diferença entre operações narrow e wide.&lt;/p&gt;

&lt;p&gt;Agora ela se torna essencial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Narrow dependency
&lt;/h2&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor &amp;gt; 100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada partição pode executar o filtro sobre seus próprios registros.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition 1 ───► Filter ───► Partition 1'

Partition 2 ───► Filter ───► Partition 2'

Partition 3 ───► Filter ───► Partition 3'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não precisamos necessariamente enviar registros de uma partição para outra.&lt;/p&gt;

&lt;p&gt;Outro exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 ───► P1'
P2 ───► P2'
P3 ───► P3'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é muito diferente de uma operação que precisa reorganizar os dados globalmente.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Wide dependency
&lt;/h1&gt;

&lt;p&gt;Agora considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os registros do mesmo cliente podem estar em qualquer partição.&lt;/p&gt;

&lt;p&gt;Temos algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 ──┐       ┌──► P1'
     ├───────┤
P2 ──┼───────┼──► P2'
     ├───────┤
P3 ──┘       └──► P3'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma partição de saída pode depender de dados provenientes de várias partições anteriores.&lt;/p&gt;

&lt;p&gt;Isso caracteriza uma dependência ampla — &lt;strong&gt;wide dependency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;E frequentemente significa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wide Dependency
       ↓
    Shuffle
       ↓
Stage Boundary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse relacionamento é uma das chaves para entender o DAG de uma aplicação Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Shuffle cria fronteiras entre Stages
&lt;/h1&gt;

&lt;p&gt;Na Parte 2 vimos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Action
   ↓
 Job
   ↓
Stages
   ↓
Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora conseguimos entender melhor como Spark pode separar determinados Stages.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ACTIVE&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos imaginar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STAGE 1

Read
 ↓
Filter
 ↓
Partial Processing
 ↓

========= SHUFFLE =========

 ↓
STAGE 2

Aggregation
 ↓
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O shuffle cria uma dependência entre conjuntos de Tasks.&lt;/p&gt;

&lt;p&gt;As Tasks da etapa posterior precisam dos dados produzidos pela etapa anterior.&lt;/p&gt;

&lt;p&gt;Isso forma uma fronteira natural na execução.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Shuffle Write e Shuffle Read
&lt;/h1&gt;

&lt;p&gt;Esse processo pode ser dividido conceitualmente em duas partes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage anterior
      │
      ▼
SHUFFLE WRITE
      │
      ▼
   Network
      │
      ▼
SHUFFLE READ
      │
      ▼
Próximo Stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Shuffle Write
&lt;/h2&gt;

&lt;p&gt;As Tasks do Stage anterior produzem dados que serão consumidos posteriormente.&lt;/p&gt;

&lt;p&gt;Esses dados precisam ser organizados para as partições de destino.&lt;/p&gt;

&lt;p&gt;Podemos imaginar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1
  │
  ├── dados para Partition A
  ├── dados para Partition B
  └── dados para Partition C

Task 2
  │
  ├── dados para Partition A
  ├── dados para Partition B
  └── dados para Partition C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esses resultados formam a saída do shuffle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shuffle Read
&lt;/h2&gt;

&lt;p&gt;No Stage seguinte, as Tasks precisam buscar os blocos correspondentes à sua partição.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  Task A

Task 1 output ──────┐
Task 2 output ──────┼──► Partition A
Task 3 output ──────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora começamos a entender métricas encontradas no Spark UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shuffle Read
Shuffle Write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elas não são apenas números abstratos.&lt;/p&gt;

&lt;p&gt;Representam movimentação real de dados durante a computação distribuída.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Um exemplo com &lt;code&gt;groupBy&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Antes da agregação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor 1

SP
RJ
SP
MG


Executor 2

MG
SP
BA
RJ


Executor 3

SP
BA
SP
MG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para calcular os totais por estado, os registros precisam ser reorganizados de acordo com a chave.&lt;/p&gt;

&lt;p&gt;Algo conceitualmente semelhante a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  SHUFFLE

Executor 1 ─────────┐
Executor 2 ─────────┼──► Partition SP
Executor 3 ─────────┘


Executor 1 ─────────┐
Executor 2 ─────────┼──► Partition RJ
Executor 3 ─────────┘


Executor 1 ─────────┐
Executor 2 ─────────┼──► Partition MG
Executor 3 ─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois disso, cada partição pode executar sua parte da agregação.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. &lt;code&gt;groupByKey()&lt;/code&gt; vs. &lt;code&gt;reduceByKey()&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;No mundo dos RDDs, um exemplo clássico para entender shuffle é comparar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupByKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduceByKey&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;("spark", 1)
("spark", 1)
("spark", 1)
("python", 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com &lt;code&gt;groupByKey()&lt;/code&gt;, os valores associados às chaves precisam ser agrupados para então serem processados.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark → [1, 1, 1]
python → [1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduceByKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark pode realizar agregações locais antes de transferir os dados entre partições.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PARTITION 1

spark → 1
spark → 1
python → 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Antes de enviar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark → 2
python → 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outra partição:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PARTITION 2

spark → 1
spark → 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pode produzir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark → 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Então, em vez de mover todos os registros individualmente, podemos movimentar resultados parcialmente agregados.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition 1 → spark:2
Partition 2 → spark:2

             ↓

          spark:4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa é uma otimização extremamente importante.&lt;/p&gt;

&lt;p&gt;A lição é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quanto menos dados precisarmos movimentar, melhor.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  9. Map-side aggregation
&lt;/h1&gt;

&lt;p&gt;O conceito anterior é frequentemente associado à ideia de realizar parte da agregação antes do shuffle.&lt;/p&gt;

&lt;p&gt;Visualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SEM AGREGAÇÃO LOCAL

1
1
1
1
1
1
1
1
      ↓
   NETWORK
      ↓
sum()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;COM AGREGAÇÃO LOCAL

Partition A
1 + 1 + 1 + 1
      ↓
      4


Partition B
1 + 1 + 1 + 1
      ↓
      4


4 ───┐
     ├──► NETWORK ───► 8
4 ───┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4
4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;é muito mais eficiente do que mover todos os registros originais.&lt;/p&gt;

&lt;p&gt;Essa ideia aparece de diferentes formas nas otimizações do Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Joins também podem gerar Shuffle
&lt;/h1&gt;

&lt;p&gt;Agora chegamos a um caso extremamente comum em Engenharia de Dados.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;clientes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PEDIDOS

customer_id=1
customer_id=5
customer_id=3
customer_id=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLIENTES

customer_id=1
customer_id=2
customer_id=3
customer_id=5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para fazer o join, registros com a mesma chave precisam se encontrar.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pedidos ──────┐
              │
              ▼
           SHUFFLE
              │
              ▼
Clientes ─────┘

customer_id=1 ──► mesma partition
customer_id=2 ──► mesma partition
customer_id=3 ──► mesma partition
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependendo da estratégia de join, isso pode significar mover grandes volumes de dados pela rede.&lt;/p&gt;

&lt;p&gt;E aqui surge uma pergunta importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;E se uma das tabelas for muito pequena?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Talvez não faça sentido redistribuir os dois lados.&lt;/p&gt;

&lt;p&gt;É aí que entra uma técnica que veremos em detalhes mais adiante:&lt;/p&gt;

&lt;h1&gt;
  
  
  Broadcast Join
&lt;/h1&gt;

&lt;p&gt;Em vez de movimentar duas tabelas grandes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Large Table ──► Shuffle
Large Table ──► Shuffle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;podemos, em determinados cenários, distribuir uma tabela pequena para os Executors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Small Table
                /     |     \
               ▼      ▼      ▼
          Executor Executor Executor

             Large Dataset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assim podemos evitar um shuffle grande de um dos lados.&lt;/p&gt;

&lt;p&gt;Essa é uma das otimizações mais importantes em joins Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. Shuffle não usa apenas rede
&lt;/h1&gt;

&lt;p&gt;É comum resumir:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Shuffle é caro porque usa rede."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Isso é verdade, mas incompleto.&lt;/p&gt;

&lt;p&gt;Shuffle pode envolver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    SHUFFLE
                       │
        ┌──────────────┼──────────────┐
        ▼              ▼              ▼
       CPU           Memory          Disk
        │              │              │
 serialization       buffers        spill
        │              │              │
        └──────────────┼──────────────┘
                       ▼
                    Network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja, um shuffle pesado pode pressionar vários recursos simultaneamente.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Quando memória não é suficiente: Spill
&lt;/h1&gt;

&lt;p&gt;Imagine que uma Task precisa organizar uma quantidade significativa de dados durante uma agregação ou ordenação.&lt;/p&gt;

&lt;p&gt;Idealmente, parte desse trabalho acontece em memória.&lt;/p&gt;

&lt;p&gt;Mas a memória é finita.&lt;/p&gt;

&lt;p&gt;Quando Spark não consegue manter todo o estado necessário em memória, pode precisar utilizar disco.&lt;/p&gt;

&lt;p&gt;Esse comportamento é conhecido como:&lt;/p&gt;

&lt;h1&gt;
  
  
  Spill
&lt;/h1&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dados
  ↓
Memory
  ↓
Memory insuficiente
  ↓
Disk Spill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois, esses dados podem precisar ser lidos novamente.&lt;/p&gt;

&lt;p&gt;Temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory
  ↓
Disk Write
  ↓
Disk Read
  ↓
Continue Processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora o custo aumentou.&lt;/p&gt;

&lt;p&gt;Além de CPU e rede, temos I/O em disco.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. Memory Spill vs. Disk Spill
&lt;/h1&gt;

&lt;p&gt;Ao analisar métricas de execução, podemos encontrar indicadores relacionados a spill.&lt;/p&gt;

&lt;p&gt;A ideia principal é perceber que:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Uma Task precisou usar disco porque o processamento intermediário ultrapassou o que podia ser tratado eficientemente em memória.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Isso pode acontecer em operações como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agregações;&lt;/li&gt;
&lt;li&gt;joins;&lt;/li&gt;
&lt;li&gt;sorts;&lt;/li&gt;
&lt;li&gt;shuffles;&lt;/li&gt;
&lt;li&gt;processamento de partições muito grandes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se encontramos grandes volumes de spill, precisamos investigar.&lt;/p&gt;

&lt;p&gt;Não significa automaticamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"adicione mais memória."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pode significar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;partições grandes demais;&lt;/li&gt;
&lt;li&gt;skew;&lt;/li&gt;
&lt;li&gt;estratégia de join inadequada;&lt;/li&gt;
&lt;li&gt;volume excessivo de shuffle;&lt;/li&gt;
&lt;li&gt;configuração inadequada;&lt;/li&gt;
&lt;li&gt;algoritmo inadequado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Performance tuning exige descobrir a causa.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. Shuffle + Data Skew = combinação perigosa
&lt;/h1&gt;

&lt;p&gt;Na Parte 3 vimos &lt;strong&gt;Data Skew&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Agora podemos entender por que ele é especialmente problemático durante shuffle.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id

A → 100.000.000 registros
B → 500.000
C → 400.000
D → 300.000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois do shuffle por &lt;code&gt;customer_id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition A █████████████████████████████████████████

Partition B █

Partition C █

Partition D █
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora temos Tasks como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task A → 25 minutos

Task B → 20 segundos

Task C → 18 segundos

Task D → 15 segundos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois de alguns segundos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor 1 → █████████████████ trabalhando

Executor 2 → idle

Executor 3 → idle

Executor 4 → idle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O cluster possui recursos.&lt;/p&gt;

&lt;p&gt;Mas uma única partição concentra grande parte do trabalho.&lt;/p&gt;

&lt;p&gt;Esse é um dos padrões mais importantes para reconhecer no Spark UI.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Stragglers
&lt;/h1&gt;

&lt;p&gt;Tasks que demoram muito mais que as demais são frequentemente chamadas de &lt;strong&gt;stragglers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage 17

Task 001 → 21 s
Task 002 → 19 s
Task 003 → 22 s
Task 004 → 20 s
Task 005 → 18 s
...
Task 199 → 20 s

Task 200 → 14 min
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Stage precisa esperar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Então:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;199 Tasks concluídas
         +
1 Task extremamente lenta
         =
Stage ainda executando
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O problema pode ser skew.&lt;/p&gt;

&lt;p&gt;Mas também pode ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hardware;&lt;/li&gt;
&lt;li&gt;I/O;&lt;/li&gt;
&lt;li&gt;garbage collection;&lt;/li&gt;
&lt;li&gt;spill;&lt;/li&gt;
&lt;li&gt;rede;&lt;/li&gt;
&lt;li&gt;tamanho da partição;&lt;/li&gt;
&lt;li&gt;executor problemático.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por isso precisamos analisar métricas antes de concluir.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. &lt;code&gt;repartition()&lt;/code&gt; é um Shuffle explícito
&lt;/h1&gt;

&lt;p&gt;Agora podemos revisitar a Parte 3.&lt;/p&gt;

&lt;p&gt;Quando executamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark precisa redistribuir os registros entre novas partições.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTES

P1 ─────┐
P2 ─────┤
P3 ─────┼────► SHUFFLE
P4 ─────┤
P5 ─────┘

                 ↓

DEPOIS

P1'
P2'
P3'
...
P200'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso explica por que adicionar &lt;code&gt;repartition()&lt;/code&gt; arbitrariamente pode prejudicar performance.&lt;/p&gt;

&lt;p&gt;Talvez tenhamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read
 ↓
repartition()
 ↓
SHUFFLE
 ↓
groupBy()
 ↓
SHUFFLE
 ↓
join()
 ↓
SHUFFLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora imagine isso sobre vários terabytes.&lt;/p&gt;

&lt;p&gt;Estamos movimentando uma quantidade enorme de dados entre máquinas.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. Shuffle e serialização
&lt;/h1&gt;

&lt;p&gt;Dados não simplesmente "viajam" pela rede como objetos Python ou JVM abstratos.&lt;/p&gt;

&lt;p&gt;Eles precisam ser representados de uma forma adequada para transferência e armazenamento.&lt;/p&gt;

&lt;p&gt;Simplificando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Object
  ↓
Serialization
  ↓
Bytes
  ↓
Network / Disk
  ↓
Deserialization
  ↓
Object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso também custa CPU.&lt;/p&gt;

&lt;p&gt;Em workloads grandes, detalhes aparentemente pequenos começam a se multiplicar por:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;milhões
bilhões
trilhões
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;de registros.&lt;/p&gt;

&lt;p&gt;É por isso que otimizações distribuídas frequentemente tratam não apenas de algoritmos, mas também de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;representação;&lt;/li&gt;
&lt;li&gt;serialização;&lt;/li&gt;
&lt;li&gt;compressão;&lt;/li&gt;
&lt;li&gt;layout;&lt;/li&gt;
&lt;li&gt;movimentação de dados.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  18. Shuffle e compressão
&lt;/h1&gt;

&lt;p&gt;Existe um trade-off interessante.&lt;/p&gt;

&lt;p&gt;Podemos comprimir dados antes de movimentá-los.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 GB
  ↓
Compression
  ↓
30 GB
  ↓
Network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reduzimos o volume transferido.&lt;/p&gt;

&lt;p&gt;Mas agora precisamos gastar CPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU → compress
Network → transfer
CPU → decompress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos um trade-off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU
 ↕
Network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em sistemas distribuídos, otimização frequentemente significa escolher &lt;strong&gt;onde queremos pagar o custo&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  19. Como identificar Shuffle no Spark UI
&lt;/h1&gt;

&lt;p&gt;Agora o Spark UI começa a ficar ainda mais interessante.&lt;/p&gt;

&lt;p&gt;Podemos observar métricas como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shuffle Read
Shuffle Write
Input
Output
Task Duration
GC Time
Spill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine um Stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input:          200 GB
Shuffle Write:  180 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o próximo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Shuffle Read:   180 GB
Output:          20 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso nos conta uma história.&lt;/p&gt;

&lt;p&gt;Talvez tenhamos movimentado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;180 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para produzir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Então surge uma pergunta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Poderíamos reduzir os dados antes do shuffle?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Talvez um filtro pudesse acontecer antes.&lt;/p&gt;

&lt;p&gt;Talvez uma projeção pudesse eliminar colunas desnecessárias.&lt;/p&gt;

&lt;p&gt;Talvez a estratégia de join pudesse mudar.&lt;/p&gt;

&lt;p&gt;Esse é o tipo de pergunta que transforma métricas em otimização.&lt;/p&gt;




&lt;h1&gt;
  
  
  20. Filter Early
&lt;/h1&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sobre:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 TB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora imagine que só precisamos de registros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;status = 'ACTIVE'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se filtrarmos antes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ACTIVE&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e apenas 20% dos registros forem ativos, conceitualmente podemos passar de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 TB
 ↓
SHUFFLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para algo mais próximo de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 TB
 ↓
FILTER
 ↓
200 GB
 ↓
SHUFFLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reduzimos drasticamente o volume que precisa atravessar a operação cara.&lt;/p&gt;

&lt;p&gt;Essa é uma ideia simples e poderosa:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Reduza os dados antes de movimentá-los, quando semanticamente possível.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  21. Project Early
&lt;/h1&gt;

&lt;p&gt;O mesmo raciocínio vale para colunas.&lt;/p&gt;

&lt;p&gt;Imagine um dataset com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 colunas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;mas nosso processamento precisa apenas de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id
valor
estado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos selecionar cedo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 columns
     ↓
SELECT
     ↓
3 columns
     ↓
SHUFFLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependendo do plano de execução e do formato da fonte, isso também pode reduzir bastante a quantidade de dados manipulada.&lt;/p&gt;

&lt;p&gt;A ideia continua:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Não mova dados que você não precisa.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  22. Aggregate Early
&lt;/h1&gt;

&lt;p&gt;Quando possível, também podemos reduzir cardinalidade antes de determinadas movimentações.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 bilhão de registros
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;que podem ser parcialmente agregados localmente antes de uma redistribuição.&lt;/p&gt;

&lt;p&gt;Se conseguirmos transformar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.000.000.000 rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10.000.000 partial results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;antes da transferência, temos uma diferença enorme.&lt;/p&gt;

&lt;p&gt;Essa é a lógica por trás de várias estratégias de agregação distribuída.&lt;/p&gt;




&lt;h1&gt;
  
  
  23. O melhor Shuffle é o Shuffle que não precisamos fazer
&lt;/h1&gt;

&lt;p&gt;Essa frase resume boa parte do tuning de sistemas distribuídos:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Movimentação de dados é cara.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Isso não significa que devemos eliminar todos os shuffles.&lt;/p&gt;

&lt;p&gt;Muitos são necessários.&lt;/p&gt;

&lt;p&gt;Um:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pode exigir redistribuição.&lt;/p&gt;

&lt;p&gt;Um:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pode exigir redistribuição.&lt;/p&gt;

&lt;p&gt;Um:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;existe justamente para redistribuir.&lt;/p&gt;

&lt;p&gt;A pergunta correta não é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Como elimino todos os shuffles?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Esse shuffle é necessário e, se for, estou movimentando apenas os dados necessários?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  24. Um exemplo de pipeline ruim
&lt;/h1&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;READ
 ↓
REPARTITION
 ↓
SHUFFLE
 ↓
GROUP BY
 ↓
SHUFFLE
 ↓
RESULT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora suponha que só precisamos de eventos de compra.&lt;/p&gt;

&lt;p&gt;Podemos reconsiderar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event_type = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;purchase&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;READ
 ↓
FILTER
 ↓
PROJECT
 ↓
menos dados
 ↓
SHUFFLE necessário
 ↓
AGGREGATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não estamos "fazendo Spark ficar rápido".&lt;/p&gt;

&lt;p&gt;Estamos simplesmente reduzindo trabalho desnecessário.&lt;/p&gt;

&lt;p&gt;Essa distinção é importante.&lt;/p&gt;




&lt;h1&gt;
  
  
  25. Mais memória nem sempre resolve Shuffle
&lt;/h1&gt;

&lt;p&gt;Um padrão comum de troubleshooting é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job lento
   ↓
aumentar memória
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;continua lento
   ↓
aumentar memória novamente
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas talvez o problema real seja:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 TB de Shuffle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adicionar memória pode ajudar em alguns aspectos.&lt;/p&gt;

&lt;p&gt;Mas não elimina necessariamente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;movimentação de rede;&lt;/li&gt;
&lt;li&gt;serialização;&lt;/li&gt;
&lt;li&gt;skew;&lt;/li&gt;
&lt;li&gt;quantidade de dados;&lt;/li&gt;
&lt;li&gt;algoritmo inadequado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Talvez o problema precise ser atacado no plano da computação.&lt;/p&gt;




&lt;h1&gt;
  
  
  26. Mais Executors também não resolvem tudo
&lt;/h1&gt;

&lt;p&gt;Outro pensamento comum:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Vou dobrar o cluster."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Podemos passar de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 Executors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20 Executors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas se o problema for uma chave extremamente skewed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id = 123

80% dos registros
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;podemos continuar tendo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 Task gigantesca
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e vários Executors ociosos.&lt;/p&gt;

&lt;p&gt;Escalar infraestrutura sem entender o gargalo pode simplesmente aumentar custo.&lt;/p&gt;




&lt;h1&gt;
  
  
  27. Performance e custo caminham juntos
&lt;/h1&gt;

&lt;p&gt;Em ambientes cloud, essa relação é ainda mais importante.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job A

Cluster: 20 workers
Tempo: 2 horas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois de reduzir shuffles desnecessários:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job A

Cluster: 20 workers
Tempo: 35 minutos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não ganhamos apenas velocidade.&lt;/p&gt;

&lt;p&gt;Reduzimos consumo de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU;&lt;/li&gt;
&lt;li&gt;memória;&lt;/li&gt;
&lt;li&gt;disco;&lt;/li&gt;
&lt;li&gt;rede;&lt;/li&gt;
&lt;li&gt;tempo de cluster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Portanto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Performance
     ↓
Efficiency
     ↓
Cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Performance tuning também é &lt;strong&gt;FinOps&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  28. O Spark moderno consegue ajudar
&lt;/h1&gt;

&lt;p&gt;Nas versões modernas do Spark, mecanismos de otimização conseguem melhorar várias decisões automaticamente.&lt;/p&gt;

&lt;p&gt;Um dos mais importantes é:&lt;/p&gt;

&lt;h1&gt;
  
  
  Adaptive Query Execution — AQE
&lt;/h1&gt;

&lt;p&gt;Durante a execução, Spark pode utilizar estatísticas reais para adaptar partes do plano.&lt;/p&gt;

&lt;p&gt;Isso pode ajudar em cenários relacionados a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shuffle partitions;&lt;/li&gt;
&lt;li&gt;estratégias de join;&lt;/li&gt;
&lt;li&gt;skew;&lt;/li&gt;
&lt;li&gt;coalescing de partições.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Plano inicial
     ↓
Execução começa
     ↓
Estatísticas reais
     ↓
AQE
     ↓
Plano adaptado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é uma evolução enorme em relação aos primeiros anos do Spark.&lt;/p&gt;

&lt;p&gt;Mas existe uma lição importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Automação não elimina a necessidade de entender o sistema.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quando sabemos como shuffle funciona, conseguimos interpretar melhor o que o Spark está tentando otimizar.&lt;/p&gt;




&lt;h1&gt;
  
  
  29. Como eu investigaria um Shuffle caro
&lt;/h1&gt;

&lt;p&gt;Imagine um Job demorando muito.&lt;/p&gt;

&lt;p&gt;Meu raciocínio seria algo parecido com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JOB LENTO
   ↓
Qual Stage?
   ↓
Existe Shuffle?
   ↓
Quanto Shuffle Read/Write?
   ↓
As Tasks têm duração semelhante?
   ↓
Existe Skew?
   ↓
Existe Spill?
   ↓
Qual operação criou o Shuffle?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;groupBy?
join?
repartition?
distinct?
orderBy?
aggregation?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Então perguntaria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Posso filtrar antes?

Posso selecionar menos colunas?

Posso agregar antes?

O repartition é necessário?

A chave está skewed?

Existe uma estratégia melhor de join?

Uma tabela poderia ser broadcast?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é muito mais eficiente do que ajustar parâmetros aleatoriamente.&lt;/p&gt;




&lt;h1&gt;
  
  
  30. Checklist de Shuffle
&lt;/h1&gt;

&lt;p&gt;Quando analiso um pipeline Spark, algumas perguntas são especialmente úteis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quanto de Shuffle Write existe?&lt;/li&gt;
&lt;li&gt;Quanto de Shuffle Read existe?&lt;/li&gt;
&lt;li&gt;Qual operação gerou o shuffle?&lt;/li&gt;
&lt;li&gt;O shuffle é realmente necessário?&lt;/li&gt;
&lt;li&gt;Estamos movimentando colunas desnecessárias?&lt;/li&gt;
&lt;li&gt;Podemos filtrar dados antes?&lt;/li&gt;
&lt;li&gt;Existe agregação local possível?&lt;/li&gt;
&lt;li&gt;Existe data skew?&lt;/li&gt;
&lt;li&gt;Existem Tasks muito maiores que as outras?&lt;/li&gt;
&lt;li&gt;Há spill para disco?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repartition()&lt;/code&gt; está sendo usado sem necessidade?&lt;/li&gt;
&lt;li&gt;O número de shuffle partitions faz sentido?&lt;/li&gt;
&lt;li&gt;O join está usando uma estratégia adequada?&lt;/li&gt;
&lt;li&gt;Uma das tabelas é pequena o suficiente para outra estratégia?&lt;/li&gt;
&lt;li&gt;O AQE está ajudando?&lt;/li&gt;
&lt;li&gt;O problema é computação ou movimentação de dados?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essas perguntas normalmente são mais valiosas do que procurar:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Qual configuração deixa Spark mais rápido?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  31. A mudança de mentalidade
&lt;/h1&gt;

&lt;p&gt;Quando comecei a aprender Spark, era fácil olhar para:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e enxergar apenas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agrupar
+
contar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois de entender arquitetura distribuída, começo a enxergar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dataset
   ↓
Partitions
   ↓
Local Processing
   ↓
Shuffle Write
   ↓
Network
   ↓
Shuffle Read
   ↓
New Partitions
   ↓
Aggregation
   ↓
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa segunda visão é muito mais próxima do que realmente importa para Engenharia de Dados em escala.&lt;/p&gt;




&lt;h1&gt;
  
  
  32. O princípio que levo para outros sistemas
&lt;/h1&gt;

&lt;p&gt;Existe uma ideia aqui que vai muito além do Spark:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Em sistemas distribuídos, mover dados costuma ser caro.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Esse princípio aparece em:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bancos distribuídos;&lt;/li&gt;
&lt;li&gt;Data Warehouses;&lt;/li&gt;
&lt;li&gt;Lakehouses;&lt;/li&gt;
&lt;li&gt;sistemas de streaming;&lt;/li&gt;
&lt;li&gt;microserviços;&lt;/li&gt;
&lt;li&gt;Machine Learning distribuído;&lt;/li&gt;
&lt;li&gt;HPC;&lt;/li&gt;
&lt;li&gt;arquiteturas cloud.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quanto maior o volume, mais importante fica pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Onde os dados estão?

Onde a computação está?

Quanto precisa atravessar a rede?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Muitas decisões de arquitetura nascem dessas três perguntas.&lt;/p&gt;




&lt;h1&gt;
  
  
  33. A principal lição deste capítulo
&lt;/h1&gt;

&lt;p&gt;Se eu pudesse resumir shuffle em uma única sequência, seria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dados distribuídos
       ↓
Operação precisa reorganizá-los
       ↓
Shuffle Write
       ↓
Disk / Memory
       ↓
Network
       ↓
Shuffle Read
       ↓
Novas Partitions
       ↓
Próximo Stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E resumiria a otimização em uma pergunta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quanto dado realmente precisa atravessar esse caminho?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quanto menos dados desnecessários movimentarmos, melhor.&lt;/p&gt;




&lt;h1&gt;
  
  
  Próximo artigo
&lt;/h1&gt;

&lt;p&gt;Agora chegamos naturalmente a um dos assuntos que mais causam diferença entre um pipeline eficiente e um pipeline caro:&lt;/p&gt;

&lt;h1&gt;
  
  
  Joins
&lt;/h1&gt;

&lt;p&gt;No próximo artigo vamos entender por que isto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pedidos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;clientes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pode resultar em planos de execução completamente diferentes dependendo dos dados.&lt;/p&gt;

&lt;p&gt;Vamos explorar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;joins distribuídos;&lt;/li&gt;
&lt;li&gt;shuffle joins;&lt;/li&gt;
&lt;li&gt;Broadcast Hash Join;&lt;/li&gt;
&lt;li&gt;Sort Merge Join;&lt;/li&gt;
&lt;li&gt;estratégias de join;&lt;/li&gt;
&lt;li&gt;tabelas grandes vs. pequenas;&lt;/li&gt;
&lt;li&gt;estatísticas;&lt;/li&gt;
&lt;li&gt;broadcast;&lt;/li&gt;
&lt;li&gt;data skew;&lt;/li&gt;
&lt;li&gt;joins com chaves problemáticas;&lt;/li&gt;
&lt;li&gt;leitura do plano com &lt;code&gt;explain()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Catalyst Optimizer;&lt;/li&gt;
&lt;li&gt;AQE;&lt;/li&gt;
&lt;li&gt;e como escolher uma estratégia baseada nos dados, não em receitas prontas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vamos evoluir deste modelo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Table A ───┐
           ├──► SHUFFLE ───► JOIN
Table B ───┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para entender quando podemos fazer algo próximo de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;               Small Table
              /     |     \
             ▼      ▼      ▼

Large ───► Executor Executor Executor
             │        │        │
             ▼        ▼        ▼
            JOIN     JOIN     JOIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É aí que Spark começa a mostrar de verdade o poder do seu otimizador.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dev.toCOLE_AQUI_A_URL_DA_PARTE_3"&gt;← Parte 3: Partições, paralelismo e performance&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Próximo:&lt;/strong&gt; Parte 5 — Joins no Apache Spark: Shuffle, Broadcast e estratégias de execução&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>bigdata</category>
      <category>pyspark</category>
      <category>spark</category>
    </item>
    <item>
      <title>Como aprendi Apache Spark — Parte 3: Partições, paralelismo e performance</title>
      <dc:creator>renanpyd</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:59:09 +0000</pubDate>
      <link>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-3-particoes-paralelismo-e-performance-gc5</link>
      <guid>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-3-particoes-paralelismo-e-performance-gc5</guid>
      <description>&lt;p&gt;No artigo anterior, desmontamos a arquitetura de uma aplicação Apache Spark e entendemos a relação entre:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
Driver
    ↓
Cluster Manager
    ↓
Executors
    ↓
Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas ficou uma pergunta importante.&lt;/p&gt;

&lt;p&gt;Se eu tenho vários Executors disponíveis, &lt;strong&gt;como o Spark consegue dividir os dados para processá-los em paralelo?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A resposta começa em um dos conceitos mais importantes — e muitas vezes subestimados — do Spark:&lt;/p&gt;

&lt;h1&gt;
  
  
  Partições
&lt;/h1&gt;

&lt;p&gt;Entender partições mudou bastante a maneira como passei a enxergar processamento distribuído.&lt;/p&gt;

&lt;p&gt;Porque um cluster pode ter dezenas de máquinas, centenas de cores e muita memória.&lt;/p&gt;

&lt;p&gt;Nada disso garante que esses recursos serão utilizados eficientemente.&lt;/p&gt;

&lt;p&gt;No fim das contas, existe uma relação fundamental:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dados
  ↓
Partitions
  ↓
Tasks
  ↓
Parallelism
  ↓
Performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E é isso que vamos explorar.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. O que é uma partição?
&lt;/h1&gt;

&lt;p&gt;Um dataset distribuído não é tratado necessariamente como um único bloco gigantesco.&lt;/p&gt;

&lt;p&gt;Spark divide logicamente os dados em &lt;strong&gt;partições&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine um dataset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dataset
│
├── Partition 0
├── Partition 1
├── Partition 2
└── Partition 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essas partições podem ser processadas paralelamente pelos Executors.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    DATASET

        ┌──────────────┼──────────────┐
        │              │              │
        ▼              ▼              ▼

   Partition 0    Partition 1    Partition 2
        │              │              │
        ▼              ▼              ▼

   Executor A     Executor B     Executor C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso nos leva a uma relação extremamente importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Em uma determinada Stage, cada partição normalmente será processada por uma Task.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Podemos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition 0 ───► Task 0
Partition 1 ───► Task 1
Partition 2 ───► Task 2
Partition 3 ───► Task 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Portanto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partitions
     ↓
   Tasks
     ↓
Parallelism
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa relação ajuda a explicar muita coisa sobre performance no Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Partição não é a mesma coisa que arquivo
&lt;/h1&gt;

&lt;p&gt;Esse é um ponto importante.&lt;/p&gt;

&lt;p&gt;Imagine que temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/data/
├── vendas_01.parquet
├── vendas_02.parquet
├── vendas_03.parquet
└── vendas_04.parquet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É tentador pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 arquivo = 1 partição Spark
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas essa relação não é necessariamente verdadeira.&lt;/p&gt;

&lt;p&gt;A forma como Spark cria partições durante a leitura depende de fatores como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;formato dos dados;&lt;/li&gt;
&lt;li&gt;tamanho dos arquivos;&lt;/li&gt;
&lt;li&gt;fonte de dados;&lt;/li&gt;
&lt;li&gt;configuração da aplicação;&lt;/li&gt;
&lt;li&gt;capacidade de divisão da fonte;&lt;/li&gt;
&lt;li&gt;operações realizadas posteriormente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Além disso, novas operações podem alterar completamente o particionamento.&lt;/p&gt;

&lt;p&gt;Por isso precisamos separar mentalmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ARMAZENAMENTO

Arquivos
   ↓
Blocos / objetos / arquivos físicos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EXECUÇÃO SPARK

Dataset
   ↓
Partitions
   ↓
Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Partições pertencem ao modelo de execução distribuída do Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Descobrindo quantas partições existem
&lt;/h1&gt;

&lt;p&gt;Com RDDs podemos verificar diretamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNumPartitions&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também podemos escolher uma quantidade inicial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora temos, conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 elementos
      │
      ▼
4 partitions
      │
      ├── P0
      ├── P1
      ├── P2
      └── P3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com DataFrames também podemos acessar o RDD subjacente para inspeção:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getNumPartitions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é útil durante análise e troubleshooting.&lt;/p&gt;

&lt;p&gt;Mas saber &lt;strong&gt;quantas&lt;/strong&gt; partições existem é apenas o começo.&lt;/p&gt;

&lt;p&gt;A pergunta realmente importante é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Essas partições estão adequadas para o workload?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  4. Partições determinam oportunidades de paralelismo
&lt;/h1&gt;

&lt;p&gt;Imagine um cluster com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 Executors
4 cores por Executor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos potencialmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 × 4 = 16 cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simplificando, podemos pensar em até 16 Tasks executando simultaneamente, dependendo das configurações e dos recursos disponíveis.&lt;/p&gt;

&lt;p&gt;Agora imagine que nosso Stage possui apenas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teremos aproximadamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1
Task 2
Task 3
Task 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo tendo capacidade para mais execução concorrente.&lt;/p&gt;

&lt;p&gt;Visualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Capacidade disponível

Core 01 → Task
Core 02 → Task
Core 03 → Task
Core 04 → Task

Core 05 → ocioso
Core 06 → ocioso
Core 07 → ocioso
Core 08 → ocioso
...
Core 16 → ocioso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos um cluster relativamente grande, mas pouco paralelismo.&lt;/p&gt;

&lt;p&gt;Agora considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;64 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos ter ondas sucessivas de Tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;16 Tasks
   ↓
16 Tasks
   ↓
16 Tasks
   ↓
16 Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os recursos podem ser utilizados de maneira muito mais eficiente.&lt;/p&gt;

&lt;p&gt;Mas isso não significa que:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quanto mais partições, melhor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Porque existe o outro extremo.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Partições demais também são um problema
&lt;/h1&gt;

&lt;p&gt;Imagine um dataset relativamente pequeno dividido em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100.000 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora Spark precisa administrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100.000 Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo que cada Task processe pouquíssimos dados.&lt;/p&gt;

&lt;p&gt;Cada Task possui algum custo operacional.&lt;/p&gt;

&lt;p&gt;Existe overhead relacionado a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scheduling;&lt;/li&gt;
&lt;li&gt;serialização;&lt;/li&gt;
&lt;li&gt;comunicação;&lt;/li&gt;
&lt;li&gt;inicialização;&lt;/li&gt;
&lt;li&gt;acompanhamento;&lt;/li&gt;
&lt;li&gt;coordenação;&lt;/li&gt;
&lt;li&gt;metadados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Podemos chegar a uma situação como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task
├── 20 ms processando dados
└── overhead operacional significativo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiplique isso por dezenas ou centenas de milhares de Tasks.&lt;/p&gt;

&lt;p&gt;O problema deixa de ser processamento.&lt;/p&gt;

&lt;p&gt;Passa a ser coordenação.&lt;/p&gt;

&lt;p&gt;Esse é um padrão comum em sistemas distribuídos:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Paralelismo tem custo.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  6. Partições grandes demais
&lt;/h1&gt;

&lt;p&gt;Agora vamos para o outro extremo.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dataset: 1 TB
Partitions: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simplificando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition 1 ≈ 250 GB
Partition 2 ≈ 250 GB
Partition 3 ≈ 250 GB
Partition 4 ≈ 250 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso pode gerar vários problemas.&lt;/p&gt;

&lt;p&gt;Cada Task terá uma quantidade enorme de dados para processar.&lt;/p&gt;

&lt;p&gt;Podemos ter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;baixa utilização do cluster;&lt;/li&gt;
&lt;li&gt;pressão de memória;&lt;/li&gt;
&lt;li&gt;spills para disco;&lt;/li&gt;
&lt;li&gt;garbage collection;&lt;/li&gt;
&lt;li&gt;Tasks muito longas;&lt;/li&gt;
&lt;li&gt;maior custo de recuperação em caso de falha.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Visualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cluster

Executor 1 → ████████████████████████
Executor 2 → ████████████████████████
Executor 3 → ████████████████████████
Executor 4 → ████████████████████████

Executor 5 → ocioso
Executor 6 → ocioso
Executor 7 → ocioso
Executor 8 → ocioso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O cluster possui recursos.&lt;/p&gt;

&lt;p&gt;Mas não existem Tasks suficientes para utilizá-los.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Partições pequenas demais
&lt;/h1&gt;

&lt;p&gt;Agora imagine o inverso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dataset: 1 TB
Partitions: 1.000.000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teríamos uma quantidade gigantesca de Tasks.&lt;/p&gt;

&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.000.000 partitions
          ↓
1.000.000 tasks
          ↓
scheduling
          ↓
coordenação
          ↓
overhead
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mesmo que o cluster consiga executar muitas Tasks paralelamente, gerenciar essa quantidade pode ser extremamente caro.&lt;/p&gt;

&lt;p&gt;Portanto, procuramos equilíbrio.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Poucas partitions
       ↓
baixo paralelismo
Tasks muito grandes


Partitions demais
       ↓
overhead excessivo
Tasks muito pequenas


Quantidade adequada
       ↓
bom paralelismo
Tasks administráveis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não existe um número universal perfeito.&lt;/p&gt;

&lt;p&gt;O tamanho adequado depende do workload.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. O problema mais perigoso: partições desbalanceadas
&lt;/h1&gt;

&lt;p&gt;Até agora assumimos algo parecido com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 = 1 GB
P2 = 1 GB
P3 = 1 GB
P4 = 1 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas dados reais raramente são tão bonitos.&lt;/p&gt;

&lt;p&gt;Podemos encontrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 = 500 MB
P2 = 600 MB
P3 = 550 MB
P4 = 18 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora imagine quatro Tasks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1 → 20 segundos
Task 2 → 23 segundos
Task 3 → 21 segundos
Task 4 → 8 minutos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Stage não termina quando as três primeiras Tasks acabam.&lt;/p&gt;

&lt;p&gt;Ele precisa esperar a última.&lt;/p&gt;

&lt;p&gt;Visualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1 ████ ✓
Task 2 █████ ✓
Task 3 ████ ✓
Task 4 ███████████████████████████████████████...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é um sinal clássico de desequilíbrio.&lt;/p&gt;

&lt;p&gt;E uma das causas pode ser:&lt;/p&gt;

&lt;h1&gt;
  
  
  Data Skew
&lt;/h1&gt;




&lt;h1&gt;
  
  
  9. O que é Data Skew?
&lt;/h1&gt;

&lt;p&gt;Imagine dados de vendas agrupados por estado.&lt;/p&gt;

&lt;p&gt;Suponha:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SP → 60 milhões
RJ → 15 milhões
MG → 10 milhões
BA → 5 milhões
AC → 200 mil
RR → 100 mil
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se uma operação distribuir dados utilizando &lt;code&gt;estado&lt;/code&gt; como chave, algumas partições podem ficar muito maiores que outras.&lt;/p&gt;

&lt;p&gt;Podemos terminar com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition SP █████████████████████████████████
Partition RJ █████████
Partition MG ██████
Partition BA ███
Partition AC ▏
Partition RR ▏
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os Executors responsáveis pelas partições pequenas terminam rapidamente.&lt;/p&gt;

&lt;p&gt;Enquanto isso, uma Task continua trabalhando.&lt;/p&gt;

&lt;p&gt;O cluster pode ficar assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor 1 → idle
Executor 2 → idle
Executor 3 → idle
Executor 4 → idle
Executor 5 → █████████████ trabalhando
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse fenômeno é importante porque mostra que:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ter muitas partições não garante paralelismo eficiente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Precisamos também de &lt;strong&gt;boa distribuição dos dados entre elas&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. &lt;code&gt;repartition()&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Spark oferece mecanismos para alterar o particionamento.&lt;/p&gt;

&lt;p&gt;Um deles é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTES

P1
P2
P3
P4

       repartition(100)

              ↓

DEPOIS

P1
P2
P3
...
P100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas existe um detalhe fundamental.&lt;/p&gt;

&lt;p&gt;Redistribuir dados entre partições pode exigir movimentação pela rede.&lt;/p&gt;

&lt;p&gt;Ou seja:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repartition()
      ↓
   Shuffle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor A                     Executor B

[A, A, B, C]                  [A, B, C, C]

             repartition

                   ↓

Executor A                     Executor B

[A, A, A]                     [B, B]
                               [C, C, C]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dados precisaram viajar entre executors.&lt;/p&gt;

&lt;p&gt;E rede não é gratuita.&lt;/p&gt;

&lt;p&gt;Por isso:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;repartition()&lt;/code&gt; não deve ser usado como um botão mágico de performance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ele resolve alguns problemas criando outro custo: &lt;strong&gt;shuffle&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. Repartition por coluna
&lt;/h1&gt;

&lt;p&gt;Também podemos particionar logicamente usando uma ou mais colunas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou especificar quantidade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso pode ser útil quando operações posteriores dependem dessa chave.&lt;/p&gt;

&lt;p&gt;Mas novamente precisamos pensar na distribuição.&lt;/p&gt;

&lt;p&gt;Se &lt;code&gt;customer_id&lt;/code&gt; estiver bem distribuído:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id
     ↓
hash
     ↓
P1 P2 P3 P4 P5 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;podemos conseguir uma distribuição razoável.&lt;/p&gt;

&lt;p&gt;Se a chave tiver forte skew, o resultado pode continuar ruim.&lt;/p&gt;

&lt;p&gt;Particionar não corrige automaticamente uma distribuição ruim.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. &lt;code&gt;coalesce()&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Outro método importante é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normalmente utilizamos &lt;code&gt;coalesce()&lt;/code&gt; quando queremos &lt;strong&gt;reduzir&lt;/strong&gt; a quantidade de partições.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTES

P1 P2 P3 P4 P5 P6 P7 P8

        coalesce(4)

             ↓

DEPOIS

P1 P2 P3 P4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A grande diferença conceitual é que &lt;code&gt;coalesce()&lt;/code&gt; pode reduzir partições evitando uma redistribuição completa dos dados.&lt;/p&gt;

&lt;p&gt;Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repartition()
     ↓
redistribuição ampla
     ↓
shuffle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;coalesce()
     ↓
combinação de partitions
     ↓
pode evitar full shuffle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso pode tornar &lt;code&gt;coalesce()&lt;/code&gt; mais barato quando o objetivo é simplesmente reduzir paralelismo.&lt;/p&gt;

&lt;p&gt;Mas também existe uma consequência.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. O perigo de usar &lt;code&gt;coalesce(1)&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Esse comando aparece bastante:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por quê?&lt;/p&gt;

&lt;p&gt;Normalmente porque alguém quer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Salvar tudo em um único arquivo."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Funciona em determinados cenários.&lt;/p&gt;

&lt;p&gt;Mas imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 partitions
       ↓
coalesce(1)
       ↓
1 partition
       ↓
1 Task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Acabamos de destruir praticamente todo o paralelismo daquela etapa.&lt;/p&gt;

&lt;p&gt;Imagine processar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em uma única partição.&lt;/p&gt;

&lt;p&gt;Podemos transformar um processamento distribuído em um enorme gargalo.&lt;/p&gt;

&lt;p&gt;Por isso, quando encontro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em um pipeline de produção, minha primeira pergunta é:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Por que precisamos de exatamente um arquivo?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Às vezes existe uma justificativa.&lt;/p&gt;

&lt;p&gt;Muitas vezes existe apenas uma dependência legada ou uma decisão que nunca foi questionada.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. &lt;code&gt;repartition()&lt;/code&gt; vs. &lt;code&gt;coalesce()&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Uma forma simples de lembrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                repartition()        coalesce()

Aumentar               ✓                 não é o uso típico

Reduzir                ✓                 ✓

Shuffle          normalmente sim     pode evitar full shuffle

Distribuição        melhor             pode ficar desigual

Custo              maior              geralmente menor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso não significa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;coalesce = bom
repartition = ruim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Significa:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cada operação resolve um problema diferente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Se preciso redistribuir os dados de forma mais uniforme, &lt;code&gt;repartition()&lt;/code&gt; pode ser exatamente o que quero.&lt;/p&gt;

&lt;p&gt;Se tenho muitas partições pequenas e apenas quero consolidá-las, &lt;code&gt;coalesce()&lt;/code&gt; pode ser mais apropriado.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Partições e Shuffle
&lt;/h1&gt;

&lt;p&gt;Agora começamos a conectar os capítulos.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para contar todos os registros do mesmo cliente, Spark precisa garantir que dados da mesma chave possam ser processados conjuntamente.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor A

customer 1
customer 2
customer 3


Executor B

customer 1
customer 4
customer 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Precisamos reorganizar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ANTES

Executor A       Executor B

1                1
2                4
3                2


        ↓ SHUFFLE ↓


DEPOIS

Partition A
1
1

Partition B
2
2

Partition C
3

Partition D
4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É nesse momento que rede, disco, memória e particionamento começam a interagir.&lt;/p&gt;

&lt;p&gt;Por isso shuffle é uma das operações mais caras e importantes de entender no Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. Partições e arquivos pequenos
&lt;/h1&gt;

&lt;p&gt;Particionamento também aparece em outro problema clássico de Data Engineering:&lt;/p&gt;

&lt;h1&gt;
  
  
  Small Files Problem
&lt;/h1&gt;

&lt;p&gt;Imagine um pipeline que grava:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/year=2026/month=10/day=23/

part-000001.parquet
part-000002.parquet
part-000003.parquet
...
part-200000.parquet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos centenas de milhares de arquivos minúsculos.&lt;/p&gt;

&lt;p&gt;Isso pode prejudicar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;leitura;&lt;/li&gt;
&lt;li&gt;listagem de arquivos;&lt;/li&gt;
&lt;li&gt;planejamento;&lt;/li&gt;
&lt;li&gt;metadados;&lt;/li&gt;
&lt;li&gt;armazenamento;&lt;/li&gt;
&lt;li&gt;engines de consulta;&lt;/li&gt;
&lt;li&gt;Data Lakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Muitas vezes a quantidade de arquivos gerados está relacionada à quantidade de partições no momento da escrita.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As partições do DataFrame influenciam a quantidade de tarefas de escrita e, consequentemente, podem influenciar a quantidade de arquivos produzidos.&lt;/p&gt;

&lt;p&gt;Isso conecta performance computacional com arquitetura de armazenamento.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. Particionamento de execução ≠ particionamento de armazenamento
&lt;/h1&gt;

&lt;p&gt;Aqui existe uma distinção importante.&lt;/p&gt;

&lt;p&gt;Podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso está relacionado ao particionamento do DataFrame durante a execução.&lt;/p&gt;

&lt;p&gt;Mas também podemos escrever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt; \
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partitionBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ano&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/vendas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora estamos falando de organização física/lógica dos dados no armazenamento:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/data/vendas/

ano=2025/
    mes=12/

ano=2026/
    mes=01/

ano=2026/
    mes=02/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;São conceitos relacionados, mas diferentes.&lt;/p&gt;

&lt;p&gt;Uma forma útil de separar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repartition()
     ↓
EXECUÇÃO


partitionBy()
     ↓
ARMAZENAMENTO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confundir os dois pode levar a decisões ruins de arquitetura.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. E quanto ao tamanho ideal de uma partição?
&lt;/h1&gt;

&lt;p&gt;Essa é uma pergunta clássica:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quantos MB uma partição Spark deveria ter?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A resposta correta é:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;depende.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Você encontrará recomendações práticas em diferentes contextos, frequentemente trabalhando com partições na ordem de dezenas ou centenas de megabytes.&lt;/p&gt;

&lt;p&gt;Mas transformar um número em regra universal é perigoso.&lt;/p&gt;

&lt;p&gt;Precisamos considerar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tipo de operação;&lt;/li&gt;
&lt;li&gt;volume de dados;&lt;/li&gt;
&lt;li&gt;formato;&lt;/li&gt;
&lt;li&gt;compressão;&lt;/li&gt;
&lt;li&gt;memória dos Executors;&lt;/li&gt;
&lt;li&gt;quantidade de cores;&lt;/li&gt;
&lt;li&gt;número de Executors;&lt;/li&gt;
&lt;li&gt;custo de serialização;&lt;/li&gt;
&lt;li&gt;skew;&lt;/li&gt;
&lt;li&gt;shuffle;&lt;/li&gt;
&lt;li&gt;origem dos dados;&lt;/li&gt;
&lt;li&gt;destino dos dados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Em vez de decorar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition = X MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;prefiro pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A Task termina rápido demais?
        ↓
Talvez partitions pequenas demais


A Task demora excessivamente?
        ↓
Talvez partition grande


Algumas Tasks demoram muito mais?
        ↓
Talvez skew


Muitos cores ociosos?
        ↓
Talvez poucas partitions


Scheduler sobrecarregado?
        ↓
Talvez partitions demais
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Spark UI ajuda muito nessa análise.&lt;/p&gt;




&lt;h1&gt;
  
  
  19. Um exemplo prático
&lt;/h1&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dataset = 800 GB

Cluster:
10 Executors
4 cores por Executor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos aproximadamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;40 cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora suponha:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos no máximo oito Tasks para aquela etapa.&lt;/p&gt;

&lt;p&gt;Então:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;40 cores disponíveis
 8 Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grande parte do cluster ficará ociosa.&lt;/p&gt;

&lt;p&gt;Agora imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;800 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Teríamos aproximadamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 GB por partition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ainda pode ser grande dependendo da operação.&lt;/p&gt;

&lt;p&gt;Se utilizarmos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3200 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;teríamos aproximadamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;250 MB por partition
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso poderia ser um ponto inicial interessante para investigação.&lt;/p&gt;

&lt;p&gt;Mas atenção:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;não estou dizendo que 3200 é a configuração correta.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Estou mostrando como raciocinar.&lt;/p&gt;

&lt;p&gt;A validação real vem das métricas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark UI
   │
   ├── Task Duration
   ├── Input Size
   ├── Shuffle Read
   ├── Shuffle Write
   ├── Spill
   ├── GC Time
   └── Executor Utilization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Engenharia de performance não deveria ser:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Vi alguém usando 3200 partitions."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Deveria ser:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Escolhi um ponto inicial, medi o comportamento e ajustei baseado em evidências."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  20. O número de cores não conta toda a história
&lt;/h1&gt;

&lt;p&gt;Suponha:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executors: 10
Cores por Executor: 4

Total: 40 cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos pensar inicialmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;40 cores
   ↓
até aproximadamente 40 Tasks simultâneas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas isso não significa que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;40 partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;é necessariamente ideal.&lt;/p&gt;

&lt;p&gt;Por quê?&lt;/p&gt;

&lt;p&gt;Porque se temos exatamente 40 Tasks e uma delas é muito lenta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;39 Tasks → 30 segundos
 1 Task  → 8 minutos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;depois dos primeiros 30 segundos grande parte do cluster pode ficar ociosa esperando aquela última Task.&lt;/p&gt;

&lt;p&gt;Ter múltiplas ondas de Tasks pode ajudar na utilização dos recursos.&lt;/p&gt;

&lt;p&gt;Novamente:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;não existe uma fórmula universal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Existe análise de workload.&lt;/p&gt;




&lt;h1&gt;
  
  
  21. Quando particionamento vira arquitetura
&lt;/h1&gt;

&lt;p&gt;Esse assunto parece inicialmente uma otimização de baixo nível.&lt;/p&gt;

&lt;p&gt;Mas não é.&lt;/p&gt;

&lt;p&gt;Particionamento influencia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    PARTITIONING
                         │
          ┌──────────────┼──────────────┐
          │              │              │
          ▼              ▼              ▼
      Parallelism      Memory         Network
          │              │              │
          ▼              ▼              ▼
        Tasks          Spill          Shuffle
          │              │              │
          └──────────────┼──────────────┘
                         ▼
                    PERFORMANCE
                         │
                         ▼
                       COST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em cloud isso fica ainda mais importante.&lt;/p&gt;

&lt;p&gt;Um pipeline ineficiente não significa apenas:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"demorou mais."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pode significar:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"custou mais."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Se um cluster fica ativo durante duas horas em vez de vinte minutos, existe impacto financeiro.&lt;/p&gt;

&lt;p&gt;Por isso performance também é uma preocupação de arquitetura e FinOps.&lt;/p&gt;




&lt;h1&gt;
  
  
  22. Um exemplo de raciocínio de troubleshooting
&lt;/h1&gt;

&lt;p&gt;Imagine que um Job demora 45 minutos.&lt;/p&gt;

&lt;p&gt;No Spark UI encontramos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage 8

Tasks: 500
Median Duration: 20 s
Max Duration: 31 min
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso é um enorme sinal.&lt;/p&gt;

&lt;p&gt;Não começaria aumentando memória aleatoriamente.&lt;/p&gt;

&lt;p&gt;Perguntaria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Por que uma Task levou 31 minutos
quando a mediana foi 20 segundos?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possíveis hipóteses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task lenta
   │
   ├── partition muito grande?
   ├── data skew?
   ├── shuffle excessivo?
   ├── spill para disco?
   ├── garbage collection?
   ├── problema no executor?
   └── problema de I/O?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse é o tipo de raciocínio que quero desenvolver ao longo desta série.&lt;/p&gt;

&lt;p&gt;Não decorar configurações.&lt;/p&gt;

&lt;p&gt;Mas saber &lt;strong&gt;onde procurar&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  23. &lt;code&gt;repartition()&lt;/code&gt; também pode piorar tudo
&lt;/h1&gt;

&lt;p&gt;Imagine um pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/events&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event_type = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;purchase&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Talvez alguém tenha adicionado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;porque:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Mais partições deixam Spark mais rápido."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mas acabamos de introduzir potencialmente uma redistribuição cara antes mesmo da operação principal.&lt;/p&gt;

&lt;p&gt;Podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read
 ↓
Repartition
 ↓
SHUFFLE
 ↓
Filter
 ↓
GroupBy
 ↓
SHUFFLE
 ↓
Aggregation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Talvez tenhamos criado dois movimentos grandes de dados onde não precisávamos.&lt;/p&gt;

&lt;p&gt;Esse é um exemplo perfeito de uma regra importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Otimização sem compreender o plano de execução pode virar desotimização.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  24. O melhor &lt;code&gt;repartition()&lt;/code&gt; às vezes é nenhum
&lt;/h1&gt;

&lt;p&gt;Antes de escrever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;eu perguntaria:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qual problema estou tentando resolver?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Se a resposta for:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Não sei, mas acho que melhora performance."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Não é uma boa justificativa.&lt;/p&gt;

&lt;p&gt;Perguntas melhores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tenho poucas partitions?

Tenho muitas?

Existe skew?

Preciso distribuir por determinada chave?

Estou preparando os dados para um join?

Estou gerando small files?

Existe um shuffle inevitável logo depois?

Qual é o plano físico?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Só depois escolheria a estratégia.&lt;/p&gt;




&lt;h1&gt;
  
  
  25. O que mudou no Spark moderno
&lt;/h1&gt;

&lt;p&gt;Existe uma diferença importante entre estudar Spark historicamente e trabalhar com versões modernas.&lt;/p&gt;

&lt;p&gt;Hoje o Spark possui mecanismos muito mais sofisticados de otimização.&lt;/p&gt;

&lt;p&gt;Um deles é o:&lt;/p&gt;

&lt;h1&gt;
  
  
  Adaptive Query Execution — AQE
&lt;/h1&gt;

&lt;p&gt;Com AQE, Spark pode utilizar informações coletadas durante a própria execução para ajustar partes do plano.&lt;/p&gt;

&lt;p&gt;Entre outras capacidades, isso pode ajudar com:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;coalescing de shuffle partitions;&lt;/li&gt;
&lt;li&gt;otimização de joins;&lt;/li&gt;
&lt;li&gt;tratamento de determinados cenários de skew.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Isso representa uma evolução enorme.&lt;/p&gt;

&lt;p&gt;Historicamente, muitas decisões precisavam ser tomadas antecipadamente com informações limitadas.&lt;/p&gt;

&lt;p&gt;Hoje Spark consegue adaptar determinadas decisões durante a execução.&lt;/p&gt;

&lt;p&gt;Mas isso não elimina a necessidade de entender partições.&lt;/p&gt;

&lt;p&gt;Na verdade, torna o conhecimento ainda mais útil.&lt;/p&gt;

&lt;p&gt;Porque agora podemos entender &lt;strong&gt;o que o engine está tentando otimizar por nós&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  26. Como penso em partições hoje
&lt;/h1&gt;

&lt;p&gt;Meu modelo mental é aproximadamente este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  DATASET
                     │
                     ▼
                PARTITIONS
                     │
          ┌──────────┼──────────┐
          │          │          │
          ▼          ▼          ▼
       Size       Balance      Count
          │          │          │
          └──────────┼──────────┘
                     ▼
                   TASKS
                     │
                     ▼
                PARALLELISM
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
         CPU       Memory     Network
          │          │          │
          └──────────┼──────────┘
                     ▼
                 PERFORMANCE
                     │
                     ▼
                    COST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não penso:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Qual número de partitions devo usar?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Penso:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Como os dados estão sendo distribuídos e como isso afeta a execução?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa é uma pergunta muito melhor.&lt;/p&gt;




&lt;h1&gt;
  
  
  27. Checklist rápido de particionamento
&lt;/h1&gt;

&lt;p&gt;Quando encontro problemas de performance, algumas perguntas ajudam bastante:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quantas partições existem?&lt;/li&gt;
&lt;li&gt;Qual o tamanho médio?&lt;/li&gt;
&lt;li&gt;Qual o tamanho máximo?&lt;/li&gt;
&lt;li&gt;As Tasks possuem duração semelhante?&lt;/li&gt;
&lt;li&gt;Existe uma ou poucas Tasks muito mais lentas?&lt;/li&gt;
&lt;li&gt;Todos os cores estão sendo utilizados?&lt;/li&gt;
&lt;li&gt;Existe shuffle?&lt;/li&gt;
&lt;li&gt;Quanto está sendo lido e escrito no shuffle?&lt;/li&gt;
&lt;li&gt;Existe spill para disco?&lt;/li&gt;
&lt;li&gt;Há indícios de data skew?&lt;/li&gt;
&lt;li&gt;Algum &lt;code&gt;repartition()&lt;/code&gt; foi adicionado sem necessidade?&lt;/li&gt;
&lt;li&gt;Existe &lt;code&gt;coalesce(1)&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Estamos gerando arquivos pequenos demais?&lt;/li&gt;
&lt;li&gt;O problema está na execução ou no layout do armazenamento?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essas perguntas transformam:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Meu Spark está lento."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;em uma investigação muito mais objetiva.&lt;/p&gt;




&lt;h1&gt;
  
  
  28. A principal lição deste capítulo
&lt;/h1&gt;

&lt;p&gt;Se eu pudesse resumir tudo em uma frase:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;O cluster fornece capacidade. As partições determinam quanto dessa capacidade conseguimos explorar.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Um cluster enorme com particionamento ruim continua sendo um sistema mal utilizado.&lt;/p&gt;

&lt;p&gt;Podemos ter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 CPUs disponíveis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e apenas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4 Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100 CPUs disponíveis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.000.000 Tasks minúsculas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nos dois casos existe desperdício.&lt;/p&gt;

&lt;p&gt;O objetivo é encontrar uma distribuição que permita ao Spark executar trabalho suficiente em paralelo sem transformar coordenação, memória ou rede no novo gargalo.&lt;/p&gt;




&lt;h1&gt;
  
  
  Próximo artigo
&lt;/h1&gt;

&lt;p&gt;Neste capítulo apareceu repetidamente uma palavra:&lt;/p&gt;

&lt;h1&gt;
  
  
  Shuffle
&lt;/h1&gt;

&lt;p&gt;Ela surgiu em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;repartition&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;reduceByKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e também aparecerá quando falarmos sobre:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shuffle é uma das operações mais importantes para entender performance em Apache Spark.&lt;/p&gt;

&lt;p&gt;No próximo artigo vamos descobrir:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;o que realmente acontece durante um shuffle;&lt;/li&gt;
&lt;li&gt;por que dados precisam atravessar a rede;&lt;/li&gt;
&lt;li&gt;narrow vs. wide dependencies;&lt;/li&gt;
&lt;li&gt;shuffle read;&lt;/li&gt;
&lt;li&gt;shuffle write;&lt;/li&gt;
&lt;li&gt;agregações;&lt;/li&gt;
&lt;li&gt;joins;&lt;/li&gt;
&lt;li&gt;impacto sobre memória e disco;&lt;/li&gt;
&lt;li&gt;spill;&lt;/li&gt;
&lt;li&gt;e por que reduzir movimentação de dados costuma ser uma das melhores otimizações possíveis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vamos seguir o fluxo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partitions
     ↓
Wide Transformation
     ↓
Shuffle Write
     ↓
Network
     ↓
Shuffle Read
     ↓
New Partitions
     ↓
Next Stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É aqui que começamos a entrar de verdade em &lt;strong&gt;performance tuning de Spark&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;[← Parte 2: Por dentro da arquitetura — Driver, Executors, Jobs, Stages e Tasks]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Próximo:&lt;/strong&gt; Parte 4 — Shuffle no Apache Spark: onde a performance começa a ficar cara&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>bigdata</category>
      <category>pyspark</category>
      <category>spark</category>
    </item>
    <item>
      <title>Próximo: Parte 2 — Por dentro da arquitetura do Apache Spark: Driver, Executors, Jobs, Stages e Tasks</title>
      <dc:creator>renanpyd</dc:creator>
      <pubDate>Fri, 23 Oct 2026 13:54:36 +0000</pubDate>
      <link>https://dev.to/renanpyd/proximo-parte-2-por-dentro-da-arquitetura-do-apache-spark-driver-executors-jobs-stages-e-3eii</link>
      <guid>https://dev.to/renanpyd/proximo-parte-2-por-dentro-da-arquitetura-do-apache-spark-driver-executors-jobs-stages-e-3eii</guid>
      <description>&lt;p&gt;No artigo anterior, vimos por que o Apache Spark chamou tanta atenção no cenário de processamento distribuído e passamos pelos conceitos de &lt;strong&gt;RDDs, lineage, transformations, actions e lazy evaluation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mas uma pergunta ficou aberta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quando executamos uma aplicação Spark, quem realmente faz o trabalho?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Quando comecei a estudar Spark, entender essa arquitetura foi um divisor de águas.&lt;/p&gt;

&lt;p&gt;Até então, código como este parecia simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dados.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;palavra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palavra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduceByKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas existe uma diferença enorme entre entender &lt;strong&gt;o que esse código faz&lt;/strong&gt; e entender &lt;strong&gt;como ele é executado em um cluster&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Por trás dessas poucas linhas existe uma arquitetura envolvendo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    │
    ▼
Driver
    │
    ▼
Cluster Manager
    │
    ├─────────────┬─────────────┐
    ▼             ▼             ▼
Executor 1    Executor 2    Executor 3
    │             │             │
   Tasks         Tasks         Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E é essa arquitetura que vamos desmontar neste artigo.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Começando pelo todo: o que é uma aplicação Spark?
&lt;/h1&gt;

&lt;p&gt;Uma aplicação Spark é composta, de maneira simplificada, por dois grandes elementos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Driver&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Executors&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Entre eles, normalmente temos um &lt;strong&gt;Cluster Manager&lt;/strong&gt;, responsável pela alocação dos recursos do cluster.&lt;/p&gt;

&lt;p&gt;Podemos pensar inicialmente assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 SPARK APPLICATION

                      Driver
                        │
                        ▼
                 Cluster Manager
                        │
             ┌──────────┼──────────┐
             │          │          │
             ▼          ▼          ▼
         Executor    Executor    Executor
             │          │          │
          Tasks       Tasks       Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa imagem mental é importante.&lt;/p&gt;

&lt;p&gt;O &lt;strong&gt;Driver coordena&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Os &lt;strong&gt;Executors executam&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mas vamos detalhar cada parte.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Driver: o cérebro da aplicação
&lt;/h1&gt;

&lt;p&gt;O &lt;strong&gt;Driver&lt;/strong&gt; é o processo responsável por coordenar a aplicação Spark.&lt;/p&gt;

&lt;p&gt;É nele que o código principal da aplicação é executado.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;

&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MinhaAplicacao&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dados/vendas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse programa não simplesmente "vai para o cluster".&lt;/p&gt;

&lt;p&gt;Existe um processo central responsável por interpretar a aplicação, construir planos de execução e coordenar o trabalho distribuído.&lt;/p&gt;

&lt;p&gt;Esse processo é o &lt;strong&gt;Driver&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;De maneira simplificada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    DRIVER

              Código da aplicação
                      │
                      ▼
             Planejamento do trabalho
                      │
                      ▼
             Divisão da execução
                      │
                      ▼
              Envio de Tasks
                      │
                      ▼
            Acompanhamento do Job
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Driver precisa saber:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;quais transformações foram definidas;&lt;/li&gt;
&lt;li&gt;quais ações precisam ser executadas;&lt;/li&gt;
&lt;li&gt;quais recursos estão disponíveis;&lt;/li&gt;
&lt;li&gt;quais executors estão ativos;&lt;/li&gt;
&lt;li&gt;quais tasks precisam ser executadas;&lt;/li&gt;
&lt;li&gt;quais tasks terminaram;&lt;/li&gt;
&lt;li&gt;quais falharam.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Por isso, uma regra importante começa a aparecer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;O Driver não é apenas "mais uma máquina" do cluster. Ele é o coordenador da aplicação.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  3. SparkContext: a porta de entrada histórica
&lt;/h1&gt;

&lt;p&gt;Nos primeiros anos do Spark, um dos objetos mais importantes era o:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SparkContext&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em PySpark, era comum encontrarmos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkContext&lt;/span&gt;

&lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SparkContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;local[*]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MinhaAplicacao&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E então:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dados.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O &lt;code&gt;SparkContext&lt;/code&gt; representava a conexão da aplicação com o ambiente de execução Spark.&lt;/p&gt;

&lt;p&gt;Podemos visualizar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aplicação
    │
    ▼
SparkContext
    │
    ▼
Cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hoje, normalmente começamos por uma abstração de nível mais alto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pyspark.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SparkSession&lt;/span&gt;

&lt;span class="n"&gt;spark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SparkSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MinhaAplicacao&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E podemos acessar o contexto subjacente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sparkContext&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa mudança é interessante porque mostra uma evolução importante da própria plataforma.&lt;/p&gt;

&lt;p&gt;Historicamente, aprender Spark significava começar por:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SparkContext
      ↓
     RDD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hoje é muito mais comum começar por:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SparkSession
      ↓
 DataFrame / SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os fundamentos distribuídos, entretanto, continuam existindo.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Cluster Manager: quem distribui os recursos?
&lt;/h1&gt;

&lt;p&gt;O Driver precisa de máquinas para executar o trabalho.&lt;/p&gt;

&lt;p&gt;É aí que entra o &lt;strong&gt;Cluster Manager&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sua função é gerenciar recursos disponíveis no cluster.&lt;/p&gt;

&lt;p&gt;Historicamente, Spark podia trabalhar com diferentes gerenciadores, como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spark Standalone;&lt;/li&gt;
&lt;li&gt;Apache Mesos;&lt;/li&gt;
&lt;li&gt;Hadoop YARN.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hoje também encontramos Spark executado em diferentes ambientes e plataformas gerenciadas.&lt;/p&gt;

&lt;p&gt;Conceitualmente, porém, podemos pensar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Driver
   │
   │ "Preciso de recursos"
   ▼
Cluster Manager
   │
   ├─────────────┬─────────────┐
   ▼             ▼             ▼
Máquina A     Máquina B     Máquina C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Cluster Manager disponibiliza recursos para que os &lt;strong&gt;Executors&lt;/strong&gt; possam ser iniciados.&lt;/p&gt;

&lt;p&gt;Uma distinção importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cluster Manager e Executor não são a mesma coisa.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O Cluster Manager administra recursos.&lt;/p&gt;

&lt;p&gt;Os Executors realizam o processamento da aplicação.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Executors: onde o trabalho acontece
&lt;/h1&gt;

&lt;p&gt;Os &lt;strong&gt;Executors&lt;/strong&gt; são processos responsáveis por executar as tarefas da aplicação Spark.&lt;/p&gt;

&lt;p&gt;Imagine um cluster com três executors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                      DRIVER
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
      EXECUTOR 1      EXECUTOR 2      EXECUTOR 3
          │              │              │
      Task 1          Task 2          Task 3
      Task 4          Task 5          Task 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada Executor possui recursos como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor
├── CPU / cores
├── memória
├── tasks
└── dados armazenados/cacheados
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando configuramos recursos para uma aplicação Spark, começamos a encontrar parâmetros relacionados justamente a essa arquitetura.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark.executor.memory
spark.executor.cores
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso deixa muito mais claro o que significa dizer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Meu executor está ficando sem memória."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Estamos falando de um processo específico responsável por executar partes da computação distribuída.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Driver vs. Executor
&lt;/h1&gt;

&lt;p&gt;Essa diferença é fundamental.&lt;/p&gt;

&lt;p&gt;Podemos resumir assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DRIVER
│
├── executa o programa principal
├── coordena a aplicação
├── planeja o trabalho
├── agenda tarefas
└── acompanha a execução


EXECUTOR
│
├── executa tasks
├── processa partições
├── mantém dados quando necessário
└── devolve resultados/status ao Driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa separação ajuda a entender vários erros.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que &lt;code&gt;collect()&lt;/code&gt; faz?&lt;/p&gt;

&lt;p&gt;Ele coleta os resultados distribuídos e os envia para o &lt;strong&gt;Driver&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine um DataFrame com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;500 GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;distribuídos entre vários executors.&lt;/p&gt;

&lt;p&gt;Se tentarmos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;conceitualmente estamos pedindo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor 1 ───┐
Executor 2 ───┤
Executor 3 ───┼───► DRIVER
Executor 4 ───┤
Executor 5 ───┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora imagine tentar colocar centenas de gigabytes no processo do Driver.&lt;/p&gt;

&lt;p&gt;Temos um problema.&lt;/p&gt;

&lt;p&gt;É por isso que um comando aparentemente inocente como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pode ser extremamente perigoso em produção.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Partições: a unidade que conecta dados e paralelismo
&lt;/h1&gt;

&lt;p&gt;No artigo anterior vimos que um RDD pode ser dividido em partições.&lt;/p&gt;

&lt;p&gt;Agora isso começa a fazer mais sentido.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dataset
   │
   ├── Partition 1
   ├── Partition 2
   ├── Partition 3
   └── Partition 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essas partições permitem distribuir o processamento.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partition 1 ───► Executor A
Partition 2 ───► Executor A
Partition 3 ───► Executor B
Partition 4 ───► Executor B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em uma determinada etapa da execução, uma &lt;strong&gt;task trabalha tipicamente sobre uma partição&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Essa relação é extremamente importante:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage
  │
  ├── Partition 1 ──► Task 1
  ├── Partition 2 ──► Task 2
  ├── Partition 3 ──► Task 3
  └── Partition 4 ──► Task 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E daqui surge uma das ideias mais importantes para performance:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;O número e o tamanho das partições influenciam diretamente o paralelismo da aplicação.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vamos aprofundar isso em um artigo específico.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Action → Job
&lt;/h1&gt;

&lt;p&gt;No artigo anterior vimos a diferença entre &lt;strong&gt;Transformations&lt;/strong&gt; e &lt;strong&gt;Actions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dados.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;filtrado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filtrado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;textFile()
    │
    ▼
filter()
    │
    ▼
count()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;filter()&lt;/code&gt; é uma transformation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;count()&lt;/code&gt; é uma action.&lt;/p&gt;

&lt;p&gt;Quando uma Action precisa ser executada, Spark dispara computação para produzir aquele resultado.&lt;/p&gt;

&lt;p&gt;Isso normalmente resulta na criação de um:&lt;/p&gt;

&lt;h1&gt;
  
  
  Job
&lt;/h1&gt;

&lt;p&gt;Uma forma útil de construir o modelo mental é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ACTION
   │
   ▼
 JOB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas o Job ainda pode ser grande demais para ser executado como uma única unidade.&lt;/p&gt;

&lt;p&gt;Então Spark o divide.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Job → Stages
&lt;/h1&gt;

&lt;p&gt;Um &lt;strong&gt;Job&lt;/strong&gt; é dividido em &lt;strong&gt;Stages&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mas Spark não divide os stages aleatoriamente.&lt;/p&gt;

&lt;p&gt;As fronteiras aparecem principalmente quando determinadas operações exigem redistribuição de dados entre partições.&lt;/p&gt;

&lt;p&gt;Ou seja:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;shuffle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dados.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;rdd&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;palavra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palavra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduceByKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma representação simplificada seria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STAGE 1

textFile
   │
flatMap
   │
 map
   │
   ▼

======= SHUFFLE =======

   │
   ▼

STAGE 2

reduceByKey
   │
collect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por quê?&lt;/p&gt;

&lt;p&gt;Porque &lt;code&gt;reduceByKey()&lt;/code&gt; precisa reunir valores que possuem a mesma chave.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor A:
spark -&amp;gt; 1
data  -&amp;gt; 1

Executor B:
spark -&amp;gt; 1
python -&amp;gt; 1

Executor C:
data  -&amp;gt; 1
spark -&amp;gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para somar todos os valores de &lt;code&gt;spark&lt;/code&gt;, os registros precisam ser reorganizados.&lt;/p&gt;

&lt;p&gt;Algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spark -&amp;gt; 1 ──┐
spark -&amp;gt; 1 ──┼──► spark -&amp;gt; 3
spark -&amp;gt; 1 ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa movimentação distribuída é um &lt;strong&gt;shuffle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;E shuffles são tão importantes que terão um artigo próprio nesta série.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Stage → Tasks
&lt;/h1&gt;

&lt;p&gt;Agora chegamos à menor unidade prática de execução que precisamos entender neste momento:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Task&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Um Stage é composto por várias Tasks.&lt;/p&gt;

&lt;p&gt;Exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage 1
│
├── Task 1 → Partition 1
├── Task 2 → Partition 2
├── Task 3 → Partition 3
└── Task 4 → Partition 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essas tasks podem ser distribuídas entre os Executors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    STAGE

        ┌─────────────┼─────────────┐
        │             │             │
        ▼             ▼             ▼

    Executor A    Executor B    Executor C

      Task 1        Task 2        Task 3
      Task 4        Task 5        Task 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora começamos a enxergar o caminho completo.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. O caminho completo de uma aplicação Spark
&lt;/h1&gt;

&lt;p&gt;Vamos juntar tudo.&lt;/p&gt;

&lt;p&gt;Temos nosso programa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dados.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;palavra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palavra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduceByKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    APPLICATION
                         │
                         ▼
                       DRIVER
                         │
                         ▼
                       ACTION
                         │
                         ▼
                        JOB
                         │
                 ┌───────┴───────┐
                 ▼               ▼
              STAGE 1          STAGE 2
                 │               │
            ┌────┼────┐      ┌───┼───┐
            ▼    ▼    ▼      ▼   ▼   ▼
           Task Task Task    Task Task Task
            │    │    │      │   │   │
            └────┼────┘      └───┼───┘
                 │               │
                 ▼               ▼
              EXECUTORS       EXECUTORS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E agora temos uma hierarquia muito útil:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
Action
    ↓
Job
    ↓
Stages
    ↓
Tasks
    ↓
Partitions
    ↓
Executors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não trate esse desenho como uma equivalência matemática perfeita entre todos os elementos.&lt;/p&gt;

&lt;p&gt;Ele é um &lt;strong&gt;modelo mental para entender a execução&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  12. Um exemplo mais próximo do Spark moderno
&lt;/h1&gt;

&lt;p&gt;Vamos considerar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/vendas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor &amp;gt; 100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O código parece simples.&lt;/p&gt;

&lt;p&gt;Mas internamente precisamos pensar em algo próximo de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Leitura
  │
  ▼
Filter
  │
  ▼
GroupBy
  │
  ▼
Shuffle
  │
  ▼
Aggregation
  │
  ▼
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Distribuído entre:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Driver
  │
  ├──────────┬──────────┬──────────┐
  ▼          ▼          ▼          ▼
Executor   Executor   Executor   Executor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É por isso que duas linhas aparentemente semelhantes podem ter comportamentos de performance completamente diferentes.&lt;/p&gt;

&lt;p&gt;Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valor &amp;gt; 100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cliente_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A primeira pode ser executada localmente sobre cada partição.&lt;/p&gt;

&lt;p&gt;A segunda pode exigir redistribuição dos registros pela chave &lt;code&gt;cliente_id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Em outras palavras:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;o código parece simples, mas a topologia de execução pode ser completamente diferente.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  13. Narrow vs. Wide Transformations
&lt;/h1&gt;

&lt;p&gt;Aqui aparece outra distinção extremamente importante.&lt;/p&gt;

&lt;h2&gt;
  
  
  Narrow Transformation
&lt;/h2&gt;

&lt;p&gt;Uma transformação é considerada &lt;strong&gt;narrow&lt;/strong&gt; quando cada partição de saída depende de um conjunto limitado de partições anteriores, frequentemente uma única partição.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos imaginar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 ───► P1'
P2 ───► P2'
P3 ───► P3'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não precisamos necessariamente redistribuir todos os dados pela rede.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wide Transformation
&lt;/h2&gt;

&lt;p&gt;Uma &lt;strong&gt;wide transformation&lt;/strong&gt; pode exigir dados provenientes de várias partições anteriores.&lt;/p&gt;

&lt;p&gt;Exemplo clássico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduceByKey&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos imaginar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P1 ──┐     ┌──► P1'
     ├─────┤
P2 ──┤     ├──► P2'
     ├─────┤
P3 ──┘     └──► P3'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora existe movimentação entre partições.&lt;/p&gt;

&lt;p&gt;Isso geralmente significa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wide Transformation
        ↓
     Shuffle
        ↓
Nova fronteira de Stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa relação é extremamente importante para otimização.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. Agora o Spark UI começa a fazer sentido
&lt;/h1&gt;

&lt;p&gt;Quando abrimos o &lt;strong&gt;Spark UI&lt;/strong&gt;, encontramos termos como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Jobs
Stages
Tasks
Executors
Storage
SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sem entender a arquitetura, essas telas parecem apenas um painel cheio de métricas.&lt;/p&gt;

&lt;p&gt;Depois de entender o modelo de execução, elas começam a contar uma história.&lt;/p&gt;

&lt;p&gt;Imagine encontrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage 12

Tasks: 200
Completed: 199
Running: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma pergunta interessante aparece:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Por que uma única task está demorando muito mais do que as outras 199?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Talvez exista:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uma partição muito maior;&lt;/li&gt;
&lt;li&gt;data skew;&lt;/li&gt;
&lt;li&gt;garbage collection;&lt;/li&gt;
&lt;li&gt;spill para disco;&lt;/li&gt;
&lt;li&gt;gargalo de I/O;&lt;/li&gt;
&lt;li&gt;problema de rede.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agora imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor 3
Memory: 95%
GC Time: alto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outra hipótese aparece:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Talvez esse executor esteja sofrendo pressão de memória.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;É aqui que Spark deixa de ser apenas uma API.&lt;/p&gt;

&lt;p&gt;Começamos a pensar como &lt;strong&gt;engenheiros de sistemas distribuídos&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. Driver também pode virar gargalo
&lt;/h1&gt;

&lt;p&gt;Muita gente olha apenas para os Executors.&lt;/p&gt;

&lt;p&gt;Mas o Driver também possui recursos limitados.&lt;/p&gt;

&lt;p&gt;Um exemplo clássico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toPandas&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dependendo do tamanho do dataset, estamos tentando trazer uma quantidade enorme de dados distribuídos para um único processo.&lt;/p&gt;

&lt;p&gt;Visualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              100 GB distribuídos

Executor 1 ─────┐
Executor 2 ─────┤
Executor 3 ─────┼────► DRIVER
Executor 4 ─────┤
Executor 5 ─────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O cluster inteiro pode ter muita memória.&lt;/p&gt;

&lt;p&gt;Isso não significa que o Driver tenha memória suficiente para receber tudo.&lt;/p&gt;

&lt;p&gt;Essa distinção parece simples depois que aprendemos.&lt;/p&gt;

&lt;p&gt;Antes disso, é uma fonte comum de problemas.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. Mais executors não significa automaticamente mais performance
&lt;/h1&gt;

&lt;p&gt;Outro aprendizado importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Distribuição não elimina custo.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Adicionar máquinas pode aumentar paralelismo.&lt;/p&gt;

&lt;p&gt;Mas também podemos aumentar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;comunicação;&lt;/li&gt;
&lt;li&gt;serialização;&lt;/li&gt;
&lt;li&gt;coordenação;&lt;/li&gt;
&lt;li&gt;shuffle;&lt;/li&gt;
&lt;li&gt;overhead de scheduling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine um dataset pequeno distribuído entre dezenas de executors.&lt;/p&gt;

&lt;p&gt;Talvez o custo de coordenar todo esse ambiente seja maior do que o benefício.&lt;/p&gt;

&lt;p&gt;Por outro lado, poucas partições em um cluster enorme também desperdiçam recursos.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cluster:

Executor 1  ████████
Executor 2  ████████
Executor 3  ████████
Executor 4  ████████
Executor 5  ████████
Executor 6  ████████
Executor 7  ████████
Executor 8  ████████

Dataset:

Partition 1
Partition 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não importa termos oito executors.&lt;/p&gt;

&lt;p&gt;Se determinada etapa possui apenas duas tasks disponíveis, boa parte dos recursos ficará ociosa.&lt;/p&gt;

&lt;p&gt;Daí surge uma relação fundamental:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PARTITIONS
    ↓
 TASKS
    ↓
PARALLELISM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E é exatamente por isso que particionamento merece tanta atenção.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. O modelo mental que eu gostaria de ter aprendido primeiro
&lt;/h1&gt;

&lt;p&gt;Se eu pudesse resumir toda essa arquitetura em um único desenho, seria este:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌───────────────────────┐
                    │      APPLICATION      │
                    └───────────┬───────────┘
                                │
                                ▼
                    ┌───────────────────────┐
                    │        DRIVER         │
                    │ coordena a aplicação │
                    └───────────┬───────────┘
                                │
                                ▼
                    ┌───────────────────────┐
                    │    CLUSTER MANAGER    │
                    │  gerencia recursos   │
                    └───────────┬───────────┘
                                │
                ┌───────────────┼───────────────┐
                │               │               │
                ▼               ▼               ▼
          ┌──────────┐    ┌──────────┐    ┌──────────┐
          │Executor 1│    │Executor 2│    │Executor 3│
          └────┬─────┘    └────┬─────┘    └────┬─────┘
               │               │               │
          ┌────┴────┐     ┌────┴────┐     ┌────┴────┐
          │  Tasks  │     │  Tasks  │     │  Tasks  │
          └────┬────┘     └────┬────┘     └────┬────┘
               │               │               │
          Partitions      Partitions      Partitions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enquanto isso, do ponto de vista lógico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ACTION
   │
   ▼
 JOB
   │
   ├───────────────┐
   ▼               ▼
STAGE 1         STAGE 2
   │               │
 Tasks           Tasks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esses dois desenhos juntos explicam uma grande parte do comportamento de uma aplicação Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  18. Da teoria para troubleshooting
&lt;/h1&gt;

&lt;p&gt;Agora podemos transformar sintomas em perguntas técnicas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aplicação lenta
&lt;/h2&gt;

&lt;p&gt;Em vez de:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spark está lento."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Podemos perguntar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qual Job está lento?
        ↓
Qual Stage?
        ↓
Quais Tasks?
        ↓
Quais Partitions?
        ↓
Em quais Executors?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  OutOfMemory
&lt;/h2&gt;

&lt;p&gt;Em vez de apenas aumentar memória:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OutOfMemory
    │
    ├── Driver?
    │
    └── Executor?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se for Executor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor OOM
    │
    ├── partição muito grande?
    ├── data skew?
    ├── cache excessivo?
    ├── operação cara?
    └── overhead?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A arquitetura transforma tentativa e erro em investigação.&lt;/p&gt;




&lt;h1&gt;
  
  
  19. O que mudou e o que continua igual
&lt;/h1&gt;

&lt;p&gt;As APIs do Spark evoluíram enormemente.&lt;/p&gt;

&lt;p&gt;Saímos de um mundo muito centrado em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SparkContext
     +
    RDD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para um ecossistema no qual trabalhamos frequentemente com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SparkSession
     +
 DataFrames
     +
 Spark SQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas alguns conceitos continuam fundamentais:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Driver;&lt;/li&gt;
&lt;li&gt;Executors;&lt;/li&gt;
&lt;li&gt;Jobs;&lt;/li&gt;
&lt;li&gt;Stages;&lt;/li&gt;
&lt;li&gt;Tasks;&lt;/li&gt;
&lt;li&gt;Partitions;&lt;/li&gt;
&lt;li&gt;Shuffle;&lt;/li&gt;
&lt;li&gt;paralelismo;&lt;/li&gt;
&lt;li&gt;memória distribuída;&lt;/li&gt;
&lt;li&gt;movimentação de dados.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É por isso que aprender arquitetura continua valendo a pena mesmo quando escrevemos praticamente todo o código utilizando DataFrames e SQL.&lt;/p&gt;




&lt;h1&gt;
  
  
  A principal lição deste capítulo
&lt;/h1&gt;

&lt;p&gt;Se existe uma ideia que quero deixar deste artigo é esta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Código Spark é apenas a descrição de uma computação distribuída.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para entender performance, precisamos enxergar a execução por trás do código.&lt;/p&gt;

&lt;p&gt;Quando vemos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;devemos começar a imaginar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data
 ↓
Partitions
 ↓
Tasks
 ↓
Executors
 ↓
Shuffle
 ↓
New Stage
 ↓
Tasks
 ↓
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É essa mudança de perspectiva que começa a separar simplesmente &lt;strong&gt;usar Spark&lt;/strong&gt; de realmente &lt;strong&gt;entender Spark&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Próximo artigo
&lt;/h1&gt;

&lt;p&gt;Agora sabemos quem executa o trabalho.&lt;/p&gt;

&lt;p&gt;Mas deixamos uma questão enorme em aberto:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Como Spark decide quantas partes do trabalho podem ser executadas em paralelo?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A resposta passa diretamente por:&lt;/p&gt;

&lt;h1&gt;
  
  
  Partições
&lt;/h1&gt;

&lt;p&gt;No próximo artigo vamos entender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;o que realmente é uma partition;&lt;/li&gt;
&lt;li&gt;relação entre partitions e tasks;&lt;/li&gt;
&lt;li&gt;paralelismo;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repartition()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;coalesce()&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;partições pequenas demais;&lt;/li&gt;
&lt;li&gt;partições grandes demais;&lt;/li&gt;
&lt;li&gt;como particionamento afeta CPU, memória e rede;&lt;/li&gt;
&lt;li&gt;e por que um cluster enorme pode ficar quase parado mesmo tendo recursos disponíveis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vamos chegar a uma das relações mais importantes de toda a série:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dados
  ↓
Partitions
  ↓
Tasks
  ↓
Parallelism
  ↓
Performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a href="https://dev.toCOLE_AQUI_A_URL_DA_PARTE_1"&gt;← Parte 1: Por que o Spark mudou o processamento de Big Data&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Próximo:&lt;/strong&gt; Parte 3 — Partições no Apache Spark: a base do paralelismo e da performance&lt;/p&gt;

</description>
      <category>pyspark</category>
      <category>bigdata</category>
      <category>dataengineering</category>
      <category>spark</category>
    </item>
    <item>
      <title>Como aprendi Apache Spark — Parte 1: Por que o Spark mudou o processamento de Big Data</title>
      <dc:creator>renanpyd</dc:creator>
      <pubDate>Tue, 06 Oct 2026 06:26:49 +0000</pubDate>
      <link>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-1-por-que-o-spark-mudou-o-processamento-de-big-data-51ei</link>
      <guid>https://dev.to/renanpyd/como-aprendi-apache-spark-parte-1-por-que-o-spark-mudou-o-processamento-de-big-data-51ei</guid>
      <description>&lt;p&gt;Hoje falamos naturalmente sobre Data Lakes, Lakehouses, processamento distribuído em cloud, Delta Lake, Apache Iceberg, pipelines em tempo real e plataformas como Databricks.&lt;/p&gt;

&lt;p&gt;Naquele período, porém, uma palavra dominava grande parte das discussões sobre processamento distribuído:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hadoop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mais especificamente, &lt;strong&gt;Hadoop MapReduce&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Entender esse contexto é importante porque Spark não surgiu simplesmente como "mais um framework de Big Data".&lt;/p&gt;

&lt;p&gt;Ele nasceu tentando resolver limitações reais do processamento distribuído da época.&lt;/p&gt;

&lt;p&gt;E foi justamente isso que chamou minha atenção.&lt;/p&gt;




&lt;h2&gt;
  
  
  Antes do Spark: o mundo era muito mais MapReduce
&lt;/h2&gt;

&lt;p&gt;Imagine que temos alguns terabytes de logs e precisamos descobrir quantas vezes cada palavra aparece nesses dados.&lt;/p&gt;

&lt;p&gt;Conceitualmente, podemos dividir o problema em duas etapas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAP
documento -&amp;gt; palavras -&amp;gt; (palavra, 1)

REDUCE
(palavra, 1), (palavra, 1), (palavra, 1)
                     ↓
              (palavra, 3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa ideia é extremamente poderosa.&lt;/p&gt;

&lt;p&gt;Podemos dividir um dataset enorme entre diversas máquinas, processar partes dele paralelamente e depois combinar os resultados.&lt;/p&gt;

&lt;p&gt;O Hadoop transformou esse modelo em uma plataforma capaz de processar volumes enormes de dados utilizando clusters de máquinas.&lt;/p&gt;

&lt;p&gt;Mas existia um problema.&lt;/p&gt;

&lt;p&gt;Para determinados workloads, principalmente aqueles compostos por &lt;strong&gt;múltiplas etapas&lt;/strong&gt;, havia muita leitura e escrita de dados entre operações.&lt;/p&gt;

&lt;p&gt;Imagine um algoritmo com várias etapas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dados
  ↓
Job 1
  ↓
Disco
  ↓
Job 2
  ↓
Disco
  ↓
Job 3
  ↓
Resultado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando comecei a estudar processamento distribuído, uma das primeiras coisas que percebi foi:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Em Big Data, mover dados frequentemente custa mais do que executar a própria transformação.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rede custa.&lt;/p&gt;

&lt;p&gt;Disco custa.&lt;/p&gt;

&lt;p&gt;Serialização custa.&lt;/p&gt;

&lt;p&gt;Shuffle custa.&lt;/p&gt;

&lt;p&gt;E essa observação continua extremamente relevante na Engenharia de Dados atual.&lt;/p&gt;




&lt;h1&gt;
  
  
  Então apareceu o Spark
&lt;/h1&gt;

&lt;p&gt;Apache Spark nasceu como um projeto de pesquisa no &lt;strong&gt;AMPLab da University of California, Berkeley&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Uma das ideias centrais era permitir que determinados conjuntos de dados fossem reutilizados eficientemente durante diferentes etapas de uma computação distribuída.&lt;/p&gt;

&lt;p&gt;Isso era especialmente interessante para workloads iterativos.&lt;/p&gt;

&lt;p&gt;Pense, por exemplo, em algoritmos que repetem operações sobre os mesmos dados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dataset
   ↓
Iteração 1
   ↓
Iteração 2
   ↓
Iteração 3
   ↓
Iteração 4
   ↓
Resultado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Machine Learning é um ótimo exemplo.&lt;/p&gt;

&lt;p&gt;Muitos algoritmos precisam executar várias iterações sobre um mesmo conjunto de dados.&lt;/p&gt;

&lt;p&gt;Se cada etapa precisar reconstruir todo o estado intermediário utilizando armazenamento persistente, o custo pode crescer rapidamente.&lt;/p&gt;

&lt;p&gt;Spark propôs uma abstração muito interessante para esse problema.&lt;/p&gt;

&lt;p&gt;Os &lt;strong&gt;RDDs&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  RDD: Resilient Distributed Dataset
&lt;/h1&gt;

&lt;p&gt;RDD significa:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resilient Distributed Dataset&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Em português, podemos pensar em algo como:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;conjunto de dados distribuído e resiliente.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mas essa tradução não explica por que a ideia foi tão importante.&lt;/p&gt;

&lt;p&gt;Um RDD representa uma coleção de elementos distribuída pelas máquinas de um cluster e capaz de ser processada paralelamente.&lt;/p&gt;

&lt;p&gt;Visualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    RDD
                     │
          ┌──────────┼──────────┐
          │          │          │
          ▼          ▼          ▼
      Partição 1  Partição 2  Partição 3
          │          │          │
          ▼          ▼          ▼
      Executor A  Executor B  Executor C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso permite que diferentes partes do conjunto de dados sejam processadas simultaneamente.&lt;/p&gt;

&lt;p&gt;Mas a parte mais interessante está no &lt;strong&gt;Resilient&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Por que "Resilient"?
&lt;/h1&gt;

&lt;p&gt;Clusters falham.&lt;/p&gt;

&lt;p&gt;Máquinas podem parar.&lt;/p&gt;

&lt;p&gt;Executors podem morrer.&lt;/p&gt;

&lt;p&gt;Processos podem ser encerrados.&lt;/p&gt;

&lt;p&gt;Nós podem ficar indisponíveis.&lt;/p&gt;

&lt;p&gt;Uma plataforma de processamento distribuído precisa assumir que falhas vão acontecer.&lt;/p&gt;

&lt;p&gt;Spark consegue reconstruir partições perdidas utilizando informações sobre &lt;strong&gt;como aquele dataset foi produzido&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Essa cadeia de dependências é chamada de:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;lineage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arquivo
   │
   ▼
textFile()
   │
   ▼
  RDD A
   │
   │ filter()
   ▼
  RDD B
   │
   │ map()
   ▼
  RDD C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark conhece essa sequência de transformações.&lt;/p&gt;

&lt;p&gt;Se uma partição de &lt;code&gt;RDD C&lt;/code&gt; for perdida, ele não necessariamente precisa manter uma cópia completa de tudo.&lt;/p&gt;

&lt;p&gt;Em muitos casos, pode reconstruir a partição a partir do lineage.&lt;/p&gt;

&lt;p&gt;Essa ideia é fundamental para entender a arquitetura do Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  Um primeiro RDD em PySpark
&lt;/h1&gt;

&lt;p&gt;Vamos olhar um exemplo extremamente simples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numeros&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aqui criamos um RDD contendo cinco elementos.&lt;/p&gt;

&lt;p&gt;Agora podemos aplicar uma transformação:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dobrados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numeros&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Intuitivamente, poderíamos imaginar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1, 2, 3, 4, 5]

       map(x * 2)

[2, 4, 6, 8, 10]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas existe um detalhe fundamental.&lt;/p&gt;

&lt;p&gt;Quando executamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;dobrados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numeros&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spark &lt;strong&gt;não precisa calcular imediatamente o resultado&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ele registra a transformação.&lt;/p&gt;

&lt;p&gt;Esse comportamento é chamado de:&lt;/p&gt;

&lt;h1&gt;
  
  
  Lazy Evaluation
&lt;/h1&gt;

&lt;p&gt;E esse é um dos conceitos mais importantes de toda a arquitetura Spark.&lt;/p&gt;




&lt;h1&gt;
  
  
  Spark não executa tudo imediatamente
&lt;/h1&gt;

&lt;p&gt;Considere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numeros&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;pares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numeros&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;dobrados&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Podemos imaginar o plano:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parallelize
     │
     ▼
   filter
     │
     ▼
    map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Até aqui estamos descrevendo &lt;strong&gt;o que queremos fazer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Agora executamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dobrados&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultado&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[4, 8]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;collect()&lt;/code&gt; é uma &lt;strong&gt;ação&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;É nesse momento que Spark precisa efetivamente produzir o resultado.&lt;/p&gt;

&lt;p&gt;Essa separação nos leva a dois conceitos fundamentais.&lt;/p&gt;




&lt;h1&gt;
  
  
  Transformations vs Actions
&lt;/h1&gt;

&lt;p&gt;Grande parte da programação com Spark pode ser compreendida através dessa divisão.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transformations
&lt;/h2&gt;

&lt;p&gt;Transformations criam um novo dataset a partir de outro.&lt;/p&gt;

&lt;p&gt;Exemplos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;distinct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normalmente elas são avaliadas de forma &lt;strong&gt;lazy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Podemos imaginar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RDD
 │
 ├── filter()
 │
 ▼
RDD
 │
 ├── map()
 │
 ▼
RDD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Estamos construindo um plano de transformação.&lt;/p&gt;




&lt;h2&gt;
  
  
  Actions
&lt;/h2&gt;

&lt;p&gt;Actions solicitam algum resultado da computação.&lt;/p&gt;

&lt;p&gt;Exemplos clássicos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando uma Action é executada, Spark precisa avaliar as transformações necessárias para produzir o resultado.&lt;/p&gt;

&lt;p&gt;Por exemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parallelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dados
  │
  ▼
filter()
  │
  ▼
map()
  │
  ▼
take(5)
  │
  ▼
Resultado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse modelo permite ao Spark entender uma sequência de operações antes da execução.&lt;/p&gt;

&lt;p&gt;Mais tarde veremos como isso se relaciona com &lt;strong&gt;jobs, stages, tasks e DAGs&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  O clássico WordCount
&lt;/h1&gt;

&lt;p&gt;Nenhuma viagem pela história do processamento distribuído estaria completa sem ele.&lt;/p&gt;

&lt;p&gt;O famoso:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WordCount.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;linhas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dados.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;palavras&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linhas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linha&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;pares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;palavras&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;palavra&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;palavra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;contagem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduceByKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contagem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arquivo
   │
   ▼
Linhas
   │
   │ flatMap()
   ▼
Palavras
   │
   │ map()
   ▼
(palavra, 1)
   │
   │ reduceByKey()
   ▼
(palavra, quantidade)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse pequeno exemplo contém vários conceitos importantes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;leitura distribuída;&lt;/li&gt;
&lt;li&gt;transformação dos dados;&lt;/li&gt;
&lt;li&gt;criação de pares chave/valor;&lt;/li&gt;
&lt;li&gt;agregação;&lt;/li&gt;
&lt;li&gt;execução distribuída;&lt;/li&gt;
&lt;li&gt;movimentação de dados entre partições.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;E existe uma palavra especialmente importante nessa lista:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;movimentação.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Porque &lt;code&gt;reduceByKey()&lt;/code&gt; pode exigir que registros com a mesma chave sejam reunidos.&lt;/p&gt;

&lt;p&gt;Isso nos leva a um dos assuntos mais importantes de performance em Spark:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;shuffle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vamos dedicar um artigo específico a isso mais adiante.&lt;/p&gt;




&lt;h1&gt;
  
  
  Spark não é simplesmente "Hadoop mais rápido"
&lt;/h1&gt;

&lt;p&gt;Durante muito tempo tornou-se comum encontrar explicações como:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spark é Hadoop em memória."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa descrição é conveniente.&lt;/p&gt;

&lt;p&gt;Mas é incompleta.&lt;/p&gt;

&lt;p&gt;Spark não é apenas uma implementação mais rápida de MapReduce.&lt;/p&gt;

&lt;p&gt;Ele oferece um modelo de execução diferente e uma API capaz de representar pipelines de transformação muito mais naturalmente.&lt;/p&gt;

&lt;p&gt;Compare conceitualmente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MapReduce

Job
 ↓
Disco
 ↓
Job
 ↓
Disco
 ↓
Job
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spark

Transformação
     ↓
Transformação
     ↓
Transformação
     ↓
Action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa mudança no modelo de programação é tão importante quanto a questão de performance.&lt;/p&gt;




&lt;h1&gt;
  
  
  Spark também não mantém "tudo em memória"
&lt;/h1&gt;

&lt;p&gt;Outro erro que encontrei muitas vezes ao aprender Spark foi:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spark coloca todos os dados na RAM."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Não.&lt;/p&gt;

&lt;p&gt;Spark &lt;strong&gt;pode&lt;/strong&gt; manter dados em memória quando isso é útil e possível, mas isso não significa que todo processamento acontece exclusivamente na memória.&lt;/p&gt;

&lt;p&gt;Dependendo da operação, configuração e volume de dados, Spark pode utilizar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memória;&lt;/li&gt;
&lt;li&gt;disco;&lt;/li&gt;
&lt;li&gt;rede;&lt;/li&gt;
&lt;li&gt;armazenamento externo;&lt;/li&gt;
&lt;li&gt;recomputação.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O modelo real é muito mais sofisticado.&lt;/p&gt;

&lt;p&gt;E entender isso evita várias decisões ruins de arquitetura.&lt;/p&gt;




&lt;h1&gt;
  
  
  O que continua relevante em 2026?
&lt;/h1&gt;

&lt;p&gt;Hoje, na maior parte dos pipelines modernos, provavelmente não começaremos escrevendo diretamente RDDs.&lt;/p&gt;

&lt;p&gt;É muito mais comum trabalharmos com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/dados/clientes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e depois:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;resultado&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ativo&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;estado&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou utilizando SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;quantidade&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;clientes&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ativo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;estado&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DataFrames e Spark SQL permitem que o Spark realize otimizações muito mais sofisticadas sobre as consultas.&lt;/p&gt;

&lt;p&gt;Então por que estudar RDD?&lt;/p&gt;

&lt;p&gt;Porque vários conceitos fundamentais continuam aparecendo por baixo das abstrações modernas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DataFrame / SQL
       │
       ▼
Plano lógico
       │
       ▼
Otimização
       │
       ▼
Plano físico
       │
       ▼
Stages
       │
       ▼
Tasks
       │
       ▼
Executors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando alguma coisa fica lenta, cara ou instável, conhecer apenas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;geralmente não é suficiente.&lt;/p&gt;

&lt;p&gt;Precisamos entender &lt;strong&gt;o que está acontecendo por baixo da API&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Uma lição que continua válida
&lt;/h1&gt;

&lt;p&gt;Uma das coisas mais importantes que aprendi estudando Spark foi que Engenharia de Dados distribuída não consiste apenas em escrever transformações.&lt;/p&gt;

&lt;p&gt;Precisamos pensar em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dados
  ↓
Particionamento
  ↓
Transformações
  ↓
Dependências
  ↓
Shuffle
  ↓
Stages
  ↓
Tasks
  ↓
Executors
  ↓
CPU / Memória / Disco / Rede
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quando começamos a enxergar um pipeline dessa forma, vários problemas deixam de parecer misteriosos.&lt;/p&gt;

&lt;p&gt;Um job lento deixa de ser simplesmente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Spark está lento."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;E passa a gerar perguntas melhores:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Houve shuffle?&lt;/p&gt;

&lt;p&gt;Existe data skew?&lt;/p&gt;

&lt;p&gt;As partições estão bem dimensionadas?&lt;/p&gt;

&lt;p&gt;Estamos movimentando dados desnecessariamente?&lt;/p&gt;

&lt;p&gt;Algum executor está sofrendo pressão de memória?&lt;/p&gt;

&lt;p&gt;O driver está recebendo dados demais?&lt;/p&gt;

&lt;p&gt;Estamos usando a abstração correta?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essas perguntas continuam extremamente atuais.&lt;/p&gt;




&lt;h1&gt;
  
  
  Daqui para frente
&lt;/h1&gt;

&lt;p&gt;Neste artigo vimos os fundamentos que me fizeram começar a olhar Spark de forma diferente:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;o contexto do Hadoop MapReduce;&lt;/li&gt;
&lt;li&gt;processamento distribuído;&lt;/li&gt;
&lt;li&gt;RDDs;&lt;/li&gt;
&lt;li&gt;resiliência;&lt;/li&gt;
&lt;li&gt;lineage;&lt;/li&gt;
&lt;li&gt;transformations;&lt;/li&gt;
&lt;li&gt;actions;&lt;/li&gt;
&lt;li&gt;lazy evaluation;&lt;/li&gt;
&lt;li&gt;e a ideia de distribuir uma computação entre várias máquinas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agora podemos entrar de verdade na arquitetura.&lt;/p&gt;

&lt;p&gt;No próximo artigo vamos responder uma pergunta fundamental:&lt;/p&gt;

&lt;h1&gt;
  
  
  Quando executo um programa Spark, quem realmente faz o trabalho?
&lt;/h1&gt;

&lt;p&gt;Vamos conhecer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Driver, SparkContext, Cluster Manager, Executors, Jobs, Stages e Tasks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;E vamos montar o caminho completo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aplicação
    ↓
Driver
    ↓
Cluster Manager
    ↓
Executors
    ↓
Stages
    ↓
Tasks
    ↓
Dados
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entender esse fluxo muda completamente a maneira como diagnosticamos performance, memória e falhas em aplicações Spark.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Série: Como aprendi Apache Spark&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;← Parte 0: Como aprendi Apache Spark: revisitando uma jornada pela Engenharia de Dados&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Próximo:&lt;/strong&gt; Parte 2 — Por dentro da arquitetura do Apache Spark: Driver, Executors, Jobs, Stages e Tasks&lt;/p&gt;

</description>
      <category>spark</category>
      <category>dataengineering</category>
      <category>bigdata</category>
      <category>python</category>
    </item>
    <item>
      <title>Como aprendi Apache Spark: revisitando uma jornada pela Engenharia de Dados</title>
      <dc:creator>renanpyd</dc:creator>
      <pubDate>Tue, 06 Oct 2026 04:05:21 +0000</pubDate>
      <link>https://dev.to/renanpyd/como-aprendi-apache-spark-revisitando-uma-jornada-pela-engenharia-de-dados-l5g</link>
      <guid>https://dev.to/renanpyd/como-aprendi-apache-spark-revisitando-uma-jornada-pela-engenharia-de-dados-l5g</guid>
      <description>&lt;p&gt;Quando comecei a estudar Apache Spark, o ecossistema de Big Data era muito diferente do que conhecemos hoje.&lt;/p&gt;

&lt;p&gt;Hadoop e MapReduce dominavam boa parte das conversas sobre processamento distribuído. Trabalhar com grandes volumes de dados normalmente significava pensar em clusters, HDFS, processamento em lote e muitas operações de leitura e escrita em disco.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;&amp;gt; Foi nesse cenário que o Spark começou a chamar minha atenção.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;**E esta série nasce justamente de uma ideia: revisitar meus antigos materiais de estudo sobre Apache Spark e reconstruí-los com o olhar que tenho hoje como profissional de Engenharia de Dados e IA.&lt;/p&gt;

&lt;p&gt;Não quero simplesmente republicar anotações antigas.&lt;/p&gt;

&lt;p&gt;Quero entender o que envelheceu, o que continua extremamente relevante e, principalmente, quais conceitos atravessaram diferentes gerações do Spark e continuam presentes na engenharia de dados moderna.**&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;## Por que revisitar Apache Spark?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Algumas tecnologias mudam tanto que estudar suas versões antigas parece arqueologia.&lt;/p&gt;

&lt;p&gt;Spark é diferente.&lt;/p&gt;

&lt;p&gt;As APIs evoluíram. O ecossistema mudou. RDDs deixaram de ser a principal abstração utilizada em muitas aplicações. DataFrames, Spark SQL, Catalyst, Structured Streaming e diversas otimizações passaram a ocupar um papel central.&lt;/p&gt;

&lt;p&gt;Mas vários fundamentos continuam essenciais.&lt;/p&gt;

&lt;p&gt;Conceitos como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;processamento distribuído;&lt;/li&gt;
&lt;li&gt;particionamento;&lt;/li&gt;
&lt;li&gt;transformações e ações;&lt;/li&gt;
&lt;li&gt;lazy evaluation;&lt;/li&gt;
&lt;li&gt;lineage;&lt;/li&gt;
&lt;li&gt;DAGs;&lt;/li&gt;
&lt;li&gt;shuffle;&lt;/li&gt;
&lt;li&gt;execução distribuída;&lt;/li&gt;
&lt;li&gt;tolerância a falhas;&lt;/li&gt;
&lt;li&gt;serialização;&lt;/li&gt;
&lt;li&gt;gerenciamento de memória;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;continuam ajudando a explicar por que um job Spark funciona — ou por que ele fica terrivelmente lento.&lt;/p&gt;

&lt;p&gt;E é justamente aí que estudar os fundamentos continua tendo valor.&lt;/p&gt;

&lt;p&gt;Um aviso importante sobre esta série:&lt;br&gt;
    &lt;em&gt;&lt;strong&gt;Esta não será simplesmente uma reprodução dos meus materiais antigos.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alguns deles utilizam versões antigas do Spark e APIs que foram substituídas ou evoluíram significativamente.&lt;/p&gt;

&lt;p&gt;Sempre que isso acontecer, vou separar três coisas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Como funcionava&lt;/li&gt;
&lt;li&gt;O comportamento ou API utilizada naquele momento da evolução do Spark.&lt;/li&gt;
&lt;li&gt;O conceito por trás&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A ideia de Engenharia de Dados que continua importante independentemente da versão.&lt;/p&gt;

&lt;p&gt;Como pensamos nisso hoje: A maneira moderna de resolver ou compreender o mesmo problema. Isso significa que eventualmente veremos código antigo. E isso é proposital.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;&amp;gt; Código legado também conta a história de uma tecnologia.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;De Hadoop MapReduce ao Spark&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Uma das ideias que tornou Spark especialmente interessante foi reduzir a dependência de sucessivas operações de leitura e escrita em disco durante determinados tipos de processamento.&lt;/p&gt;

&lt;p&gt;Em workloads iterativos e análises interativas, isso fazia uma diferença enorme.&lt;/p&gt;

&lt;p&gt;Mas seria simplista resumir Spark a:&lt;/p&gt;

&lt;p&gt;"Spark é rápido porque processa tudo em memória."&lt;/p&gt;

&lt;p&gt;A arquitetura é muito mais interessante.&lt;/p&gt;

&lt;p&gt;Spark introduziu um modelo de computação distribuída baseado em abstrações capazes de manter informações sobre como os dados foram produzidos.&lt;/p&gt;

&lt;p&gt;Uma dessas abstrações tornou-se fundamental para entender os primeiros anos do projeto:&lt;/p&gt;

&lt;p&gt;RDD — Resilient Distributed Dataset&lt;/p&gt;

&lt;p&gt;Um RDD representa uma coleção distribuída de dados que pode ser processada paralelamente.&lt;/p&gt;

&lt;p&gt;Mas a parte realmente interessante não é apenas o fato de os dados estarem distribuídos.&lt;/p&gt;

&lt;p&gt;É a combinação de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;particionamento;&lt;/li&gt;
&lt;li&gt;imutabilidade;&lt;/li&gt;
&lt;li&gt;operações funcionais;&lt;/li&gt;
&lt;li&gt;execução lazy;&lt;/li&gt;
&lt;li&gt;lineage;&lt;/li&gt;
&lt;li&gt;recomputação;&lt;/li&gt;
&lt;li&gt;tolerância a falhas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essas ideias ajudaram a criar um modelo poderoso para computação distribuída.&lt;/p&gt;

&lt;p&gt;Em 2012, o trabalho sobre Resilient Distributed Datasets recebeu o prêmio de melhor artigo no NSDI.&lt;/p&gt;

&lt;p&gt;Anos depois, muitas das abstrações de mais alto nível utilizadas no Spark seriam construídas sobre fundamentos estabelecidos nesse período.&lt;/p&gt;

&lt;p&gt;E então vieram DataFrames, Spark SQL e muito mais&lt;/p&gt;

&lt;p&gt;A história não parou nos RDDs.&lt;/p&gt;

&lt;p&gt;O ecossistema evoluiu.&lt;/p&gt;

&lt;p&gt;Spark SQL tornou o processamento de dados estruturados muito mais acessível.&lt;/p&gt;

&lt;p&gt;DataFrames elevaram o nível de abstração.&lt;/p&gt;

&lt;p&gt;O Catalyst Optimizer passou a desempenhar um papel fundamental na otimização de consultas.&lt;/p&gt;

&lt;p&gt;O projeto Tungsten trouxe importantes avanços na execução e no gerenciamento de memória.&lt;/p&gt;

&lt;p&gt;Structured Streaming aproximou processamento batch e streaming sob um modelo mais uniforme.&lt;/p&gt;

&lt;p&gt;E Spark tornou-se uma das principais tecnologias da Engenharia de Dados moderna.&lt;/p&gt;

&lt;p&gt;É essa evolução que quero explorar ao longo desta série.&lt;/p&gt;

&lt;p&gt;O que vamos estudar&lt;/p&gt;

&lt;p&gt;Nos próximos artigos vamos passar por assuntos como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;arquitetura do Apache Spark;&lt;/li&gt;
&lt;li&gt;RDDs;&lt;/li&gt;
&lt;li&gt;transformations e actions;&lt;/li&gt;
&lt;li&gt;lazy evaluation;&lt;/li&gt;
&lt;li&gt;SparkContext;&lt;/li&gt;
&lt;li&gt;execução distribuída;&lt;/li&gt;
&lt;li&gt;Client Mode e Cluster Mode;&lt;/li&gt;
&lt;li&gt;particionamento;&lt;/li&gt;
&lt;li&gt;joins e shuffles;&lt;/li&gt;
&lt;li&gt;accumulators;&lt;/li&gt;
&lt;li&gt;broadcast variables;&lt;/li&gt;
&lt;li&gt;JSON;&lt;/li&gt;
&lt;li&gt;DataFrames;&lt;/li&gt;
&lt;li&gt;Spark SQL;&lt;/li&gt;
&lt;li&gt;schemas;&lt;/li&gt;
&lt;li&gt;fontes de dados;&lt;/li&gt;
&lt;li&gt;window functions;&lt;/li&gt;
&lt;li&gt;Spark Streaming;&lt;/li&gt;
&lt;li&gt;testes;&lt;/li&gt;
&lt;li&gt;gerenciamento de memória;&lt;/li&gt;
&lt;li&gt;memoryOverhead;&lt;/li&gt;
&lt;li&gt;otimização e troubleshooting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Também vamos comparar algumas APIs históricas com a forma como resolveríamos os mesmos problemas atualmente.&lt;/p&gt;

&lt;p&gt;O objetivo não é decorar Spark&lt;/p&gt;

&lt;p&gt;Depois de trabalhar com Engenharia de Dados por algum tempo, uma coisa fica cada vez mais clara:&lt;/p&gt;

&lt;p&gt;saber escrever código Spark não significa necessariamente saber Spark.&lt;/p&gt;

&lt;p&gt;É relativamente fácil escrever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;O mais importante é conseguir perguntar:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quantas partições existem?&lt;/li&gt;
&lt;li&gt;Haverá shuffle?&lt;/li&gt;
&lt;li&gt;Qual será o volume movimentado pela rede?&lt;/li&gt;
&lt;li&gt;Existe data skew?&lt;/li&gt;
&lt;li&gt;Como o plano será executado?&lt;/li&gt;
&lt;li&gt;O dataset cabe adequadamente nas partições?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O problema está no driver ou nos executors?&lt;/p&gt;

&lt;p&gt;O gargalo é CPU, memória, disco ou rede?&lt;/p&gt;

&lt;p&gt;Essa transformação realmente precisa existir?&lt;/p&gt;

&lt;p&gt;É esse tipo de raciocínio que transforma conhecimento de uma API em conhecimento de Engenharia de Dados distribuída.&lt;/p&gt;

&lt;p&gt;Próximo artigo&lt;/p&gt;

&lt;p&gt;Na Parte 1, vamos começar pelos fundamentos:&lt;/p&gt;

&lt;p&gt;O que é Apache Spark e por que sua arquitetura foi tão importante?&lt;/p&gt;

&lt;p&gt;Vamos entender:&lt;/p&gt;

&lt;p&gt;o problema que Spark buscava resolver;&lt;/p&gt;

&lt;p&gt;a relação histórica com Hadoop;&lt;/p&gt;

&lt;p&gt;o papel dos RDDs;&lt;/p&gt;

&lt;p&gt;processamento em memória;&lt;/p&gt;

&lt;p&gt;tolerância a falhas;&lt;/p&gt;

&lt;p&gt;e como essas decisões influenciaram o Spark que usamos atualmente.&lt;/p&gt;

&lt;p&gt;Esta série será uma viagem pelas versões do Spark, mas principalmente pelos conceitos de Engenharia de Dados que sobreviveram a elas.&lt;/p&gt;

&lt;p&gt;Nos vemos na Parte 1.&lt;/p&gt;

</description>
      <category>spark</category>
      <category>bigdata</category>
      <category>dataengineering</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
