Fabric Runtime 2.0: Spark 4.1, Delta 4.2 e o roteiro para migrar do Runtime 1.3 sem quebrar produção

Resumo: o Runtime 2.0 chegou em GA no Microsoft Fabric e já é o padrão para novos workspaces. Ele troca praticamente toda a pilha: Spark 4.1, Delta Lake 4.2, Java 21, Scala 2.13, Python 3.13 e um sistema operacional novo. O ganho de motor é real, mas a migração não é um clique. Mudaram defaults de configuração, centena

Blog

Neste post
Fabric Runtime 2.0: migração do Runtime 1.3, Spark 4.1 e Delta 4.2

Resumo: o Runtime 2.0 chegou em GA no Microsoft Fabric e já é o padrão para novos workspaces. Ele troca praticamente toda a pilha: Spark 4.1, Delta Lake 4.2, Java 21, Scala 2.13, Python 3.13 e um sistema operacional novo. O ganho de motor é real, mas a migração não é um clique. Mudaram defaults de configuração, centenas de bibliotecas saíram da imagem e o protocolo Delta pode trancar outros consumidores. Neste post eu mostro o que muda por baixo, onde costuma quebrar e um roteiro de migração em etapas com código de validação.

Introdução

Se você roda notebooks e Spark Job Definitions no Fabric, provavelmente já viu o seletor de runtime oferecendo a opção 2.0 (Spark 4.1, Delta 4.2). E provavelmente também já recebeu a pergunta do gestor: "dá pra trocar logo e ganhar performance?". A resposta curta é sim. A resposta honesta é sim, mas não do jeito que muita gente vai tentar fazer.

O Runtime 1.3 já está com fim de suporte anunciado. Então a migração não é uma questão de "se", é de "quando" e "como". O problema é que o 2.0 não é uma versão incremental. É uma troca de geração inteira do Spark, com mudança de Scala, de Java, de Python e de sistema operacional ao mesmo tempo. Qualquer uma dessas sozinha já merecia um teste de regressão. Todas juntas pedem um plano.

Vamos destrinchar o que mudou de verdade, quais defaults foram alterados sem fazer barulho e como montar uma migração que não termine com um pipeline de produção quebrado numa segunda-feira de manhã.

1. Não é update, é troca de geração

A melhor forma de entender o tamanho do salto é colocar as duas versões lado a lado. Repare que não sobra praticamente nenhum componente igual.

Componente

Runtime 1.3

Runtime 2.0

Apache Spark

3.5

4.1

Delta Lake

3.2

4.2

Java

11

21

Scala

2.12

2.13

Python

3.11

3.13

R

4.4

4.5

Sistema operacional

Azure Linux 2.0

Azure Linux 3.0

Para quem trabalha só com PySpark e SQL o impacto mais visível está em Python e nas bibliotecas. Para quem tem JAR customizado em Scala o impacto é direto: artefatos Scala 2.12 e 2.13 não são compatíveis em binário. Aquele JAR que funciona há dois anos no 1.3 pode simplesmente falhar no carregamento de classe no 2.0, mesmo sem nenhuma linha de código alterada.

E tem um detalhe que pouca gente nota: o Runtime 2.0 virou o padrão para novos workspaces e novos itens de Environment. Ou seja, se o seu time cria um workspace novo para um projeto e copia notebooks de um workspace antigo, a migração já aconteceu. Só que ninguém testou.

2. O que vem de bom no motor

Vale a pena entender o lado positivo antes de falar dos riscos, porque ele justifica o esforço. O principal ganho está no Native Execution Engine, o motor vetorizado que o Fabric usa por baixo do Spark. Em vez de processar linha por linha na JVM, ele trabalha em lotes colunares em código nativo. A Microsoft divulga até seis vezes mais performance que o Spark open source no TPC-DS. Trate esse número como o melhor cenário possível, não como promessa para o seu pipeline. Mas a direção é clara: workloads pesados de leitura e agregação em Delta tendem a ganhar bastante sem mudar código.

O Spark 4.1 também traz recursos de linguagem que mudam o dia a dia do engenheiro:

  • Tipo VARIANT: armazena JSON semi-estruturado de forma eficiente, sem precisar definir schema rígido nem guardar tudo como string.

  • SQL UDFs: funções definidas em SQL puro, sem precisar de Python e sem o custo de serialização de UDF Python.

  • Pipe syntax: uma forma de escrever SQL encadeado de cima para baixo, muito mais legível em transformações longas.

  • Session variables e string collation: coisas que todo mundo que vem do SQL Server sentia falta.

  • Python Data Source API e Python UDTFs: agora dá pra escrever conectores e funções que retornam tabelas em Python puro.

No Delta Lake 4.2 o destaque fica para o Liquid Clustering com otimização incremental e o carregamento paralelo de snapshot, que reduz o tempo de início de query em tabelas grandes com muito histórico de commits.

Dica de campo: o pipe syntax é o recurso que mais rápido conquista o time. Uma transformação Silver para Gold que tinha três CTEs aninhadas vira um fluxo linear que qualquer pessoa lê de cima para baixo. Vale começar a adotar já nos notebooks novos.

3. Os defaults que mudaram em silêncio

Aqui mora a parte que mais pega time experiente. Você não alterou nenhuma configuração, mas o comportamento do job mudou. Isso acontece porque o Runtime 2.0 vem com outros valores padrão para propriedades que afetam diretamente tamanho de arquivo, particionamento e escrita em Delta.

Configuração

1.3

2.0

Impacto prático

spark.sql.files.maxPartitionBytes

128 MiB

512 MiB

Menos tasks na leitura, cada uma com mais dados. Pode pressionar memória.

spark.hadoop.parquet.block.size

1 GiB

128 MiB

Row groups menores nos arquivos escritos.

spark.databricks.delta.optimizeWrite.enabled

true

não definido

O comportamento de Optimize Write muda. Verifique o tamanho dos arquivos gerados.

spark.microsoft.delta.targetFileSize.adaptive.enabled

false

true

Tamanho alvo de arquivo passa a ser adaptativo.

spark.sql.orc.compression.codec

snappy

zstd

Só afeta quem escreve ORC, mas afeta.

spark.speculation.multiplier

1.5

3

Execução especulativa fica bem mais conservadora.

Nenhuma dessas mudanças é ruim. Na maioria dos casos são escolhas melhores para o motor novo. O problema é que elas alteram o perfil de tempo, de custo em CU e de layout dos arquivos. Se você compara o tempo de execução antes e depois e vê diferença, precisa saber de onde ela vem antes de comemorar ou de culpar o runtime.

Outro ponto importante: o Spark 4.1 upstream liga o modo ANSI por padrão (spark.sql.ansi.enabled=true). O Fabric optou por manter false no Runtime 2.0. Boa decisão para compatibilidade. Mesmo assim o Spark 4.1 mudou comportamentos de cast, overflow, timezone e intervalo que podem alterar resultado de query sem gerar erro nenhum. Esse é o tipo de problema que só aparece quando alguém do financeiro questiona um número.

4. Na prática: um notebook de regressão lado a lado

A estratégia que eu recomendo é simples de explicar e trabalhosa de fazer direito: rodar a mesma entrada nos dois runtimes e comparar a saída. Para isso você cria um Environment separado com o Runtime 2.0, anexa a uma cópia do notebook e grava o resultado numa tabela de validação. Depois compara com a saída do 1.3.

O notebook abaixo faz três coisas: registra versão do runtime, gera uma impressão digital do resultado (contagem, somas e hash por linha) e verifica o protocolo Delta da tabela de destino. Ele roda igual nos dois runtimes.

# Célula 1: identifica o runtime da sessão
import pyspark, sys
from pyspark.sql import functions as F

runtime_info = {
    "spark": spark.version,
    "pyspark": pyspark.__version__,
    "python": sys.version.split()[0],
}
print(runtime_info)

# Célula 2: impressão digital do resultado da transformação
TABELA = "silver.vendas_consolidadas"
df = spark.read.table(TABELA)

colunas = sorted(df.columns)
fingerprint = (
    df.select(
        F.sha2(F.concat_ws("||", *[F.col(c).cast("string") for c in colunas]), 256)
         .alias("row_hash")
    )
    .agg(
        F.count("*").alias("linhas"),
        F.sum(F.conv(F.substring("row_hash", 1, 8), 16, 10).cast("long"))
         .alias("checksum")
    )
    .withColumn("spark_version", F.lit(spark.version))
    .withColumn("tabela", F.lit(TABELA))
    .withColumn("executado_em", F.current_timestamp())
)

fingerprint.write.mode("append").saveAsTable("validacao.runtime_regressao")

# Célula 3: checa o protocolo Delta antes e depois
spark.sql(f"DESCRIBE DETAIL {TABELA}") \
     .select("minReaderVersion", "minWriterVersion", "tableFeatures") \
     .show(truncate=False)

Com a tabela validacao.runtime_regressao alimentada pelos dois runtimes, a comparação vira uma query. Aproveitando, ela já usa a pipe syntax do Spark 4.1, então rode essa parte no 2.0:

-- Compara as execuções do 1.3 e do 2.0 por tabela
FROM validacao.runtime_regressao
|> AGGREGATE
     MIN(linhas)   AS linhas_min,
     MAX(linhas)   AS linhas_max,
     MIN(checksum) AS checksum_min,
     MAX(checksum) AS checksum_max
   GROUP BY tabela
|> EXTEND (linhas_min = linhas_max AND checksum_min = checksum_max) AS resultado_identico
|> WHERE NOT resultado_identico

Se essa query retornar alguma linha, você tem divergência de resultado. Aí o trabalho é investigar coluna por coluna: precisão de decimal, timestamp com fuso, tratamento de nulo e divisão por zero são os suspeitos de sempre.

Dica de campo: o cast para string dentro do hash é proposital. Se o tipo de uma coluna mudar entre os runtimes (precisão de decimal, por exemplo), o hash denuncia. Compare também o schema com df.schema.json() e guarde junto da impressão digital.

5. Onde a migração costuma quebrar

Depois de olhar a documentação de migração e os relatos de quem já começou, os pontos de quebra se repetem bastante. Eu agruparia em quatro famílias.

Bibliotecas. O Runtime 2.0 removeu centenas de pacotes que vinham pré-instalados no 1.3. Entre eles estão nomes muito usados como xlsxwriter, lxml, networkx, pytorch, transformers e azure-storage. Aquele notebook que fazia import xlsxwriter sem configurar nada vai quebrar. A solução é declarar explicitamente no Environment, com versão fixada.

Python 3.13 e pandas. Todo pacote precisa ter wheel para Python 3.13 em Linux. Além disso o Spark 4.1 subiu as versões mínimas de pandas, NumPy e PyArrow e removeu APIs depreciadas. Quem usa pandas API on Spark vai esbarrar em iteritems (agora items) e append (agora ps.concat).

Scala e JARs. Todo artefato precisa ser recompilado para Scala 2.13, Java 21 e Spark 4.1. Quem tem listeners customizados ou mexe em internals do Spark ainda precisa revisar a troca de javax para jakarta.

Streaming. O EventHubConnector foi removido. Quem lê Event Hubs via Structured Streaming precisa migrar para o conector Kafka usando o endpoint compatível do Event Hubs, ou avaliar o Eventstream. E checkpoints de streaming podem não ser reaproveitáveis entre versões, o que exige um restart controlado com sink idempotente.

Atenção: não use configurações de compatibilidade legada como solução permanente. Elas servem para isolar a causa de uma falha durante o teste. Se você termina a migração cheio de flags legacy no Environment, você só adiou o problema para o próximo runtime.

6. Delta 4.2 e o risco que ninguém vê: o protocolo

Esse é o ponto que eu considero mais perigoso, justamente porque não gera erro no notebook. Tabelas Delta existentes continuam legíveis no 2.0. O risco aparece quando você habilita um recurso novo de tabela. Isso pode subir a versão de leitor e escritor do protocolo Delta. E upgrade de protocolo, em geral, não tem volta.

Pense no Delta como um contrato entre quem escreve e quem lê. O Spark do Runtime 2.0 pode assinar um contrato novo. Mas o SQL analytics endpoint, o modelo semântico em Direct Lake, o pipeline e o dataflow que leem a mesma tabela precisam entender esse contrato também. A própria Microsoft avisa que os recursos específicos do Delta 4.2 são experimentais e funcionam só nas experiências Spark. Se a tabela é consumida por outras cargas do Fabric, não habilite.

Outro detalhe: o Runtime 2.0 liga deletion vectors por padrão em tabelas novas (spark.databricks.delta.properties.defaults.enableDeletionVectors). Isso é ótimo para performance de UPDATE e DELETE, mas vale conferir o metadado das tabelas criadas no runtime novo antes de expor para outros consumidores.

Atenção: faça um inventário de quem consome cada tabela Delta antes de migrar. A célula 3 do notebook acima, com DESCRIBE DETAIL, mostra minReaderVersion, minWriterVersion e tableFeatures. Se esses valores mudarem depois da migração, pare e valide todos os consumidores.

7. O roteiro de migração que eu seguiria

A regra de ouro é não migrar produção no lugar. O runtime pode ser definido no workspace ou no item de Environment, e o Environment sobrepõe o padrão do workspace para os notebooks e jobs anexados. Isso permite uma migração cirúrgica, item por item.

  1. Inventário. Liste notebooks, Spark Job Definitions, Environments, bibliotecas com versão, JARs, configurações Spark customizadas, checkpoints de streaming e os consumidores de cada tabela Delta. Priorize o que tem Scala, código nativo e streaming.

  2. Environment paralelo. Num workspace de desenvolvimento crie um Environment novo com 2.0 (Spark 4.1, Delta 4.2), adicione só as bibliotecas que o workload usa, publique e anexe a cópias dos notebooks.

  3. Correção de código. Atualize pacotes, recompile JARs, ajuste chamadas de pandas e PySpark e substitua o EventHubConnector.

  4. Regressão. Rode a mesma entrada nos dois runtimes. Compare resultado, schema, histórico Delta, tempo e consumo de CU. Inclua testes de falha, retry e recuperação de checkpoint.

  5. Rollout em ondas. Anexe o Environment validado a um lote pequeno de jobs de produção, monitore, expanda. Troque o padrão do workspace por último.

  6. Rollback pronto. Mantenha o Environment 1.3 e os checkpoints antigos durante o período de observação. Voltar é reanexar o Environment antigo e iniciar uma nova sessão.

Um detalhe de mecânica que ajuda: trocar o runtime do workspace afeta só sessões novas. Sessões em execução continuam no runtime em que nasceram até serem encerradas. Isso evita surpresa no meio de um job longo, mas também significa que você pode ter os dois runtimes rodando ao mesmo tempo durante a transição.

Dica de campo: defina critérios de aceite antes de testar. Algo como "resultado idêntico, tempo igual ou menor, consumo de CU igual ou menor". Sem isso a discussão vira opinião e a migração empaca em reunião.

8. Quando migrar já (e quando segurar)

Nem todo workload tem o mesmo risco. Essa é a forma como eu priorizaria:

Cenário

Recomendação

Por quê

Notebooks PySpark e SQL com bibliotecas padrão

Migrar primeiro

Menor risco e ganho imediato do Native Execution Engine.

Workspaces e projetos novos

Já nascer no 2.0

É o padrão e evita uma migração futura.

Notebooks com muitas bibliotecas Python ou ML

Migrar com inventário de pacotes

Muitos pacotes saíram da imagem e precisam de wheel para Python 3.13.

JARs Scala customizados

Migrar após recompilar

Incompatibilidade binária entre Scala 2.12 e 2.13.

Structured Streaming com Event Hubs

Segurar até planejar o restart

Conector removido e checkpoints possivelmente incompatíveis.

Tabelas Delta lidas por Direct Lake, endpoint SQL ou pipelines

Migrar sem habilitar features novas de Delta

Upgrade de protocolo pode trancar outros consumidores.

Conclusão

O Runtime 2.0 é a maior atualização da engenharia de dados no Fabric desde que a plataforma nasceu. Spark 4.1 traz recursos de linguagem que deixam o código mais limpo, o motor nativo entrega performance de verdade e o Delta 4.2 aponta para tabelas que se organizam sozinhas. Ficar no 1.3 não é opção de longo prazo.

Mas a maturidade de um time de dados aparece justamente em migrações assim. Quem troca o runtime do workspace numa sexta-feira e torce está apostando. Quem cria um Environment paralelo, mede impressão digital de resultado, controla o protocolo Delta e faz rollout em ondas está fazendo engenharia. O esforço é maior no começo e muito menor no dia em que algo dá errado.

Takeaway: migre por Environment, não por workspace. Valide resultado com hash, não com olho. E não habilite feature nova de Delta em tabela que outro motor lê. Se você já começou a migração para o Runtime 2.0, me conta nos comentários o que quebrou primeiro no seu ambiente. Aposto que foi biblioteca.