Quando falamos em shuffle de dados, estamos falando em redistribuir entre os nós os dados de acordo com uma coluna para facilitar o processamento de acordo com a necessidade.

Precisamos ter atenção pois o shuffle exige muito da rede: a troca de dados entre todos os nós envolvidos é muito grande e há grande uso do disco com muitas escritas e serializações ocorrendo no processo.

Diagrama

flowchart LR
    subgraph Stage1["Estágio 1 (antes do shuffle)"]
        direction TB
        E1["Executor 1\nchaves: A, B, C, D"]
        E2["Executor 2\nchaves: B, A, D, C"]
    end

    subgraph SW["Shuffle Write (disco local)"]
        direction TB
        W1B0["Executor 1\nbucket 0: A, A"]
        W1B1["Executor 1\nbucket 1: B, C, D"]
        W2B0["Executor 2\nbucket 0: A"]
        W2B1["Executor 2\nbucket 1: B, C, D"]
    end

    subgraph SR["Shuffle Read (rede)"]
        direction TB
        N1["Reducer 0\nbusca bucket 0 em todos"]
        N2["Reducer 1\nbusca bucket 1 em todos"]
    end

    subgraph Stage2["Estágio 2 (após o shuffle)"]
        direction TB
        R1["Executor A\nA, A, A agrupados"]
        R2["Executor B\nB, C, D, B, C, D agrupados"]
    end

    E1 -->|"hash(key)"| W1B0 & W1B1
    E2 -->|"hash(key)"| W2B0 & W2B1
    W1B0 -->|fetch| N1
    W2B0 -->|fetch| N1
    W1B1 -->|fetch| N2
    W2B1 -->|fetch| N2
    N1 --> R1
    N2 --> R2

O ponto central que o diagrama mostra: cada executor escreve N arquivos no disco (um por bucket de destino), e cada reducer vai buscar o seu bucket em todos os executores pela rede. É esse tráfego all-to-all que torna o shuffle a operação mais cara do spark.

Particularmente, acho o termo shuffle confuso. Quando embaralhamos cartas esperamos que elas fiquem mais bagunçadas. Mas a ideia aqui é agrupar os dados por uma determinada coluna, como organizar um baralho por naipes.

Conexões

Referências