Otimização de Desempenho na Databricks/AWS: Liquid Clustering, Layout de Arquivos, Skew e Ajuste de Warehouse
Otimize um lakehouse Databricks na AWS camada por camada: tamanhos de arquivo alvo (auto-optimize de 128 MB, piso de autotune de 256 MB, teto manual do OPTIMIZE de 1 GiB) e a escada dos arquivos pequenos; a decisão de particionamento (<1 TB nunca, 1-100 TB clustering, >=100 TB talvez) com a proteção de >=1 GB por partição; liquid clustering a fundo (CLUSTER BY / CLUSTER BY AUTO, até quatro chaves, limiares de clustering na escrita, OPTIMIZE incremental vs OPTIMIZE FULL, idempotência do bin packing) contra o caminho legado partição+ZORDER em uma comparação resolvida de OPTIMIZE vs CLUSTER BY com um gráfico de latência antes/depois; data skipping e estatísticas por arquivo; padrões do AQE (200 partições de shuffle, broadcast de 30 MB em runtime, fator de skew 5x, advisory de 64 MB), escada de broadcast/skew/salting com SQL real; Photon por carga de trabalho; dimensionamento resize-vs-scale do Databricks SQL warehouse e os níveis de cache de resultado/disco/Spark; profile de query e profiling na Spark UI com um cálculo resolvido de partições de shuffle; sete exemplos resolvidos, sete diagramas mermaid, cinco verificações interativas, estudos de caso de Comcast, Rivian e Punchh na AWS, uma caixa de atualização de outubro de 2026 e doze questões de prática.
Consultas lentas no lakehouse quase nunca são "o Spark é lento". Elas são uma camada específica se comportando mal: arquivos pequenos demais no S3, uma coluna de filtro cujos intervalos min/max se sobrepõem em todos os arquivos, uma única chave de join quente carregando 38% das linhas, ou um warehouse com o tamanho certo e a quantidade errada. Esta lição ensina a pilha na ordem em que você deve consertá-la, disposição → estatísticas → planejamento/runtime → serving, e ela ensina a versão de 2026 dessa pilha, não a de 2019.
Essa distinção importa mais do que qualquer knob isolado. A documentação atual da Databricks é direta: "A Databricks recomenda usar liquid clustering em vez de partições, ZORDER ou outras abordagens de disposição de dados" (docs .../tables/operations/optimize, última atualização 2026-03-31, espelho 2026-09-11), e "Recomendações de ajuste manual não se aplicam a tabelas gerenciadas do Unity Catalog, que usam ajuste automático de tamanho de arquivo" (docs .../tables/tune-file-size, 2026-09-11). Enquanto isso, a camada do motor continua se auto-ajustando: AQE está ativo por padrão desde o DBR 7.3, Photon é o motor de execução padrão nos SQL warehouses da Databricks, e a Databricks relata que "em 2025, o Databricks SQL entregou desempenho até 40% mais rápido em cargas de trabalho de produção, com melhorias aplicadas automaticamente" (blog, 2026-01-28).
Então o enquadramento honesto para outubro de 2026 é: o ajuste manual é mais valioso em tabelas externas, cargas de trabalho legadas de hive-metastore e clusters fora do padrão, e entender qual camada está quebrada antes de mexer em qualquer coisa. Cada fato desta lição vem das páginas de documentação AWS da Databricks sobre optimize, tamanho de arquivo, partições, clustering, data skipping, AQE, hints, predictive optimization, caching, comportamento de warehouse e query profile (todas acessadas ou datadas em outubro de 2026), além de blogs datados de engenharia da Databricks. Nenhuma taxa de DBU aparece aqui, custo é o assunto da lição 15.
=============================================================
A PILHA DE DESEMPENHO - conserte de cima para baixo, meça após cada passo
-------------------------------------------------------------
1. DISPOSIÇÃO DOS DADOS .. tamanhos de arquivo, CLUSTER BY, partições
2. ESTATÍSTICAS ........... min/max por arquivo -> data skipping
3. PLANEJAMENTO + RUNTIME AQE, broadcast, skew, Photon
4. SERVING + CACHING ...... tamanho vs quantidade de warehouse, caches
O loop fecha com: DBSQL query profile (warehouses)
Spark UI (clusters clássicos / de jobs)
Realidade da tabela gerenciada: predictive optimization roda
OPTIMIZE + VACUUM + ANALYZE para você; ajuste automático de
tamanho de arquivo escolhe as faixas de 256 MB / 1 GB; trabalho
de layout é sobretudo para tabelas EXTERNAS e ambientes legados.
Padrões verificados em out 2026, sem preços nesta lição.
=============================================================Nesta lição você vai:
- percorrer a pilha de quatro camadas e aprender qual sintoma pertence a qual camada antes de tocar em uma única configuração;
- dimensionar arquivos contra os três alvos oficiais, 128 MB do auto-optimize, a faixa de autotune de 256 MB → 1 GB e o teto manual de
OPTIMIZEde 1 GiB, e calcular as contagens de arquivo na mão; - aplicar a escada de particionamento (< 1 TB nunca · 1 to 100 TB clustering · ≥ 100 TB talvez) e a proteção de ≥ 1 GB por partição, e então ver o anti-padrão de alta cardinalidade falhar na aritmética;
- ir a fundo no liquid clustering:
CLUSTER BY,CLUSTER BY AUTO, ≤ 4 chaves, limiares de clustering na escrita,OPTIMIZEincremental vsOPTIMIZE FULL, chaves hierárquicas, idempotência do bin packing e os pisos de DBR com as suas datas; - rodar uma comparação resolvida de OPTIMIZE vs CLUSTER BY, a mesma tabela de 8 TB, o SQL legado de partição+
ZORDERao lado do SQL de liquid clustering, e ler o gráfico de latência antes/depois; - diagnosticar skew de join a partir de evidência real de profile e subir a escada documentada: hint de broadcast → split de skew do AQE → salting, com a trilha dos consertos errados ao lado;
- configurar o AQE com os padrões verificados (200 partições de shuffle, broadcast de 30 MB em runtime, advisory de 64 MB, fator de skew 5×);
- julgar o Photon por carga de trabalho, onde ele vence, onde Python UDFs o derrotam, e dimensionar um Databricks SQL warehouse com a doutrina de resize-vs-scale mais os níveis de cache;
- fazer profiling como operador: o query profile para warehouses, a Spark UI para clusters, e um cálculo de partições de shuffle que você faz no papel;
- estudar três histórias nomeadas de desempenho de clientes na AWS, ler a caixa de atualização de 2026 e praticar com doze questões no estilo de exame mais quatro verificações interativas.
1. A pilha de quatro camadas, e como escolher uma camada
Toda latência de consulta deste curso é decidida em quatro camadas, nesta ordem. Conserte-as de cima para baixo e meça de novo após cada mudança: nunca duas mudanças de uma vez, ou você não consegue atribuir o ganho.
Camada | A pergunta que ela responde | A evidência que você lê | A alavanca |
|---|---|---|---|
1. Disposição dos dados | As linhas certas estão nos mesmos arquivos? | contagem de arquivos, tamanho médio, % < 20 MB, DESCRIBE DETAIL | OPTIMIZE, CLUSTER BY, cadência de compactação |
2. Estatísticas | O motor consegue provar que um arquivo é irrelevante? | ícone "% of data pruned" no query profile | colunas de estatísticas, chaves de clustering, predicados sargable |
3. Planejamento + runtime | O plano e o shuffle estão saudáveis? | métricas de stage na Spark UI, SortMergeJoin (isSkew=true) | AQE, hints, salting, Photon, reescrita de UDF |
4. Serving + caching | O compute tem a forma certa? | "Bytes spilled to disk", Peak Queued Queries | tamanho do warehouse, max_num_clusters, caches |
Duas realidades de 2026 remolduram essa tabela. Primeiro, as camadas 3 e 4 cada vez mais se auto-ajustam: AQE está ligado por padrão (spark.databricks.optimizer.adaptive.enabled = true), Photon roda por padrão nos SQL warehouses da Databricks, e as tabelas gerenciadas ganham ajuste automático de tamanho de arquivo. Segundo, a camada 2 não tem teto em tabelas gerenciadas: para tabelas externas do Unity Catalog, as estatísticas são coletadas nas primeiras 32 colunas; em tabelas gerenciadas da UC, "o predictive optimization coleta estatísticas de pulsação de arquivo nas colunas mais usadas nos seus filtros de consulta, em vez de um conjunto fixo de colunas, de modo que essas tabelas não têm limite de 32 colunas" (docs .../tables/data-skipping, 2026-06-18).
O resto desta lição é essa pilha, camada por camada.
2. Camada 1: dimensionamento de arquivos e o problema dos arquivos pequenos no S3
Arquivos pequenos machucam porque cada arquivo custa um round-trip de LIST/GET do S3, uma task do Spark e a análise do rodapé do Parquet. Como o próprio guia da Databricks coloca, parte do custo é simplesmente "o tempo gasto só para abrir e fechar esses arquivos minúsculos" (guia discover, 2026-05-06). No S3 não existe camada de bloco: o arquivo é a unidade de paralelismo, a unidade de estatísticas e a unidade que o VACUUM recupera.
2.1 Três alvos oficiais, uma faixa saudável
Três números diferentes são todos "oficiais", e ensiná-los como um número só é o clássico erro de tutorial desatualizado:
Mecanismo | Tamanho de arquivo alvo | Fonte (data) |
|---|---|---|
Teto de saída do OPTIMIZE manual | 1.073.741.824 B = 1 GiB ( spark.databricks.delta.optimize.maxFileSize) | manual de linguagem do Delta OPTIMIZE; guia discover (2026-05-06) |
Auto-optimize definido como true (autoOptimize.optimizeWrite + autoCompact) | 128 MB | docs .../tables/tune-file-size (2026-09-11) |
Alvo do autotune (comportamento padrão em tabelas gerenciadas da UC) | < 2,56 TB → 256 MB · 2,56 to 10 TB → linear 256 MB → 1 GB · > 10 TB → 1 GB | mesma página (2026-09-11) |
Faixa de orientação saudável | "entre 16MB e 1GB" | guia discover (2026-05-06) |
Duas consequências que evitam os dois erros de layout mais comuns:
- 1 GiB é um teto, não uma meta para tabelas pequenas. Compactar uma tabela de 300 GB em arquivos de 1 GiB deixa ~300 arquivos e destrói o paralelismo do scan podado.
- Aumentar um alvo nunca mescla nada por si só. "Quando o tamanho de arquivo alvo de uma tabela cresce, os arquivos existentes não são re-otimizados em arquivos maiores pelo comando
OPTIMIZE" (tune-file-size, 2026-09-11): você precisa rodarOPTIMIZEdepois.
2.2 Exemplo resolvido E1: como é a saída de 8 TB de Auto Loader
Exemplo resolvido E1 - aritmética de arquivos para uma tabela de eventos de 8 TB no S3 (em out 2026)
ANTES (24 streams de Auto Loader, uma por frota de dispositivos)
Contagem de arquivos ........ 19.000
Média ....................... 8 TB / 19.000 ~= 440 MB <- ENGANOSO
Forma ....................... 60% dos arquivos abaixo de 20 MB (micro-batches de streaming)
Consulta de serving ......... 12.000 arquivos escaneados após a poda por min/max
p95 ......................... ~9,5 min em um warehouse X-Large, spill visível no profile
O ALVO (faixa de autotune 2.56 - 10 TB -> linear 256 MB -> 1 GB)
8 TB interpola para ~768 MB por arquivo
Teto do OPTIMIZE manual ............... 1 GiB (1.073.741.824 B)
Faixa saudável ....................... 16 MB - 1 GB
Contagens de referência da tabela dos docs:
1 TB @ 256 MB ....... 4.096 arquivos
10 TB @ 1 GB ......... 24.437 arquivos
100 TB @ 1 GB ........ 114.437 arquivos
DEPOIS (layout corrigido - veja o exemplo resolvido E4 para o SQL)
Contagem de arquivos ........ ~10.000 arquivos próximos do alvo
Poda ....................... > 95% dos arquivos excluídos pelos filtros customer_id / event_date
p95 ......................... ~25-40 s (faixa ilustrativa, coerente com os ganhos de leitura
de 2-12x relatados pela Databricks e um relato de point query de 12x,
blog 2024-05-22 - não é um benchmark medido desta tabela)A lição de E1 não é a faixa de 15 to 25×; é que contagem de arquivo e forma do arquivo são números diferentes. Uma média de 440 MB com mediana de 20 MB significa que o motor ainda paga o overhead de uma task por arquivo na maior parte da tabela.
2.3 Três mecanismos combatem arquivos pequenos, em ordem crescente de autoridade
- Escritas otimizadas (
autoOptimize.optimizeWrite/spark.databricks.delta.optimizeWrite.enabled) fazem shuffle antes de escrever para que cada partição emita arquivos de ~128 MB. Elas estão ligadas por padrão paraMERGE,UPDATE/DELETEcom subconsultas, CTAS/INSERT em SQL warehouses, e "No Databricks Runtime 13.3 LTS e superior, todas as tabelas registradas no Unity Catalog têm escritas otimizadas habilitadas para instruções CTAS e operações INSERT em tabelas particionadas". O custo é real: "Gravar menos arquivos grandes é mais eficiente … mas você ainda pode ver um aumento na latência de escrita porque os dados são embaralhados antes de serem gravados." - Auto compaction (
autoOptimize.autoCompact) "roda sincronamente no cluster que faz a escrita, depois que a escrita tem sucesso, e só compacta arquivos que ainda não foram compactados antes". No histórico da tabela ela aparece como uma operaçãoOPTIMIZEcomoperationParameters.auto=true. ParaMERGE,UPDATEeDELETEela é obrigatória: "Você não pode desligar essa funcionalidade." OPTIMIZEagendado ou previsto: "Para tabelas maiores que 1 TB, a Databricks recomenda rodarOPTIMIZEem um agendamento", clássicamente "diariamente, de preferência à noite, quando os preços de spot estão baixos" e em um job cluster separado, e para tabelas quentes com liquid clustering, "agendar um jobOPTIMIZEa cada uma ou duas horas" (docs de clustering, 2026-09-25), porque o clustering é incremental e a maioria das execuções é rápida.
Auto compaction e escritas otimizadas "não substituem completamente o OPTIMIZE" (tune-file-size, 2026-09-11), e se o predictive optimization estiver habilitado, "a Databricks recomenda desabilitar quaisquer jobs OPTIMIZE agendados" para você parar de pagar duas vezes (docs de clustering).
Como responder
Exercício de preenchimento com 4 lacunas. Digite as palavras que faltam e verifique a resposta.
Preencha os valores de layout de arquivo e do AQE exatamente como a documentação de outubro de 2026 os declara:
Auto-optimize definido como espaço mira arquivos de 128 MB; o teto de saída do OPTIMIZE manual é de espaço GiB; tabelas abaixo de 2,56 TB são autotunadas para um alvo de espaço MB; e o valor padrão de spark.sql.shuffle.partitions é espaço.
Banco de Palavras, arraste as palavras para os espaços acima:
⚠️ AVISO, não brigue com o caminho de escrita usando as próprias mãos. "Se você usa escritas otimizadas, a Databricks recomenda que você não rode
coalesce(n)ourepartition(n)imediatamente antes de uma escrita para controlar a quantidade de arquivos gravados" (tune-file-size, 2026-09-11), você paga dois shuffles para um trabalho só. Igualmente: nunca desligue a auto compaction no nível de sessão e esqueça. Ela não pode ser desabilitada paraMERGE/UPDATE/DELETE, mas umfalseno nível de sessão desliga silenciosamente ela para appends e CTAS nessa carga de trabalho.
- 📚 Você sabia? O
OPTIMIZEé seguro por construção: "A otimização de bin-packing é idempotente, ou seja, se ela rodar duas vezes no mesmo dataset, a segunda execução não tem efeito", e "Como oOPTIMIZEnão faz nenhuma mudança de dados na tabela, uma leitura antes e depois de umOPTIMIZEtem os mesmos resultados. RodarOPTIMIZEem uma tabela que é fonte de streaming não afeta nenhuma stream atual ou futura com essa tabela como fonte" (docs.../operations/optimize, 2026-03-31). O ZORDER, por contraste, nunca foi idempotente: era incremental e precisava ser reexecutado.
3. Camada 1, particionamento: a escada e a proteção de ≥ 1 GB
A orientação atual de particionamento é mais nova do que a maioria dos tutoriais e mais direta do que a maioria dos times espera: "A maioria das tabelas na Databricks com menos de 100 TB de dados não precisa de particionamento" (docs .../tables/partitions, 2026-07-01).
A segunda proteção é por partição: "verifique que cada partição contém pelo menos 1 GB de dados. Tabelas com menos partições e maiores tendem a superar tabelas com muitas partições menores" (mesma página). Contagem de partição é contagem de diretórios no seu bucket, então cada nível extra de cardinalidade multiplica chamadas LIST, entradas de _delta_log, varreduras de planejamento do OPTIMIZE e superfície de conflito do MERGE.
Não particione quando: a tabela estiver abaixo de 1 TB; a coluna de filtro tiver alta cardinalidade (user_id, event_id: clusterize em vez disso); os padrões de acesso mudarem (o particionamento "é estático e se manifesta como uma hierarquia de sistema de arquivos. Não existe um jeito fácil de mudar partições", página de best practices); a coluna for um timestamp que explodir em milhões de instantes (clusterize o timestamp bruto no lugar disso); ou você fosse particionar por um campo struct (PARTITIONED BY trata um struct como expressão, não como referência de coluna).
3.1 Exemplo resolvido E2, o anti-padrão de alta cardinalidade, em aritmética
Exemplo resolvido E2 - particionando a mesma tabela de eventos de 8 TB por customer_id
PARTITIONED BY (customer_id) com ~200.000 clientes distintos
Diretórios ................ 200.000
Bytes médios por partição . 8 TB / 200.000 ~= 40 MB
Proteção .................. >= 1 GB por partição -> VIOLADA por 25x
Sintoma .................... milhares de arquivos minúsculos por dia, a compactação
nunca acompanha, o listagem de _delta_log cresce, todo
OPTIMIZE e MERGE paga 200k listagens de diretório
CLUSTER BY (customer_id, event_date) no lugar
Diretórios ................ nenhum (particionamento oculto, protocolo 3/7)
Custo de reescrita ........ só metadados para declarar; um OPTIMIZE FULL uma vez
Poda ...................... granularidade de arquivo, guiada pelas estatísticas
min/max de cada arquivo
Mudança de ideia .......... ALTER TABLE ... CLUSTER BY (novas colunas) - sem reescritaO particionamento mantém exatamente dois papéis legítimos em 2026: movimentos de ciclo de vida por partição (DELETE/DROP PARTITION em uma coluna de data em escala ≥ 100 TB) e filtro de partição do Delta Sharing em tabelas compartilhadas: que é também o motivo de converter uma tabela compartilhada particionada para clustering ser bloqueado.
4. Liquid clustering, a fundo
"O liquid clustering é uma técnica de otimização de disposição de dados que substitui o particionamento da tabela e o ZORDER" (docs .../tables/clustering, última atualização 2026-09-25), e "você pode redefinir as chaves de clustering sem reescrever os dados existentes". Essa última cláusula é o design inteiro: o particionamento assenta uma hierarquia de sistema de arquivos que você não consegue mudar barato; o clustering guarda um conjunto declarado de chaves nos metadados da tabela e deixa o OPTIMIZE colocar fisicamente os intervalos juntos de forma incremental.
4.1 A superfície de declaração e os pisos de DBR
-- Declare at CREATE time (metadata only - no data is touched)
CREATE TABLE main.silver.events (
customer_id STRING,
event_date DATE,
event_type STRING,
payload STRING
) USING DELTA
CLUSTER BY (customer_id, event_date);
-- Or attach clustering to an existing table
ALTER TABLE main.silver.events CLUSTER BY (customer_id, event_date);
-- Let the platform pick and evolve keys (15.4 LTS+, UC managed + predictive optimization)
ALTER TABLE main.silver.events CLUSTER BY AUTO;
-- Remove clustering
ALTER TABLE main.silver.events CLUSTER BY NONE;Carregue esses números de versão com as suas datas: são os números que provas e revisões de arquitetura perguntam:
Fato | Valor | Fonte (data) |
|---|---|---|
Gatilho / clustering na escrita | DBR 13.3 LTS+ | docs de clustering (2026-09-25) |
Anúncio de GA | "generally available in Databricks from DBR 15.2" | blog (2024-05-22) |
Afirmação atual de GA | "GA for Delta Lake tables with DBR 15.4 LTS and above"; Iceberg em Public Preview a partir do 16.4 LTS+ | docs de clustering (2026-09-25) |
Recomendado para OPTIMIZE em tabela grande | DBR 17.3 LTS+ | docs de clustering (2026-09-25) |
OPTIMIZE … FULL (reclusterizar à força) | DBR 16.4 LTS+ | docs de clustering (2026-09-25) |
OPTIMIZE … FULL WHERE … (reclusterização parcial) | DBR 18.1+ | docs de clustering (2026-09-25) |
CLUSTER BY AUTO | DBR 15.4 LTS+, UC gerenciada + predictive optimization | docs de clustering (2026-09-25) |
Clustering hierárquico | DBR 17.1+ ( delta.liquid.hierarchicalClusteringColumns) | docs de clustering (2026-09-25) |
REPLACE PARTITIONED BY WITH CLUSTER BY | DBR 18.1+ (leitura a partir do 13.3 LTS+) | docs de clustering (2026-09-25) |
Máximo de chaves de clustering | 4: abaixo de 10 TB, mais chaves "podem degradar o desempenho ao filtrar em uma única coluna" | docs de clustering (2026-09-25) |
Estatísticas obrigatórias nas chaves | sim: primeiras 32 colunas por padrão | docs de clustering + data-skipping (2026) |
Protocolo da tabela | reader 3 / writer 7 ("particionamento oculto") | docs de compatibilidade de recursos (2026-09-11) |
Compatível com particionamento ou ZORDER? | Não: "Clustering is not compatible with partitioning or ZORDER" | docs de clustering (2026-09-25) |
4.2 Clustering na escrita: como o streaming aterrissa pré-clusterizado
Você não precisa esperar por um job noturno. Quando a escrita é grande o suficiente, o clustering roda durante a escrita, por contagem de chaves (tabelas gerenciadas da UC / outras tabelas Delta):
Chaves de clustering | Limiar em tabela gerenciada da UC | Outras tabelas Delta |
|---|---|---|
1 | 64 MB | 256 MB |
2 | 256 MB | 1 GB |
3 | 512 MB | 2 GB |
4 | 1 GB | 4 GB |
É por isso que um CTAS ou um micro-batch de streaming pode aterrissar fisicamente clusterizado sem nenhum manutenção agendada, e é por isso que o OPTIMIZE em uma tabela clusterizada costuma ser rápido: ele só reescreve o que ainda precisa de clustering.
4.3 O ciclo de vida, incluindo os dois momentos caros
Dois momentos nesse diagrama custam dinheiro de verdade e tempo de calendário de verdade:
⚠️ AVISO: declarar chaves é grátis; fazer dados antigos baterem com elas não é. "O comportamento padrão não aplica clustering a dados escritos anteriormente": você precisa rodar
OPTIMIZE <table> FULL, o que "pode levar horas" em uma tabela grande não clusterizada (docs de clustering, 2026-09-25). Orce isso como um job de migração único, no seu próprio cluster (instâncias compute-optimized, spot à noite), não como uma etapa de um pipeline de release. E nunca afirme "habilitar o clustering reescreveu a minha tabela":ALTER TABLE … CLUSTER BYé metadado; oOPTIMIZE FULLé a reescrita.
⚠️ IMPORTANTE, o
OPTIMIZEreescreve arquivos, então ele alimenta oVACUUM. Toda reclusterização apaga e adiciona arquivos; as versões antigas sobrevivem apenas durantedelta.deletedFileRetentionDuration(padrão de 7 dias). Defina essa propriedade antes de habilitar o predictive optimization: "Se você definirdelta.deletedFileRetentionDurationpara menos de 7 dias, o predictive optimization ainda retém os arquivos de dados por um mínimo de 7 dias quando rodaVACUUM FULL" (docs de predictive optimization, 2026-09-11), e nunca rodeVACUUMna mesma etapa de job que leitores ou streams de longa duração.
4.4 Exemplo resolvido E3: adotando clustering em uma tabela em produção
-- Worked example E3 - adopt liquid clustering on main.silver.events (8 TB, external on S3)
-- Step 1 - declare the keys your filters actually use (metadata only)
ALTER TABLE main.silver.events CLUSTER BY (customer_id, event_date);
-- correlated columns? one is enough: "If two columns are highly correlated,
-- you only need to include one of them in the clustering columns"
-- Step 2 - one-off full recluster, on its own job cluster, at night (DBR 16.4 LTS+)
OPTIMIZE main.silver.events FULL;
-- Step 3 - verify what actually ran (history disambiguates the operation type)
DESCRIBE HISTORY main.silver.events LIMIT 5;
-- operationParameters.clusterBy -> liquid clustering
-- operationParameters.zOrderBy -> legacy ZORDER
-- operationParameters.auto -> auto compaction
SHOW TBLPROPERTIES main.silver.events;
-- delta.clusteringColumns -> [customer_id, event_date]
-- Step 4 - incremental maintenance: hot table, every one or two hours
OPTIMIZE main.silver.events WHERE event_date >= current_date() - 2;
-- Step 5 - per-quarter deep maintenance on older data, still incremental
OPTIMIZE main.silver.events WHERE event_date < current_date() - 90;Por que um predicado no passo 4/5? OPTIMIZE <table> WHERE <predicate> limita a compactação/reclusterização a um subconjunto: mais barato, e ele encolhe a janela de conflito com escritores concorrentes, o mesmo motivo pelo qual as best practices empurram predicados de poda para dentro do MERGE... ON.
- 📚 Você sabia? O
DESCRIBE HISTORYtambém é a sua trilha de auditoria de manutenção: a auto compaction, o liquid clustering e o Z-ordering "aparecem todos no histórico da tabela como operaçõesOPTIMIZE", e você só os distingue através deoperationParameters.{auto, clusterBy, zOrderBy, predicate}(docs.../tables/history, 2026-09-11). Um dashboard que conta "execuções deOPTIMIZE" portanto superconta a auto compaction em larga margem: filtre porauto = 'false'para as execuções manuais.
5. Exemplo resolvido E4: OPTIMIZE vs CLUSTER BY, lado a lado
Mesma tabela, mesmos 8 TB, duas gerações de doutrina de layout. É essa a comparação para a qual a lição existe.
-- ============================================================
-- PATH A - legacy: Hive partitioning + ZORDER (pre-2024)
-- ============================================================
CREATE TABLE main.silver.events (
customer_id STRING, event_date DATE, event_type STRING, payload STRING
) USING DELTA
PARTITIONED BY (event_date); -- date cardinality "seems" safe
-- props you had to remember to set yourself
ALTER TABLE main.silver.events SET TBLPROPERTIES (
'autoOptimize.optimizeWrite' = 'true',
'autoOptimize.autoCompact' = 'true',
'delta.targetFileSize' = '536870912' -- 512 MB, manual guess
);
-- ZORDER the column your dashboards filter on (non-partitioned, stats required)
OPTIMIZE main.silver.events
WHERE event_date >= DATE'2026-10-01'
ZORDER BY (customer_id);
-- and because ZORDER "is not idempotent but is incremental", and because MERGE
-- rewrites blunt it (low shuffle merge helps but does not remove the need),
-- you re-run it on a schedule, forever, inside the date partition.
-- ============================================================
-- PATH B - liquid clustering (Oct 2026 recommendation)
-- ============================================================
CREATE TABLE main.silver.events (
customer_id STRING, event_date DATE, event_type STRING, payload STRING
) USING DELTA
CLUSTER BY (customer_id, event_date); -- no PARTITIONED BY, no ZORDER
-- one-off, after keys are first set or changed (DBR 16.4 LTS+)
OPTIMIZE main.silver.events FULL;
-- incremental maintenance from then on; bin packing happens in the same command
OPTIMIZE main.silver.events WHERE event_date >= current_date() - 2;
-- managed tables: skip the cron job entirely and let predictive optimization
-- run OPTIMIZE/VACUUM/ANALYZE for you (serverless-billed), then delete yours.O que mudou entre os dois caminhos:
Dimensão | Caminho A: partição + ZORDER | Caminho B: CLUSTER BY |
|---|---|---|
Declarado em | hierarquia de sistema de arquivos + um comando de reescrita | metadados da tabela (protocolo 3/7) |
Mudar a chave | efetivamente um rebuild da tabela; "não existe jeito fácil de mudar partições" | ALTER TABLE … CLUSTER BY, sem reescrita |
Manutenção | reexecutar o ZORDER (não é idempotente), dentro das partições | OPTIMIZE incremental, bin packing idempotente |
Predictive optimization | "não roda ZORDER … ignora arquivos Z-ordered" | totalmente suportado: operações CLUSTERING aparecem no histórico |
Coluna de alta cardinalidade | proibida como chave de partição | a melhor candidata a chave de clustering |
Benchmark (carga de warehouse de 1 TB) | particionamento estilo Hive ~uma ordem de magnitude mais lento que o liquid; Z-order no meio | clustering 2,5× mais rápido que Z-order (blog, 2023-06-29) |
Resultados de campo | : | point queries até 12× em uma firma de manufatura; desempenho de leitura 2 to 12× relatado no GA (blog, 2024-05-22) |
Leia esse gráfico como uma escada causal: escritas otimizadas e bin packing (camada 1, tamanho de arquivo) compram um ganho sólido mas limitado porque a poda ainda não consegue provar nada; CLUSTER BY (camada 1 + camada 2 juntas) é o que faz o filtro de customer_id excluir >95% dos arquivos. A última barra é rotulada como ilustrativa porque é um esboço de arquitetura de referência restrito à faixa publicada de leitura de 2 to 12× da Databricks, não um número da sua tabela: meça a sua no query profile.
5.1 Camada 2: por que o clustering é o que faz o skipping funcionar
O Delta grava estatísticas por arquivo no momento do commit: "valores mínimo e máximo, contagens de nulos e total de registros" (docs de data skipping, 2026-06-18), e no momento da consulta o motor pula qualquer arquivo cujas estatísticas provem que o predicado não pode casar. O clustering não muda o que pode ser pulado, "o modelo poda em granularidade de arquivo. Não existe um atalho em nível de diretório para perder" (blog da Databricks, 2026-01-06), ele muda quanto é pulado, ao deixar os intervalos da chave fisicamente contíguos.
O skipping falha silenciosamente quando: o predicado não é sargable (WHERE lower(col) = …, WHERE date_add(col, 1) = …); a coluna de filtro não tem estatísticas; os valores são distribuídos de forma uniforme por todos os arquivos; ou você combina colunas de um jeito que o min/max não consegue provar. Knobs de estatísticas: delta.dataSkippingNumIndexedCols (posicional) e delta.dataSkippingStatsColumns (DBR 13.3 LTS+, lista explícita, substitui o numérico), e "atualizar essas propriedades não recalcula automaticamente as estatísticas dos dados existentes". Por fim: "A Databricks recomenda que você não use ZORDER BY em colunas que não têm estatísticas coletadas, porque isso é ineficaz e usa recursos de computação desnecessários."
6. Camada 3, joins: AQE, broadcast, skew e salting
6.1 Os padrões do AQE com os quais você realmente está rodando
O AQE está ligado por padrão e faz quatro coisas: converter sort-merge join em broadcast em runtime, coalescer partições pós-shuffle, dividir (e, se necessário, replicar) partições enviesadas, e propagar relações vazias (docs de AQE, página datada de 2023-10-12, ainda atual em out 2026).
Configuração | Padrão | Significado |
|---|---|---|
spark.databricks.optimizer.adaptive.enabled | true | interruptor mestre do AQE |
spark.sql.shuffle.partitions | 200 (valor auto = shuffle auto-otimizado) | contagem inicial de partições de shuffle |
spark.databricks.adaptive.autoBroadcastJoinThreshold | 30 MB | chave de runtime para broadcast join |
spark.sql.autoBroadcastJoinThreshold | 10 MB | limiar estático de planejamento (Spark upstream) |
spark.sql.adaptive.advisoryPartitionSizeInBytes | 64 MB | alvo de coalescência após o shuffle |
spark.sql.adaptive.skewJoin.enabled | true | split automático de skew |
spark.sql.adaptive.skewJoin.skewedPartitionFactor | 5 | enviesado se > 5× a mediana e acima do limiar de bytes |
Limiar de bytes de skew | 256 MB (padrão do Spark: verifique no seu DBR) | segunda condição do teste de skew |
spark.sql.broadcastTimeout | 300 s | timeout de construção do broadcast |
Regra prática para dimensionar o shuffle: "cada task deveria processar aproximadamente 128MB a 200MB de dados" (guia discover, 2026-05-06). E mantenha o AQE ligado: "É recomendado manter o AQE habilitado" (best practices, 2026-09-11).
6.2 Exemplo resolvido E5: um join noturno de 52 minutos, diagnosticado
Exemplo resolvido E5 - skew de join, quatro movimentos, da evidência ao conserto
Setup ........ orders 400 GB, customers 2 GB, join em cust_id
uma chave (cust_id = 'ANON') carrega 38% de todas as linhas
Evidência ..... Spark UI stage mais longo: 200 tasks, mediana 12 s, Max 41 min
"Se a duração do Max estiver 50% acima da duração do percentil 75,
você pode estar sofrendo de skew" -> 41 min >> 1,5 x p75 => SKEW
uma task lê 180 GB de shuffle; a task mediana lê 0,4 GB
plano: dois nós Exchange + SortMergeJoin (o AQE não broadcastou:
customers filtrados = 40 MB, acima do limiar de runtime de 30 MB)-- Move 1 - REMOVE the shuffle (always the best outcome if it fits)
SELECT /*+ BROADCAST(customers) */ c.region, SUM(o.amount) AS revenue
FROM main.silver.orders o
JOIN main.gold.customers c ON o.cust_id = c.cust_id
GROUP BY c.region;
-- hint priority: BROADCAST > MERGE > SHUFFLE_HASH > SHUFFLE_REPLICATE_NL
-- aliases: MAPJOIN, BROADCASTJOIN for BROADCAST; result: job 52 min -> ~6 min
-- Move 2 - dimension too big to broadcast (4 GB): let AQE split the hot partition
-- plan marker: SortMergeJoin (isSkew=true) -> job 52 min -> ~18 min
SET spark.sql.adaptive.skewJoin.enabled = true; -- default true
SET spark.sql.adaptive.skewJoin.skewedPartitionFactor = 5; -- default 5
-- Databricks' own recommendation: "rely on AQE skew join handling rather than
-- use the skew join hint ... completely automatic and in general performs better"
-- Move 3 - the documented DataFrame escape hatch when AQE cannot see it
-- df_orders.hint("skew", "cust_id", "ANON")
-- df_orders.hint("skew", ["cust_id", "store_region_id"])
-- Move 4 - LAST RESORT: salting (hand-rolled, maintenance cost, query rewrite)
-- fact side : cust_id || '_' || CAST(FLOOR(RAND() * 16) AS INT) for the hot key
-- dim side : replicate the 'ANON' dimension row 16 times, one per salt value
-- join on the salted key, then GROUP BY partials and collapseA trilha dos consertos errados: os três aparecem em código de produção e nenhum deles ajuda:
SET spark.sql.shuffle.partitions = 2000: não divide nada; a chave quente ainda cai em exatamente uma partição..repartition("cust_id"): a mesma falha, mais um shuffle extra.- Um hint de skew quando o AQE já disparou: overhead para um conserto que já aconteceu; "A otimização de skew join tem algum overhead, então é melhor usá-la só quando necessário" (docs aposentadas de skew-join, página atualizada em 2024-05-13).
- 📚 Você sabia? Você lê as decisões do AQE direto do plano físico:
CustomShuffleReadercom a propriedadeCoalescedsignifica que as partições foram mergeadas após o shuffle,SortMergeJoincomisSkew=truesignifica que o AQE dividiu uma partição quente, e umLocalTableScansignifica que o AQE provou um lado do join vazio e cortou o caminho (docs de AQE, 2023-10-12). Três strings que valem grepar no plano de qualquer consulta lenta antes de você mudar uma única configuração.
7. Photon: julgue-o pela carga de trabalho, não pelo slogan
Photon é o motor vetorial em C++ que processa dados em lotes colunares, e "o Photon é usado por padrão nos SQL warehouses da Databricks" (best practices, 2026-09-11). A evidência publicada, com as suas ressalvas:
Afirmação | Número | Fonte (data) |
|---|---|---|
Ganhos de SQL de clientes | média 3×, máximo acima de 10× | artigo Photon SIGMOD 2022 |
TPC-DS / ETL vs baseline sem Photon | até 5,3× analítico, 4,4× ETL | blog (2025-10-16): instâncias Azure, benchmark da era do DBR 11.2: evidência do efeito do Photon, não uma afirmação de preço da AWS |
Databricks SQL, 5 anos de trabalho no motor | 5× desde 2022, +25% na versão de 2025 (Photon Vectorized Shuffle = 1,5× de throughput de shuffle) | blog (2025-06-17) |
Cargas de trabalho de produção em 2025 | até 40% mais rápido no total: exploratório ~40%, BI ~20%, ETL ~10%, espacial até 17× | blog (2026-01-28) |
Feature engineering de ML (ML Runtime 15.2+) | ~2×; point-in-time joins de 2,0 to 2,4× no AWS r6id.xlarge | blog (2026-02-13) |
Os vereditos por carga de trabalho:
- SQL pesado / BI / dashboards: os maiores ganhos (scans, joins e agregações vetorizados). Já ligado por padrão nos SQL warehouses.
- ETL / Spark batch: sólido, porém menor; caminhos dominados por
MERGEsão dominados no lugar pelo low shuffle merge (GA desde o DBR 10.4 LTS, "até 5x … em média na ordem de 2-3x"). - Código com muito Python e UDFs, o Photon não consegue acelerar a execução de Python UDFs: "Serialização é necessária para transferir dados entre Python e Spark. Isso desacelera significativamente as consultas" (best practices). O movimento vencedor é reescrever em SQL nativo (ou pandas UDFs se uma UDF for inevitável) primeiro, Photon depois. (Este é um raciocínio a partir da orientação documentada de UDFs, não um benchmark de Python publicado.)
- Loops de treino de ML construídos sobre RDDs/gráfico/código próprio, o Photon é em grande parte irrelevante ali.
8. Camada 4: dimensionamento de warehouse e os níveis de cache
8.1 Resize vs scale out: dois sintomas, dois knobs
A doutrina de dimensionamento da Databricks (docs de comportamento de warehouse, acessados em 2026-10; blog de alta concorrência, 2025-02-09): resize (aumentar o tamanho) quando as consultas precisam de mais memória por consulta (agregações, joins multi-tabela: observe o spill de disco); scale out (adicionar clusters) quando as consultas esperam (observe a fila). "Se as consultas estão fazendo spill para disco, aumente o tamanho do cluster. Verifique os spills no query profile." Warehouses clássicos e pro têm "um limite fixo de um cluster para cada 10 consultas simultâneas", a profundidade da fila é limitada com dureza em 1.000 consultas para todo tipo de warehouse, e um limiar prático de alerta é pico de consultas enfileiradas > 10 que não resolve quando o autoscaling dispara.
8.2 Exemplo resolvido E6: dimensionando um warehouse de 60 analistas
Exemplo resolvido E6 - 60 analistas simultâneos em um warehouse de dashboard (em out 2026)
Evidência ..... query profile: "Bytes spilled to disk" na agregação principal
monitoramento do warehouse: Peak Queued Queries = 27
Passo 1 TAMANHO ..... spill => aumente o tamanho do cluster: X-Large -> 2X-Large
(spill é um problema de memória/disco por consulta, não de quantidade)
Passo 2 QUANTIDADE .. 60 simultâneos / 10 por cluster = 6 clusters
27 enfileirados >> heurística de alerta de 10 => max_num_clusters >= 6
lembrete do teto rígido: a fila guarda no máximo 1.000 consultas
Passo 3 OCIOSO ....... auto-stop: serverless padrão 10 min (mín. de UI 5, mín. de API 1);
pro/classic padrão 45 min (mín. 10) - defina deliberadamente
Passo 4 COLD START ... serverless reinicia em 2-6 s; pro/classic ~4 min;
clusters são reciclados após 24 h de qualquer forma
Passo 5 REABRIR ...... profile de novo: spill sumiu? fila vazia? Se não, o
gargalo nunca foi o serving - são as camadas 1-3Para a maioria das cargas de trabalho "a Databricks recomenda usar um SQL warehouse serverless", e o conselho de dimensionamento dele é contraintuitivo mas correto: "Comece com um único warehouse maior e deixe os recursos serverless gerenciarem concorrência e desempenho. Geralmente é mais eficiente reduzir o tamanho se necessário do que começar pequeno e escalar para cima."
8.3 Quatro caches, quatro vidas úteis
Cache | O que ele guarda | Vida útil / invalidação |
|---|---|---|
Local result cache | resultados de consulta, em memória | vida útil do cluster ou até encher; limpo no stop/restart |
Remote result cache | resultados de consulta, persistidos como dados de sistema do workspace (só serverless) | sobrevive ao stop/restart do warehouse, compartilhado entre warehouses; invalidado quando as tabelas base atualizam |
Disk cache (antes "Delta cache") | cópias de arquivos de dados remotos no SSD local | gerenciado automaticamente, "no máximo metade" do SSD dos workers; invalidado quando os arquivos mudam; perdido na descomissionação do worker |
Spark cache ( df.cache()/persist()) | DataFrames arbitrários | ciclo de vida manual; envelhece entre commits |
Regras que evitam os bugs clássicos de cache: cache de resultado precisa de consultas determinísticas, um predicado no estilo NOW() nunca atinge ele; SET use_cached_result = false o contorna (só para teste/benchmark); o cache de disco "funciona por padrão com todos os tipos de instância que têm SSDs locais", e em compute clássico isso significa escolher workers com SSD; "Nos SQL warehouses e no Databricks Runtime 14.2 e superior, o comando CACHE SELECT é ignorado", o disk cache aprimorado é usado no lugar dele.
⚠️ AVISO, o cache do Spark é a armadilha, não o padrão. "usar locais incorretos em uma consulta pode consumir toda a memória disponível e desacelerar significativamente as consultas. Como regra prática, evite o cache do Spark" (best practices, 2026-09-11), e a página de best practices do Delta diz sem rodeios "Não use o cache do Spark com o Delta Lake" (atualizada em 2026-09-11). DataFrames cacheados comem memória de executor que shuffle e joins depois despejam em disco, a assinatura reveladora é spill subindo enquanto a aba Storage mostra grandes blocos cacheados. Os usos legítimos são estreitos: um dataset não-Delta verdadeiramente imutável e escaneado repetidamente dentro de uma única sessão, sempre pareado com
unpersist(); além do prewarming de um cluster frio antes da correria da manhã.
- 📚 Você sabia? O disk cache é o Delta cache renomeado: "O disk cache (antes conhecido como 'Delta cache') guarda cópias de dados remotos nos discos locais (por exemplo, SSD) das máquinas virtuais… A maneira recomendada (e mais fácil) de usar disk caching é escolher um tipo de worker com volumes SSD" (best practices, 2026-09-11). E como o autoscaling descomissiona workers, "se o autoscaling está habilitado, há alguma instabilidade com o cache": job clusters sensíveis a cache querem scale-down conservador ou pools.
9. Profiling: o query profile e a Spark UI
Profiling é o que faz a pilha virar um loop em vez de uma lista de verificação. Escolha a ferramenta pelo tipo de compute: query profile para SQL warehouses e serverless, Spark UI para clusters clássicos e de jobs.
O que cada sinal diz, e a única resposta documentada:
Sinal | Onde | Camada | Resposta |
|---|---|---|---|
"% de dados podados" baixo | nó de scan do query profile | 1 to 2 | chaves de clustering / colunas de estatísticas / predicado sargable |
"Bytes spilled to disk" | shuffle/sort do query profile | 4 | aumentar o tamanho do cluster do warehouse |
Max da task ≫ 1,5× p75 | Summary Metrics da Spark UI | 3 | broadcast → split de skew do AQE → salting |
Uma task lendo 10× os bytes de shuffle da mediana | métricas por task da Spark UI | 3 | chave quente: escada de skew |
Peak Queued Queries > 10 | monitoramento do warehouse | 4 | aumentar max_num_clusters |
"Query profile is not available" | query profile | : | acerto de result cache, não é bug |
Dois detalhes de acesso que economizam um ticket de suporte: acessar o profile exige dono da consulta "ou pelo menos a permissão CAN MONITOR no SQL warehouse", e "Um query profile não está disponível para consultas que rodam a partir do cache de consulta. Para contornar o cache de consulta, faça uma mudança trivial na consulta, como mudar ou remover o LIMIT."
9.1 Exemplo resolvido E7: dimensionando spark.sql.shuffle.partitions no papel
Exemplo resolvido E7 - quantas partições de shuffle para um shuffle de 400 GB em 16 cores?
Regra .......... 128 - 200 MB por task (guia discover, 2026-05-06)
Volume ......... 400 GB = 409.600 MB ; cores = 16
Limite sup. .... 409.600 / (128 MB x 16) = 200 partições <- 128 MB por task
Limite inf. .... 409.600 / (200 MB x 16) = 128 partições <- 200 MB por task
Decisão ........ manter 200 (o padrão), ou SET spark.sql.shuffle.partitions = 'auto'
e deixar o AQE coalescer em direção ao alvo advisory de 64 MB
Movimento ruim . 2000 partições => ~2 MB por task => overhead de agendamento vence
Também ruim .... coalesce/repartition logo antes de uma escrita com escritas
otimizadas ligadas - dois shuffles, um job (tune-file-size, 2026-09-11)
Nota de leitura: o Spark lê ~128 MB por task por core de Parquet/Delta, e é por isso
que 128 MB é o piso da faixa e não um número arbitrárioUma ressalva de streaming: spark.sql.shuffle.partitions "não pode ser alterado entre reinicializações de consulta a partir da mesma localização de checkpoint" (docs de AQE): escolha-o antes do primeiro start de uma streaming query.
- 📚 Você sabia? Uma consulta servida pelo result cache não tem query profile nenhum: "Query profile is not available" na UI geralmente significa a sua consulta foi rápida porque estava cacheada, não que o profiling está quebrado. Durante benchmarks,
SET use_cached_result = false(docs.../query-caching, acessado em 2026-10), e mantenha o nondeterminismo no estiloNOW()fora das consultas de BI que você quer cacheadas em produção.
10. A camada de automação: predictive optimization e materialized views
Dois recursos de 2026 tiram trabalho do seu runbook, mas só se você souber o que eles substituem.
Predictive optimization "roda automaticamente OPTIMIZE, VACUUM e ANALYZE em tabelas gerenciadas do Unity Catalog (Delta Lake e Iceberg)", está "habilitado por padrão para contas criadas em ou depois de 11 de novembro de 2024" com o rollout gradual das contas existentes "esperado para ser concluído até agosto de 2026", roda em compute serverless cobrado como um SKU de jobs serverless, e, a pegadinha, "o OPTIMIZE não roda ZORDER quando executado pelo predictive optimization. Em tabelas que usam Z-order, o predictive optimization ignora arquivos Z-ordered." Ele se aplica apenas a tabelas gerenciadas; tabelas externas continuam com a escada manual das seções 2 to 4. Observe-o com DESCRIBE TABLE EXTENDED … AS JSON → predictive_optimization_evaluations, o filtro "Automatic runs" do Catalog Explorer History, ou system.storage.predictive_optimization_operations_history.
Materialized views são a contraparte da camada de serving: "uma view onde resultados pré-computados ficam disponíveis para consulta e podem ser atualizados para refletir mudanças na entrada. Todas as materialized views são suportadas por um pipeline de ETL", criadas "usando um SQL warehouse Pro ou Serverless, ou dentro de um pipeline", atualizadas por pipelines serverless do Lakeflow, com a saída cacheada materializada em armazenamento de nuvem. A elegibilidade para refresh incremental é verificável antes de você commitar: EXPLAIN CREATE MATERIALIZED VIEW … (DBR 17.3+) relata "a Materialized View pode ser atualizada de forma incremental" ou não: um filtro/projeção/agregação sobre Delta se qualifica, LIMIT e fontes não-Delta (INPUT_NOT_IN_DELTA) não se qualificam, e uma expectation dentro da definição força um refresh completo. Tanto materialized views quanto streaming tables aceitam liquid clustering: "O liquid clustering se aplica a streaming tables e materialized views."
11. As armadilhas: dezesseis confusões que esta lição não vai deixar você manter
- "O tamanho de arquivo alvo é 1 GiB." O
OPTIMIZEmanual bate no teto de 1 GiB; o auto-optimize mira 128 MB; o autotune escolhe 256 MB abaixo de 2,56 TB. Compactar uma tabela de 300 GB em arquivos de 1 GiB prejudica o paralelismo do scan podado. - Assumir que tabelas gerenciadas precisam de rituais manuais de
OPTIMIZE. Autotune + predictive optimization são donos delas; um job cron noturno paga duas vezes. - Particionar uma tabela de 50 GB "por desempenho." Abaixo de 1 TB você não deveria particionar de forma alguma, e toda partição precisa de ≥ 1 GB.
ZORDERem uma coluna sem estatísticas, ou esperar que tabelas com ZORDER continuem mantidas sob o predictive optimization ("ignora arquivos Z-ordered").- Mudar chaves de clustering e esperar resultado instantâneo. Você precisa do
OPTIMIZE FULL, e ele "pode levar horas" em uma tabela grande não clusterizada. - Chaves de clustering em excesso / de cardinalidade alta demais. Máximo de 4; abaixo de 10 TB chaves extras degradam filtros de coluna única; chaves hierárquicas devem ser de baixa cardinalidade.
- A interação
VACUUM↔ clustering. Toda reclusterização apaga e adiciona; definadelta.deletedFileRetentionDurationantes de habilitar o predictive optimization (piso de 7 dias) e nunca faça vacuum junto com leitores de longa duração. coalesce(n)/repartition(n)logo antes de uma escrita com escritas otimizadas ligadas: dois shuffles, um job.- Um
autoOptimize.autoCompact = falseno nível de sessão que você esqueceu: obrigatório paraMERGE/UPDATE/DELETE, silenciosamente desligado para os seus appends. spark.sql.shuffle.partitions = 2000"porque o padrão é baixo demais." A regra é 128 to 200 MB por task; com AQE prefiraautoou comece alto e deixe a coalescência mergear.- Ir direto para um hint de skew quando o AQE já trata disso, ou salting antes de tentar broadcast. Se um dos lados couber em 10/30 MB, um hint
BROADCASTapaga o shuffle: estritamente melhor do que dividi-lo. - Abuso do cache do Spark. "Como regra prática, evite o cache do Spark"; "Não use o cache do Spark com o Delta Lake".
- Confusão com o result cache durante o debug. Uma consulta cacheada não tem profile; a latência do dashboard pode ser latência de cache.
- Ler skew só pelo relógio de parede. O teste é Max > 1,5× p75 nos Summary Metrics, não "o job pareceu lento".
- Assumir que o Photon conserta pipelines de Python UDF. Serialização domina; reescreva em SQL nativo primeiro.
- Rodar
OPTIMIZE … ZORDER BYem doze colunas, no meio do dia, no cluster interativo. Técnica legada, hora errada, cluster errado: use clustering, um job cluster de spot separado e um agendamento.
Veredito Comparativo: partição tradicional + ZORDER vs liquid clustering (AWS, em out de 2026)
- Partição tradicional + ZORDER, o playbook de 2019 to 2023, agora legado. Você declara uma hierarquia de diretórios estilo Hive (
PARTITIONED BY (event_date)) para poda, e então periodicamenteOPTIMIZE … ZORDER BY (hot_col)para colocar fisicamente uma coluna não-particionada junto nos arquivos. Funciona, e em ≥ 100 TB com uma chave de data genuinamente de baixa cardinalidade, o particionamento ainda pode pagar o seu custo paraDELETE/DROPpor partição e para o filtro de partição do Delta Sharing. Os custos: a hierarquia é "estática … não existe jeito fácil de mudar partições"; o ZORDER é uma reescrita que "não é idempotente mas é incremental" e é quebrada peloMERGE(o low shuffle merge ameniza, mas não remove isso); o ZORDER precisa de estatísticas ou é "ineficaz e um desperdício de recursos"; colunas de alta cardinalidade não podem ser chaves de partição de forma alguma; e, o prego operacional, o predictive optimization nunca rodaZORDERe "ignora arquivos Z-ordered", então tabelas legadas com ZORDER param silenciosamente de serem mantidas.- Liquid clustering, o padrão documentado para todas as tabelas novas.
CLUSTER BY(até 4 chaves, estatísticas obrigatórias, protocolo 3/7) é só metadados para declarar ou mudar; o clustering na escrita aterrissa tráfego de CTAS e streaming já clusterizado nos limiares de 64 MB/256 MB/512 MB/1 GB (gerenciadas); oOPTIMIZEentão faz clustering e bin packing juntos, de forma incremental, idempotente, em um cadência de 1 to 2 horas para tabelas quentes; oCLUSTER BY AUTOdeixa a plataforma escolher as chaves com consciência de custo; e o predictive optimization mantém isso para você. Evidência: clustering 2,5× mais rápido que Z-order em uma carga de warehouse de 1 TB, com o particionamento Hive "uma ordem de magnitude mais lento" (blog, 2023-06-29), point queries até 12× e leituras 2 to 12× no GA (blog, 2024-05-22). Os custos: umOPTIMIZE FULLúnico quando as chaves são definidas ou mudadas (horas em uma tabela grande), incompatibilidade com particionamento/ZORDER existentes, e um teto de 4 chaves.- Veredito em uma linha: tabela nova →
CLUSTER BY(ouCLUSTER BY AUTOem uma tabela gerenciada) com predictive optimization ligado; tabela existente particionada/com ZORDER → migre comALTER TABLE … REPLACE PARTITIONED BY WITH CLUSTER BY(18.1+) mais umOPTIMIZE FULL, mantendo particionamento apenas onde uma tabela ≥ 100 TB genuinamente precisa de movimentos de ciclo de vida por partição ou de sharing com filtro de partição. Cada número de versão nessa frase está datado na seção 4.1; carregue as datas, não só os números.
Atualizações de 2026 (em outubro de 2026)
O que mudou na pilha de desempenho da qual esta lição depende: cada linha verificada contra uma fonte primária:
- 2026-09-25: a página de clustering é o regimento atual. "O liquid clustering é uma técnica de otimização de disposição de dados que substitui o particionamento da tabela e o
ZORDER"; GA "para tabelas Delta Lake com Databricks Runtime 15.4 LTS e superior"; até quatro chaves;CLUSTER BY AUTO(15.4 LTS+); clustering hierárquico (17.1+);OPTIMIZE FULL(16.4 LTS+) eOPTIMIZE FULL WHERE(18.1+);REPLACE PARTITIONED BY WITH CLUSTER BY(18.1+); limiares de clustering na escrita 64 MB/256 MB/512 MB/1 GB para tabelas gerenciadas (docs.../aws/en/tables/clustering, atualizado em 2026-09-25). - 2026-09-11: as páginas de autotune de tamanho de arquivo e de predictive optimization foram atualizadas juntas. Faixas de autotune < 2,56 TB → 256 MB, 2,56 to 10 TB → linear 256 MB → 1 GB, > 10 TB → 1 GB; auto compaction e escritas otimizadas "sempre habilitadas para
MERGE,UPDATEeDELETE"; predictive optimization ligado por padrão para contas criadas em ou depois de 2024-11-11, com rollout "esperado para ser concluído até agosto de 2026", cobrado como serverless, e explicitamente sem rodarZORDER(docs.../tables/tune-file-size e.../optimizations/predictive-optimization, atualizados em 2026-09-11). - 2026-07-01: a escada de particionamento ficou explícita. "A maioria das tabelas na Databricks com menos de 100 TB … não precisa de particionamento"; < 1 TB não particione, 1 to 100 TB use liquid clustering, ≥ 100 TB "o particionamento pode melhorar o desempenho, mas a Databricks recomenda usar liquid clustering primeiro"; ≥ 1 GB por partição (docs.../tables/partitions, datado de 2026-07-01).
- 2026-06-18: as páginas de data skipping e de hints trazem a redação atual. Estatísticas por arquivo são "valores mínimo e máximo, contagens de nulos e total de registros"; tabelas gerenciadas da UC não têm teto de 32 colunas de estatísticas;
delta.dataSkippingStatsColumnssubstituidataSkippingNumIndexedCols; a gramática de hints cobreBROADCAST/MERGE/SHUFFLE_HASH/SHUFFLE_REPLICATE_NLmaisREBALANCE(ignorado se o AQE estiver desligado) (docs.../tables/data-skipping e.../sql-ref-syntax-qry-select-hints, datados de 2026-06-18). - 2026-05-06, o guia de ajuste foi re-datado com a regra por task intacta. Tamanho de arquivo saudável "entre 16MB e 1GB"; ajuste de shuffle "cada task deveria processar aproximadamente 128MB a 200MB"; "Sempre rode o comando OPTIMIZE em um job cluster separado"; instâncias compute-optimized recomendadas para
OPTIMIZE(databricks.com/discover/pages/optimize-data-workloads-guide, datado de 2026-05-06). - 2026-01-28, ganhos do lado do motor chegaram sem ajuste. "Em 2025, o Databricks SQL entregou desempenho até 40% mais rápido em cargas de trabalho de produção, com melhorias aplicadas automaticamente", exploratório ~40%, BI ~20%, ETL ~10%, espacial até 17×, vindos do Predictive Query Execution e do Photon Vectorized Shuffle (blog, 2026-01-28; blog companheiro de 5×/25%, 2025-06-17).
Casos Reais
📋 Caso A: Comcast (comunicações e mídia, Nuvem: AWS) Desafio. Petabytes de telemetria e bilhões de interações de voz de mais de 20 milhões de remotas de voz, com pipelines frágeis, dor de ingestão de arquivos pequenos e centenas de modelos para implantar em nuvem, on-prem e edge. Stack na AWS. Databricks, Delta Lake para ingest/enriquecimento/otimização de arquivos pequenos, MLflow gerenciado, autoscaling + spot instances, notebooks, Tableau a jusante:
Cloud: AWSna página da história. Resultado relatado. "10x de redução nos custos globais de compute para processar dados · 90% de redução nos recursos de DevOps necessários"; "O Delta Lake permitiu à Comcast otimizar a ingestão de dados, substituindo 640 máquinas por 64 enquanto melhorava o desempenho"; implantação de modelo "de semanas para minutos" (https://www.databricks.com/customers/comcast, página sem data, acessada em 2026-10). A Comcast também é citada entre clientes "already leveraging Databricks on AWS" no press release do AWS Marketplace da Databricks (2022-05-10), e a telemetria RDK dela é descrita como "petabytes de dados, coletados a cada 15 minutos de 70 milhões de dispositivos" (databricks.com/blog, 2021-05-10). Mapeia para esta lição. Seções 2 e 8, o destaque é um ganho estrutural: o tratamento de arquivos do Delta colapsou 640 máquinas em 64 antes de qualquer ajuste de consulta, que é exatamente a ordenação de camada primeiro que esta lição ensina, mais spot instances para a janela de compactação. ⏳ Nota temporal: origem da história 2019, página sem data acessada em out de 2026: um resultado de cliente relatado, não uma garantia de 2026.
📋 Caso B: Rivian (veículos elétricos, Nuvem: AWS) Desafio. Mais de 70.000 veículos na estrada "gerando terabytes de dados todos os dias", com ferramentas legadas que "lutavam para escalar pipelines de forma custo-eficiente", alimentando ML de manutenção preditiva e de previsão de carga. Stack na AWS. Databricks SQL, Delta Lake, Unity Catalog, e, explicitamente, "Liquid Clustering para disposição de dados" ao lado de "Unity Catalog para governança de dados, Delta Tables para armazenamento" para ML de previsão de carga em tempo real (databricks.com/blog, 2025-07-09);
Cloud: AWSna página da história. Resultado relatado. "50% better runtime performance" (https://www.databricks.com/your-ai/customers/rivian, 2024-05-30); "mais de 1.000 usuários de plataforma, um aumento de 75% em relação a um ano atrás" (https://www.databricks.com/customers/rivian, sem data, acessada em 2026-10). Mapeia para esta lição. Seções 4 e 5: a Rivian é o exemplo nomeado na AWS exatamente da alavanca que o exemplo resolvido E4 desta lição exercita: a telemetria de veículos é de alta cardinalidade, cresce rápido e é consultada por colunas de filtro, que é a lista documentada de casos ideais para clustering ("consultas que filtram em colunas de alta cardinalidade", "tabelas de crescimento rápido", "tabelas com padrões de acesso variados ou em mudança"). ⏳ Nota temporal: a cifra de 50% é de 2024-05-30; a menção ao stack de liquid clustering é de 2025-07-09.
📋 Caso C: Punchh (fidelidade de varejo / tecnologia para restaurantes, Nuvem: AWS) Desafio. MySQL da AWS superado, um warehouse caro demais para manter, e relatórios do Tableau lentos por joins executados do zero a cada relatório: "Quando você faz join de 12 tabelas em tempo real toda vez que um relatório é executado por um cliente, o custo para gerar o relatório pode ser alto." Stack na AWS. Databricks SQL, Delta Lake bronze/silver/gold, notebooks, Tableau por cima:
Cloud: AWSna página da história. Resultado relatado. "10x Faster time-to-insight · 30% Lower operational costs · 12% Increase in customer satisfaction" (https://www.databricks.com/customers/punchh, página sem data, acessada em 2026-10; origem da história 2021). Mapeia para esta lição. Seções 8 e 10, o join mais barato é o que você não roda: uma tabela gold pré-joinada (ou uma materialized view com refresh incremental) vence tanto um join de 12 vias no momento do relatório quanto qualquer resize de warehouse que teria forçado tudo na marra. ⏳ Nota temporal: história de origem 2021 ainda exibida em 2026: ensine o mecanismo, date as métricas.
Pontos Principais (Key Takeaways):
- Conserte de cima para baixo, meça após cada mudança: disposição → estatísticas → planejamento/runtime → serving. O query profile diz qual camada: "% de dados podados" baixo = camadas 1 to 2, skew/spill no plano = camada 3, "Bytes spilled to disk" ou fila = camada 4.
- Três números de tamanho de arquivo, três empregos: auto-optimize
true= 128 MB; autotune = 256 MB abaixo de 2,56 TB → 1 GB acima de 10 TB; teto doOPTIMIZEmanual = 1 GiB (1.073.741.824 B). Faixa saudável 16 MB, 1 GB; 1 GiB é um teto, não uma meta para tabela pequena; aumentar um alvo nunca mescla arquivos existentes, rode oOPTIMIZEdepois. - O particionamento agora é exceção: < 1 TB → nunca; 1 to 100 TB → liquid clustering ("o particionamento provavelmente afeta o desempenho negativamente com mais frequência do que ajuda"); ≥ 100 TB → talvez, verifique; e nunca abaixo de 1 GB por partição.
- O liquid clustering substitui tanto o particionamento quanto o
ZORDER:CLUSTER BYna criação ou noALTER TABLE, ≤ 4 chaves, estatísticas obrigatórias, protocolo 3/7, incompatível com partições/ZORDER, GA no DBR 15.4 LTS+ (gatilho 13.3 LTS+,FULL16.4 LTS+,AUTO15.4 LTS+). Declarar chaves é metadado; oOPTIMIZE FULLé a reescrita única: orce horas. - O
OPTIMIZEé incremental e idempotente: o bin packing não tem efeito na segunda execução, leituras antes/depois são idênticas, fontes de streaming não são afetadas; tabelas clusterizadas quentes querem uma execução a cada 1 to 2 horas, tabelas > 1 TB um agendamento, e o predictive optimization deveria substituir o seu cron (desligue o duplicado). - Escada de skew: detecte com Max > 1,5× p75, depois
BROADCAST(10 MB estático / 30 MB em runtime), depois o split automático do AQE (5× a mediana e acima do limiar de bytes, padrão do Spark de 256 MB), depois, só então, DataFrame.hint("skew")ou salting. Nunca conserte skew comshuffle.partitions. - O Photon vence por carga de trabalho: SQL/BI/ETL sim (5× desde 2022, +25% em 2025, até 40% mais rápido em cargas de produção de 2025); código com muitas Python UDFs não: reescreva em SQL nativo primeiro. O AQE continua ligado; partições de shuffle miram 128 to 200 MB por task.
- O dimensionamento de warehouse são dois knobs: spill → aumente o tamanho; fila (pico enfileirado > 10) → aumente
max_num_clustersem 1 cluster para cada 10 consultas simultâneas (teto da fila de 1.000). Cacheie de forma determinística: uma consulta cacheada não tem profile, o cache de disco precisa de SSDs locais, e o cache do Spark no Delta é explicitamente desaconselhado.
Perguntas de Prática (Practice Questions)
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Uma tabela de 8 TB atende um dashboard cujo query profile mostra 94% de dados podados mas 'Bytes spilled to disk' na agregação principal, e o monitoramento do warehouse não mostra fila. Qual é a camada gargalo e qual é o primeiro movimento documentado?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Qual conjunto de valores de tamanho de arquivo está correto segundo a documentação de outubro de 2026?
Como responder
Questão de múltipla escolha com 2 opções. Selecione uma opção e escolha Verificar resposta.
Uma tabela pode declarar chaves de clustering com CLUSTER BY mantendo também a sua cláusula PARTITIONED BY e um ZORDER existente: as três técnicas se empilham.
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Uma tabela com liquid clustering recebe escritas concorrentes pesadas. Qual cadência de manutenção os docs da Databricks recomendam, e por que ela é barata?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Nos Summary Metrics da Spark UI de um stage longo, a duração da task do percentil 75 é 12 s. Qual leitura dispara a suspeita de skew documentada?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
O AQE converte um sort-merge join em broadcast join em runtime quando um dos lados fica abaixo de qual limiar documentado?
Como responder
Questão de múltipla escolha com 2 opções. Selecione uma opção e escolha Verificar resposta.
Rodar o OPTIMIZE duas vezes seguidas no mesmo dataset dobra o benefício da compactação, e o OPTIMIZE muda os resultados da consulta porque ele reescreve arquivos.
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Um pipeline com muito Python e várias Python UDFs roda em um cluster com Photon e continua lento. O que a orientação de outubro de 2026 manda fazer primeiro?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Um warehouse de dashboard mostra Peak Queued Queries = 27 que não cai quando o autoscaling dispara, e os query profiles não mostram spill de disco. Qual é a resposta documentada?
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Um time propõe PARTITIONED BY (user_id) em uma tabela Delta de 300 GB no S3 para acelerar dashboards por usuário. O que a orientação atual diz?
Como responder
Questão de múltipla escolha com 2 opções. Selecione uma opção e escolha Verificar resposta.
Em uma tabela gerenciada do Unity Catalog, o predictive optimization vai manter uma tabela legada com ZORDER rodando ZORDER para você.
Como responder
Questão de múltipla escolha com 4 opções. Selecione uma opção e escolha Verificar resposta.
Qual predicado derrota o data skipping do Delta com mais confiança, mesmo que a coluna tenha estatísticas por arquivo?
Como responder
Exercício de associação com 5 pares. Associe cada item à esquerda com a resposta à direita.
Associe cada sintoma à camada à qual ele pertence e à alavanca documentada:
Arraste as respostas daqui:
Como responder
Exercício de ordenação com 7 itens. Coloque-os na ordem correta e verifique a resposta.
Ordene a sequência de depuração de skew de join exatamente como esta lição a constrói (evidência primeiro, conserto mais barato primeiro, reescrita por último):