Partilhar via


Compreender o desempenho da consulta Direct Lake

Além do design do modelo semântico e da complexidade da consulta, o desempenho do Direct Lake depende especificamente de tabelas Delta bem ajustadas para carregamento de coluna (transcodificação) eficiente e rápido e execução ideal de consultas. Certifique-se de aplicar a otimização V-Order. Além disso, mantenha o número de arquivos do Parquet pequeno, use grandes grupos de linhas e se esforce para minimizar o efeito das atualizações de dados no log Delta. Essas são práticas recomendadas comuns que podem ajudar a garantir a execução rápida de consultas em estados frio, morno, quente e muito quente no modo Direct Lake.

Este artigo explica como o desempenho do Direct Lake depende da integridade da tabela Delta e das atualizações de dados eficientes. Entender essas dependências é crucial. Você aprende que o layout de dados em seus arquivos Parquet é tão importante para o desempenho da consulta quanto um bom design de modelo semântico e medidas DAX (Data Analysis Expression) bem ajustadas.

O que precisa de saber

Este artigo pressupõe que você já esteja familiarizado com os seguintes conceitos:

  • Tabelas Delta no OneLake: Informações detalhadas sobre tabelas Delta no OneLake estão disponíveis na documentação de fundamentos do Microsoft Fabric.
  • Arquivos Parquet, grupos de linhas e log Delta: o formato de arquivo Parquet, incluindo como os dados são organizados em grupos de linhas e blocos de colunas, e como os metadados são tratados, é explicado na documentação do formato de arquivo Parquet. Consulte também a documentação do protocolo de log de transações Delta.
  • Otimização de tabelas Delta e V-Order: Consulte a documentação do Fabric Lakehouse sobre manutenção de tabelas Delta, como otimização de tabelas Delta Lake e V-Order.
  • Enquadramento e transcodificação: Atualizar um modelo semântico do Direct Lake (enquadramento) e carregar dados de coluna sob demanda (transcodificação) são conceitos importantes, abordados em um nível introdutório no artigo de visão geral do Direct Lake.
  • Mecanismo de Fórmula e Armazenamento: Quando uma consulta DAX é executada, o Mecanismo de Fórmula gera o plano de consulta e recupera os dados necessários e as agregações iniciais do Mecanismo de Armazenamento. As otimizações discutidas neste artigo se concentram no mecanismo de armazenamento. Para saber mais sobre o Formula Engine e o Storage Engine, explore a documentação do desenvolvedor do Analysis Services.
  • VertiPaq e VertiScan: No modo de importação e no modo Direct Lake, o mecanismo de armazenamento usa seu mecanismo VertiPaq para manter um armazenamento colunar na memória. O VertiScan permite que o Formula Engine interaja com o VertiPaq. Para obter mais informações, consulte a documentação do desenvolvedor do Analysis Services.
  • Codificação de dicionário: Tanto os arquivos Parquet quanto o VertiPaq usam a codificação de dicionário, que é uma técnica de compactação de dados aplicada a colunas individuais de vários tipos de dados, como int, long, date e char. Ele funciona armazenando cada valor de coluna exclusivo na memória como um inteiro usando a codificação Run Length Encoding (RLE)/Bit-Packing Hybrid. O VertiPaq sempre usa codificação de dicionário, mas o Parquet pode mudar para codificação simples ou codificação delta em algumas circunstâncias, como explicado em Codificações na documentação do Parquet File-Format, o que exigiria que o Direct Lake recodificasse os dados com efeito correspondente no desempenho da transcodificação.
  • Blocos de coluna e segmentos de coluna: Consulte a maneira como o Parquet e o VertiPaq armazenam dados de coluna para uma recuperação de dados eficiente. Cada coluna de uma tabela é dividida em pedaços menores que podem ser processados e compactados independentemente. VertiPaq chama esses segmentos de pedaços. Você pode utilizar o conjunto de linhas de esquema DISCOVER_STORAGE_TABLE_COLUMN_SEGMENTS para obter informações sobre os segmentos de coluna num modelo semântico Direct Lake.
  • Python e Jupyter Notebooks: Os Jupyter Notebooks fornecem um ambiente interativo para escrever e executar código Python. O conhecimento básico de Python é útil se você quiser seguir os trechos de código mais adiante neste capítulo. Para obter mais informações, consulte a referência da linguagem Python. Para obter informações sobre como usar blocos de anotações no Microsoft Fabric, consulte Como usar blocos de anotações - Microsoft Fabric.

O que afeta o desempenho da consulta Direct Lake

Esta seção resume os principais fatores que afetam o desempenho do Direct Lake. As secções seguintes oferecem explicações mais detalhadas:

  • Compressão V-Order: A eficácia da compressão pode afetar o desempenho da consulta, uma vez que uma melhor compressão conduz a um carregamento de dados mais rápido e a um processamento de consultas mais eficiente. O carregamento de dados é rápido porque o streaming de dados compactados aumenta a eficiência da transcodificação. O desempenho da consulta também é ideal porque a compactação V-Order permite que o VertiScan calcule os resultados diretamente sobre os dados compactados, ignorando a etapa de descompactação.
  • Tipos de dados: o uso de tipos de dados apropriados para colunas pode melhorar a compactação e o desempenho. Por exemplo, use tipos de dados inteiros em vez de cadeias de caracteres sempre que possível e evite armazenar inteiros como cadeias de caracteres.
  • Tamanho e contagem de segmentos: o VertiPaq armazena dados de colunas em segmentos. Um grande número de segmentos menores pode afetar negativamente o desempenho da consulta. Para tabelas grandes, o Direct Lake prefere tamanhos de segmento grandes, entre 1 milhão e 16 milhões de linhas.
  • Cardinalidade da coluna: colunas de cardinalidade alta (colunas com muitos valores exclusivos) podem diminuir o desempenho. Reduzir a cardinalidade sempre que possível pode ajudar.
  • Índices e agregações: Colunas com cardinalidade mais baixa se beneficiam da codificação de dicionário, que pode acelerar as consultas reduzindo a quantidade de dados que precisam ser digitalizados.
  • Retrocesso do DirectQuery: as operações de retrocesso podem resultar num desempenho de consulta mais lento, pois os dados agora devem ser buscados no endpoint de análise SQL da fonte de dados Fabric. Além disso, o fallback depende de planos de consulta híbrida para oferecer suporte ao DirectQuery e ao VertiScan, com algumas compensações no desempenho, mesmo quando o Direct Lake não precisa fazer fallback. Se possível, desative o fallback do DirectQuery para evitar planos de consulta híbridos.
  • Grau de residência de memória: Os modelos semânticos Direct Lake podem estar em estado frio, semiquente, quente ou quente com desempenho cada vez melhor de frio a quente. A transição rápida do frio para o quente é a chave para um bom desempenho do Direct Lake.
    • Frio: A loja VertiPaq está vazia. Todos os dados necessários para responder a uma consulta DAX devem ser carregados a partir de tabelas Delta.
    • Semiquente: o Direct Lake apenas descarta os segmentos de coluna durante o processamento que pertencem a grupos de linhas removidos. Isso significa que apenas os dados atualizados ou recém-adicionados devem ser carregados. Um modelo semântico Direct Lake também pode entrar em estado semiquente sob pressão de memória quando precisa descarregar segmentos e unir índices devido à pressão da memória.
    • Quente: os dados da coluna necessários para responder a uma consulta DAX já estão totalmente carregados na memória.
    • Quente: Os dados da coluna já estão completamente carregados na memória, os caches do VertiScan estão preenchidos e as consultas DAX são executadas nos caches.
  • Pressão da memória: o Direct Lake deve carregar todos os dados de coluna necessários para responder a uma consulta DAX na memória, o que pode esgotar os recursos de memória disponíveis. Devido à insuficiência de memória, o Direct Lake deve descarregar dados de colunas já carregados, podendo ser necessário recarregá-los para consultas DAX subsequentes. O dimensionamento adequado dos modelos semânticos Direct Lake pode ajudar a evitar recargas frequentes.

Residência de memória e desempenho de consulta

Direct Lake tem melhor desempenho no estado quente ou morno, enquanto os estados frios resultam num desempenho mais lento. O Direct Lake evita ao máximo voltar ao frio usando enquadramento incremental.

Inicialização

Após a carga inicial do modelo semântico, nenhum dado de coluna é residente na memória ainda. Direct Lake está frio. Quando um cliente envia uma consulta DAX para um modelo semântico Direct Lake no estado frio, o Direct Lake deve executar as seguintes tarefas principais para que a consulta DAX possa ser processada e respondida:

  • Transcodificação do dicionário VertiPaq. Direct Lake deve mesclar os dicionários Parquet locais para cada segmento de coluna, para criar um dicionário VertiPaq global para a coluna. Essa operação de mesclagem afeta o tempo de resposta da consulta.

  • Carregando pedaços de coluna de Parquet em segmentos de coluna. Na maioria dos casos, trata-se de um remapeamento direto de IDs de dados do Parquet para IDs do VertiPaq quando ambos os lados podem usar a codificação híbrida RLE/Bit-Packing. Se os dicionários Parquet usam codificação simples, o VertiPaq deve converter os valores para RLE/Bit-Packing codificação híbrida, o que leva mais tempo.

    • O desempenho do Direct Lake é ideal em arquivos V-Ordered Parquet porque o V-Ordering aumenta a qualidade da compactação RLE. O Direct Lake pode carregar dados V-Ordered compactados mais rápido do que dados menos compactados.
  • Geração de índices de junção. Se a consulta DAX acessar colunas de várias tabelas, o Direct Lake deverá criar índices de junção de acordo com as relações de tabela para que o VertiScan possa unir as tabelas corretamente. Para criar os índices de junção, o Direct Lake deve carregar os dicionários das colunas de chave que participam da relação e os segmentos da coluna da chave primária (a coluna no lado Único da relação de tabela).

  • Aplicação de vetores de exclusão Delta. Se uma tabela Delta de origem usar vetores de exclusão, o Direct Lake deverá carregar esses vetores de exclusão para garantir que os dados excluídos sejam excluídos do processamento de consultas.

    Observação

    O estado frio também pode ser induzido enviando um processClear comando seguido por um processFull XMLA para o modelo. O ProcessClear comando remove todos os dados e a associação com a versão da tabela Delta emoldurada. O ProcessFull comando XMLA executa o enquadramento para vincular o modelo à versão de confirmação Delta mais recente disponível.

Enquadramento incremental

Durante o enquadramento, o Direct Lake analisa o log Delta de cada tabela Delta e descarta segmentos de coluna carregados e índices de junção somente quando os dados subjacentes foram alterados. Os dicionários são retidos para evitar transcodificação desnecessária e novos valores são simplesmente adicionados aos dicionários existentes. Essa abordagem de estruturação incremental reduz a carga de recarga e beneficia o desempenho de consultas frias.

Você pode analisar a eficácia do enquadramento incremental usando a INFO.STORAGETABLECOLUMNSEGMENTS() função DAX, que encapsula o conjunto de linhas de esquema DISCOVER_STORAGE_TABLE_COLUMN_SEGMENTS. Siga estas etapas para garantir resultados significativos:

  1. Consulte o seu modelo semântico Direct Lake para garantir que ele esteja em um estado aquecido ou quente.
  2. Atualize a tabela Delta que você deseja investigar e atualize o modelo semântico Direct Lake para executar o enquadramento.
  3. Execute uma consulta DAX para recuperar informações de segmento de coluna usando a INFO.STORAGETABLECOLUMNSEGMENTS() função, como na captura de tela a seguir. A captura de tela usa uma pequena tabela de exemplo para fins de ilustração. Cada coluna tem apenas um único segmento. Os segmentos não são residentes na memória. Isso indica um verdadeiro estado frio. Se o modelo estava quente antes do enquadramento, isso significa que a tabela Delta foi atualizada usando um padrão de carregamento de dados destrutivo, impossibilitando o uso de enquadramento incremental. Os padrões de atualização de tabela delta são abordados mais adiante neste artigo.

Captura de ecrã mostrando o resultado de uma consulta DAX usando INFO.STORAGETABLECOLUMNSEGMENTS em um modelo semântico do Direct Lake, destacando a residência do segmento de coluna.

Observação

Quando uma tabela Delta não recebe atualizações, nenhuma recarga é necessária para colunas já residentes na memória. Ao usar padrões de atualização não destrutivos, as consultas mostram muito menos efeito de desempenho após o enquadramento, porque o enquadramento incremental essencialmente permite que o Direct Lake atualize partes substanciais dos dados existentes na memória.

Residência de memória completa

Com dicionários, segmentos de coluna e índices de junção carregados, o Direct Lake atinge o estado ativo com o desempenho da consulta no mesmo nível do modo de importação. Em ambos os modos, o número e o tamanho dos segmentos de coluna desempenham um papel crucial na otimização do desempenho da consulta.

Diferenças da tabela delta

Os arquivos Parquet organizam os dados por colunas em vez de linhas. O Direct Lake também organiza dados por colunas. O alinhamento facilita uma integração perfeita, mas existem diferenças importantes, especificamente no que diz respeito a grupos de linhas e dicionários.

Grupos de linhas versus segmentos de colunas

Um grupo de linhas em um arquivo Parquet consiste em blocos de coluna, e cada bloco contém dados para uma coluna específica. Um segmento de coluna em um modelo semântico, por outro lado, também contém um pedaço de dados de coluna.

Há uma relação direta entre a contagem total de grupos de linhas de uma tabela Delta e a contagem de segmentos para cada coluna da tabela de modelo semântico correspondente. Por exemplo, se uma tabela Delta em todos os seus arquivos Parquet atuais tiver três grupos de linhas no total, a tabela de modelo semântico correspondente terá três segmentos por coluna, conforme ilustrado no diagrama a seguir. Em outras palavras, se uma tabela Delta tem um grande número de pequenos grupos de linhas, uma tabela de modelo semântico correspondente também teria um grande número de segmentos de coluna minúsculos. Isso afetaria negativamente o desempenho da consulta.

Diagrama mostrando a relação entre grupos de linhas da tabela Delta e segmentos de colunas do modelo semântico no Direct Lake.

Observação

Como o Direct Lake prefere segmentos de coluna grandes, os grupos de linhas das tabelas Delta de origem devem, idealmente, ser grandes.

Dicionários locais versus dicionário global

A contagem total de grupos de linhas de uma tabela Delta também tem efeito direto no desempenho de transcodificação de dicionários porque os arquivos Parquet usam dicionários locais, enquanto os modelos semânticos Direct Lake usam um dicionário global para cada coluna, conforme descrito no diagrama a seguir. Quanto maior o número de grupos de linhas, maior o número de dicionários locais que o Direct Lake deve mesclar para criar um dicionário global e mais tempo leva para a transcodificação ser concluída.

Diagrama ilustrando o processo de fusão de dicionários locais de Parquet em um dicionário global para modelos semânticos Direct Lake.

Padrões de atualização de tabela delta

O método usado para ingerir dados em uma tabela Delta pode influenciar grandemente a eficiência de enquadramento incremental. Por exemplo, usar a opção Substituir ao carregar dados em uma tabela existente apaga o log Delta com cada carregamento. Isso significa que o Direct Lake não pode usar enquadramento incremental e deve recarregar todos os dados, dicionários e índices de junção. Esses padrões de atualização destrutivos afetam negativamente o desempenho da consulta.

Diagrama mostrando ingestão de dados e padrões de atualização para tabelas Delta no Direct Lake.

Esta seção aborda os padrões de atualização de tabelas Delta que possibilitam ao Direct Lake a utilização de enquadramento incremental. Isso preserva elementos do armazenamento de colunas VertiPaq, como dicionários, segmentos de coluna e índices de junção, a fim de maximizar a eficiência de transcodificação e melhorar o desempenho de consultas frias.

Processamento em lote sem particionamento

Esse padrão de atualização coleta e processa dados em grandes lotes em intervalos agendados, como semanalmente ou mensalmente. À medida que novos dados chegam, os dados antigos geralmente são removidos de forma rolante ou deslizante para manter o tamanho da tabela sob controle. No entanto, remover dados antigos pode ser um desafio se os dados estiverem espalhados pela maioria dos arquivos Parquet. Por exemplo, remover um dia em 30 dias pode afetar 95% dos arquivos do Parquet em vez de 5%. Neste caso, o Direct Lake teria que recarregar 95% dos dados, mesmo para uma operação de exclusão relativamente pequena. O mesmo problema também se aplica a atualizações de linhas existentes porque as atualizações são exclusões e acréscimos combinados. Você pode analisar o efeito das operações de exclusão e atualização usando o Delta Analyzer, conforme explicado mais adiante neste artigo.

Processamento em lote com particionamento

O particionamento de tabelas Delta pode ajudar a reduzir o efeito de operações de eliminação, pois a tabela é dividida em arquivos Parquet menores armazenados em pastas com base nos valores distintos na coluna de partições. As colunas de partição mais usadas incluem data, região ou outras categorias dimensionais. No exemplo anterior de remoção de um dia em 30 dias, uma tabela Delta particionada por data restringiria as exclusões apenas aos arquivos Parquet da partição para esse dia. No entanto, é importante notar que o particionamento extensivo pode resultar em um número substancialmente maior de arquivos Parquet e grupos de linhas, causando assim um aumento excessivo nos segmentos de coluna dentro do modelo semântico Direct Lake, afetando negativamente o desempenho da consulta. A escolha de uma coluna de partição de baixa cardinalidade é crucial para o desempenho da consulta. Como prática recomendada, a coluna deve ter menos de 100 a 200 valores distintos.

Carregamento incremental

Com o carregamento incremental, o processo de atualização apenas insere novos dados em uma tabela Delta sem afetar os arquivos e grupos de linhas existentes do Parquet. Não há exclusões. O Direct Lake pode carregar os novos dados incrementalmente sem ter que descartar e recarregar elementos de armazenamento de coluna VertiPaq existentes. Este método funciona bem com o sistema de enquadramento incremental do Direct Lake. O particionamento de tabela delta não é necessário.

Processamento de fluxos

O processamento de dados quase em tempo real, à medida que chegam, pode causar uma proliferação de pequenos arquivos Parquet e grupos de linhas, o que pode afetar negativamente o desempenho do Direct Lake. Tal como acontece com o padrão de carregamento incremental, não é necessário particionar a tabela Delta. No entanto, a manutenção frequente da tabela é essencial para garantir que o número de arquivos Parquet e grupos de linhas permaneça dentro dos limites de guardrail especificados no artigo de visão geral do Direct Lake. Em outras palavras, não se esqueça de executar o Spark Otimize regularmente, como diariamente ou até com mais frequência. O Spark Otimize é abordado novamente na próxima seção.

Observação

A análise real em tempo real é melhor implementada usando Eventstreams, bancos de dados KQL e Eventhouse. Consulte a documentação do Real-Time Intelligence no Microsoft Fabric para obter orientações.

Manutenção de tabela Delta

As principais tarefas de manutenção incluem realizar o 'vacuum' e otimizar as tabelas Delta. Para automatizar as operações de manutenção, você pode usar as APIs do Lakehouse, conforme explicado na documentação Manage the Lakehouse with Microsoft Fabric REST API .

Aspiração

O vácuo remove arquivos do Parquet que não estão mais incluídos na versão de confirmação Delta atual e são mais antigos do que um limite de retenção definido. A remoção desses arquivos do Parquet não afeta o desempenho do Direct Lake porque o Direct Lake carrega apenas os arquivos do Parquet que estão na versão de confirmação atual. Se você executar VACUUM diariamente com os valores padrão, as versões de confirmação Delta dos últimos sete dias serão mantidas para viagens no tempo.

Importante

Como um modelo semântico Direct Lake enquadrado faz referência a uma versão de confirmação Delta específica, você deve garantir que a tabela Delta mantenha essa versão até atualizar (enquadrar) o modelo novamente para movê-lo para a versão atual. Caso contrário, os usuários encontrarão erros de consulta quando o modelo semântico Direct Lake tentar acessar arquivos do Parquet que não existem mais.

Otimização Spark

A otimização da tabela delta mescla vários arquivos pequenos do Parquet em menos arquivos grandes. Como isso pode afetar o desempenho do Direct Lake em condições de frio, é uma boa prática otimizar esporadicamente, como nos fins de semana ou no final do mês. Otimize com mais frequência se pequenos arquivos do Parquet se acumularem rapidamente (pequenas atualizações de alta frequência) para garantir que a tabela Delta permaneça dentro dos limites do guardrail.

O particionamento pode ajudar a minimizar os efeitos da otimização no enquadramento incremental porque o particionamento efetivamente organiza os dados. Por exemplo, particionar uma grande tabela Delta com base em uma coluna date_key de baixa cardinalidade restringiria a manutenção semanal a um máximo de sete partições. A tabela Delta manteria a maioria dos arquivos Parquet existentes. O Direct Lake só teria que recarregar sete dias de dados.

Analisando atualizações da tabela Delta

Use o Delta Analyzer ou ferramentas semelhantes para estudar como as atualizações da tabela Delta afetam os arquivos Parquet e os grupos de linhas. Delta Analyzer permite acompanhar a evolução de ficheiros Parquet, grupos de linhas, blocos de colunas e colunas como resposta a operações de acréscimo, atualização e exclusão. O Delta Analyzer está disponível como um notebook Jupyter independente. Também está disponível na biblioteca semantic-link-labs. As secções seguintes utilizam semantic-link-labs. Esta biblioteca é fácil de instalar em um bloco de anotações usando o %pip install semantic-link-labs comando.

Tamanho do grupo de linhas

O tamanho ideal de grupo de linhas para modelos semânticos Direct Lake é entre 1 milhão e 16 milhões de linhas, mas o Fabric pode usar tamanhos de grupo de linhas maiores para tabelas grandes se os dados forem compactáveis. Geralmente, não recomendamos que você altere o tamanho padrão do grupo de linhas. É melhor permitir que o Fabric gerencie o layout da tabela Delta. Mas também é uma boa ideia verificar duas vezes.

O código Python a seguir pode servir como ponto de partida para analisar os tamanhos de grupos de linhas e outros detalhes de uma tabela Delta em um lakehouse conectado ao notebook Fabric. A tabela a seguir mostra a saída de uma tabela de exemplo com 1 bilhão de linhas.

import sempy_labs as labs
from IPython.display import HTML
from IPython.display import clear_output

table_name = "<Provide your table name>"

# Load the Delta table and run Delta Analyzer
df = spark.read.format("delta").load(f"Tables/{table_name}")
da_results = labs.delta_analyzer(table_name)

# Display the table summary in an HTML table.
clear_output()

df1 = da_results['Summary'].iloc[0]

html_table = "<table border='1'><tr><th>Column Name</th><th>{table_name}</th></tr>"
for column in da_results['Summary'].columns:
    html_table += f"<tr><td>{column}</td><td>{df1[column]}</td></tr>"
html_table += "</table>"

display(HTML(html_table))

Saída:

Parâmetro Valor
Nome da tabela sales_1
Contagem de linhas 1000000000
Grupos de linhas 24
Ficheiros Parquet 8
Máximo de linhas por grupo de linhas 51210000
Linhas mínimas por grupo de linhas 22580000
Média de linhas por grupo de linhas 41666666.666666664
VOrder ativado Verdade
Tamanho total 7700808430
Marca temporal 2025-03-24 03:01:02.794979

O resumo do Delta Analyzer mostra um tamanho médio de grupo de linhas de aproximadamente 40 milhões de linhas. Isso é maior do que o tamanho máximo recomendado do grupo de linhas de 16 milhões de linhas. Felizmente, o tamanho maior do grupo de linhas não causa problemas significativos para o Direct Lake. Grupos de linhas maiores facilitam tarefas contínuas de segmentação com sobrecarga mínima no motor de armazenamento. Por outro lado, grupos de linhas pequenas, aqueles significativamente abaixo de 1 milhão de linhas, podem causar problemas de desempenho.

Mais importante no exemplo anterior é que o Fabric distribuiu os grupos de linhas em oito arquivos Parquet. Isso se alinha com o número de núcleos na capacidade de malha para suportar operações de leitura paralela eficientes. Também é importante que os tamanhos individuais dos grupos de linhas não se desviem muito da média. Grandes variações podem causar uma carga não uniforme do VertiScan, resultando em um desempenho de consulta menos ideal.

Atualizações de janelas deslizantes

Para fins de ilustração, o exemplo de código Python a seguir simula uma atualização de janela contínua. O código remove as linhas com o DateID mais antigo de uma tabela Delta de exemplo. Em seguida, ele atualiza o DateID dessas linhas e as insere novamente na tabela de exemplo como as linhas mais recentes.

from pyspark.sql.functions import lit

table_name = "<Provide your table name>"
table_df = spark.read.format("delta").load(f"Tables/{table_name}")

# Get the rows of the oldest DateID.
rows_df = table_df[table_df["DateID"] == 20200101]
rows_df = spark.createDataFrame(rows_df.rdd, schema=rows_df.schema)

# Delete these rows from the table
table_df.createOrReplaceTempView(f"{table_name}_view")
spark.sql(f"DELETE From {table_name}_view WHERE DateID = '20200101'")

# Update the DateID and append the rows as new data
rows_df = rows_df.withColumn("DateID", lit(20250101))
rows_df.write.format("delta").mode("append").save(f"Tables/{table_name}")

A get_delta_table_history função na biblioteca semantic-link-labs pode ajudar a analisar o efeito desta atualização de janela contínua. Consulte o exemplo de código Python a seguir. Veja também a tabela com a saída após o trecho de código.

import sempy_labs as labs
from IPython.display import HTML
from IPython.display import clear_output

table_name = "<Provide your table name>"
da_results = labs.get_delta_table_history(table_name)

# Create a single HTML table for specified columns
html_table = "<table border='1'>"
# Add data rows for specified columns
for index, row in da_results.iterrows():
    for column in ['Version', 'Operation', 'Operation Parameters', 'Operation Metrics']:
        if column == 'Version':
            html_table += f"<tr><td><b>Version</b></td><td><b>{row[column]}</b></td></tr>"
        else:
            html_table += f"<tr><td>{column}</td><td>{row[column]}</td></tr>"
html_table += "</table>"

# Display the HTML table
display(HTML(html_table))

Saída:

Versão Descrição Valor
2 Funcionamento ESCREVER
Parâmetros de operação {'modo': 'Append', 'partitionBy': '[]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '548665', 'numOutputBytes': '4103076'}
1 Funcionamento SUPRIMIR
Parâmetros de operação {'predicado': '["(DateID#3910 = 20200101)"]'}
Métricas de operação {'numRemovedFiles': '8', 'numRemovedBytes': '7700855198', 'numCopiedRows': '999451335', 'numDeletionVectorsAdded': '0', 'numDeletionVectorsRemoved': '0', 'numAddedChangeFiles': '0', 'executionTimeMs': '123446', 'numDeletionVectorsUpdated': '0', 'numDeletedRows': '548665', 'scanTimeMs': '4820', 'numAddedFiles': '18', 'numAddedBytes': '7696900084', 'rewriteTimeMs': '198625'}
0 Funcionamento ESCREVER
Parâmetros de operação {'modo': 'Sobrescrever', 'partitionBy': '[]'}
Métricas de operação {'numFiles': '8', 'numOutputRows': '1000000000', 'numOutputBytes': '7700892169'}

O histórico do Delta Analyzer acima mostra que essa tabela Delta agora tem as seguintes três versões:

  • Versão 0: Esta é a versão original com oito arquivos Parquet e 24 grupos de linhas, conforme discutido na seção anterior.
  • Versão 1: Esta versão reflete a operação de exclusão . Embora apenas um único dia de dados (DateID = '20200101') tenha sido removido da tabela de amostra com cinco anos de transações de vendas, todos os oito arquivos Parquet foram afetados. Nas Métricas de Operação, numRemovedFiles são oito [arquivos Parquet] e numAddedFiles são 18 [arquivos Parquet]. Isso significa que a operação de eliminação substituiu os oito arquivos Parquet originais por 18 novos arquivos Parquet.
  • Versão 3: As métricas da operação revelam que mais um arquivo Parquet com 548.665 linhas foi adicionado à tabela Delta.

Após a atualização da janela deslizante, a versão mais recente do Delta inclui 19 arquivos Parquet e 21 grupos de linhas, com tamanhos que variam de 500 mil a 50 milhões de linhas. A atualização da janela rolante de 548.665 linhas afetou toda a tabela Delta de 1 bilhão de linhas. Ele substituiu todos os arquivos Parquet e grupos de linhas. Não se pode esperar que o enquadramento incremental melhore o desempenho em condições frias neste caso, e é improvável que a maior variação no tamanho dos grupos de linhas beneficie o desempenho em condições normais.

Atualizações da tabela delta

O código Python a seguir atualiza uma tabela Delta essencialmente da mesma maneira descrita na seção anterior. Na superfície, a função de atualização altera apenas o valor DateID das linhas existentes que correspondem a um determinado DateID. No entanto, os arquivos Parquet são imutáveis e não podem ser modificados. Abaixo da superfície, a operação de atualização remove arquivos Parquet existentes e adiciona novos arquivos Parquet. O resultado e o efeito são os mesmos da atualização da janela contínua.

from pyspark.sql.functions import col, when
from delta.tables import DeltaTable

# Load the Delta table
table_name = "<Provide your table name>"
delta_table = DeltaTable.forPath(spark, f"Tables/{table_name}")

# Define the condition and the column to update
condition = col("DateID") == 20200101
column_name = "DateID"
new_value = 20250101

# Update the DateID column based on the condition
delta_table.update(
    condition,
    {column_name: when(condition, new_value).otherwise(col(column_name))}
)

Atualizações de janelas contínuas particionadas

O particionamento pode ajudar a reduzir o efeito das atualizações de tabela. Pode ser tentador usar as chaves de data, mas uma rápida verificação de cardinalidade pode revelar que essa não é a melhor escolha. Por exemplo, a tabela de exemplo discutida até agora contém transações de vendas dos últimos cinco anos, equivalentes a cerca de 1800 valores de data distintos. Esta cardinalidade é demasiado elevada. A coluna de partição deve ter menos de 200 valores distintos.

column_name = 'DateID'
table_name = "<Provide your table name>"
table_df = spark.read.format("delta").load(f"Tables/{table_name}")

distinct_count = table_df.select(column_name).distinct().count()
print(f"The '{column_name}' column has {distinct_count} distinct values.")

if distinct_count <= 200:
    print(f"The '{column_name}' column is a good partitioning candidate.")
    table_df.write.format("delta").partitionBy(column_name).save(f"Tables/{table_name}_by_date_id")
    print(f"Table '{table_name}_by_date_id' partitioned and saved successfully.")
else:   
    print(f"The cardinality of the '{column_name}' column is possibly too high.")

Saída:

The 'DateID' column has 1825 distinct values.
The cardinality of the 'DateID' column is possibly too high.

Se não houver uma coluna de partição adequada, ela pode ser criada artificialmente reduzindo a cardinalidade de uma coluna existente. O código Python a seguir adiciona uma coluna Month removendo os dois últimos dígitos do DateID. Isto produz 60 valores distintos. Em seguida, o código de exemplo salva a tabela Delta particionada pela coluna Mês.

from pyspark.sql.functions import col, expr

column_name = 'DateID'
table_name = "sales_1"
table_df = spark.read.format("delta").load(f"Tables/{table_name}")

partition_column = 'Month'
partitioned_table = f"{table_name}_by_month"
table_df = table_df.withColumn(partition_column, expr(f"int({column_name} / 100)"))

distinct_count = table_df.select(partition_column).distinct().count()
print(f"The '{partition_column}' column has {distinct_count} distinct values.")

if distinct_count <= 200:
    print(f"The '{partition_column}' column is a good partitioning candidate.")
    table_df.write.format("delta").partitionBy(partition_column).save(f"Tables/{partitioned_table}")
    print(f"Table '{partitioned_table}' partitioned and saved successfully.")
else:   
    print(f"The cardinality of the '{partition_column}' column is possibly too high.")

Saída:

The 'Month' column has 60 distinct values.
The 'Month' column is a good partitioning candidate.
Table 'sales_1_by_month' partitioned and saved successfully.

O resumo do Delta Analyzer agora mostra que o layout da tabela Delta está bem alinhado com o Direct Lake. O tamanho médio do grupo de linhas é de cerca de 16 milhões de linhas, e o desvio absoluto médio dos tamanhos dos grupos de linhas e, portanto, dos tamanhos dos segmentos é inferior a 1 milhão de linhas.

Parâmetro Valor
Nome da tabela vendas_1_por_mês
Contagem de linhas 1000000000
Grupos de linhas 60
Ficheiros Parquet 60
Máximo de linhas por grupo de linhas 16997436
Linhas mínimas por grupo de linhas 15339311
Média de linhas por grupo de linhas 16666666.666666666
VOrder ativado Verdade
Tamanho total 7447946016
Marca temporal 2025-03-24 03:01:02.794979

Após uma atualização de janela contínua em relação a uma tabela de amostra particionada, o histórico do Delta Analyzer mostra que apenas um arquivo Parquet foi afetado. Consulte a tabela de saída a seguir. A versão 2 tem exatamente 16.445.655 linhas copiadas do antigo arquivo Parquet para um arquivo Parquet de substituição, e a versão 3 adiciona um novo arquivo Parquet com 548.665 linhas. No total, o Direct Lake só precisa recarregar cerca de 17 milhões de linhas, uma melhoria considerável em relação a uma recarga de 1 bilhão de linhas sem particionamento.

Versão Descrição Valor
2 Funcionamento ESCREVER
Parâmetros de operação {'mode': 'Append', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '548665', 'numOutputBytes': '4103076'}
1 Funcionamento SUPRIMIR
Parâmetros de operação {'predicado': '["(DateID#3910 = 20200101)"]'}
Métricas de operação {'numRemovedFiles': '1', 'numRemovedBytes': '126464179', 'numCopiedRows': '16445655', 'numDeletionVectorsAdded': '0', 'numDeletionVectorsRemoved': '0', 'numAddedChangeFiles': '0', 'executionTimeMs': '19065', 'numDeletionVectorsUpdated': '0', 'numDeletedRows': '548665', 'scanTimeMs': '1926', 'numAddedFiles': '1', 'numAddedBytes': '121275513', 'rewriteTimeMs': '17138'}
0 Funcionamento ESCREVER
Parâmetros de operação {'mode': 'Overwrite', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '60', 'numOutputRows': '1000000000', 'numOutputBytes': '7447681467'}

Padrão de somente acréscimo seguido pelo Spark Optimize

Padrões de acréscimo não afetam os arquivos Parquet existentes. Eles funcionam bem com o enquadramento incremental do Direct Lake. No entanto, não se esqueça de otimizar suas tabelas Delta para consolidar arquivos Parquet e grupos de linhas, conforme discutido anteriormente neste artigo. Apêndices pequenos e frequentes podem acumular arquivos rapidamente e distorcer a uniformidade dos tamanhos dos grupos de linhas.

O resultado seguinte mostra o histórico do Delta Analyzer para uma tabela não particionada, comparada a uma tabela particionada. O histórico inclui sete apêndices e uma operação de otimização subsequente.

Versão Descrição Valor no layout padrão Valor na disposição particionada
8 Funcionamento OTIMIZAR OTIMIZAR
Parâmetros de operação {'predicado': '[]', 'auto': 'false', 'clusterBy': '[]', 'vorder': 'true', 'zOrderBy': '[]'} {'predicado': '["('Month >= 202501)"]', 'auto': 'false', 'clusterBy': '[]', 'vorder': 'true', 'zOrderBy': '[]'}
Métricas de operação {'numRemovedFiles': '8', 'numRemovedBytes': '991234561', 'p25FileSize': '990694179', 'numDeletionVectorsRemoved': '0', 'minFileSize': '990694179', 'numAddedFiles': '1', 'maxFileSize': '990694179', 'p75FileSize': '990694179', 'p50FileSize': '990694179', 'numAddedBytes': '990694179'} {'numRemovedFiles': '7', 'numRemovedBytes': '28658548', 'p25FileSize': '28308495', 'numDeletionVectorsRemoved': '0', 'minFileSize': '28308495', 'numAddedFiles': '1', 'maxFileSize': '28308495', 'p75FileSize': '28308495', 'p50FileSize': '28308495', 'numAddedBytes': '28308495'}
7 Funcionamento ESCREVER ESCREVER
Parâmetros de operação {'modo': 'Append', 'partitionBy': '[]'} {'mode': 'Append', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '547453', 'numOutputBytes': '4091802'} {'numFiles': '1', 'numOutputRows': '547453', 'numOutputBytes': '4091802'}
6 Funcionamento ESCREVER ESCREVER
Parâmetros de operação {'modo': 'Append', 'partitionBy': '[]'} {'mode': 'Append', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '548176', 'numOutputBytes': '4095497'} {'numFiles': '1', 'numOutputRows': '548176', 'numOutputBytes': '4095497'}
5 Funcionamento ESCREVER ESCREVER
Parâmetros de operação {'modo': 'Append', 'partitionBy': '[]'} {'mode': 'Append', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '547952', 'numOutputBytes': '4090107'} {'numFiles': '1', 'numOutputRows': '547952', 'numOutputBytes': '4093015'}
4 Funcionamento ESCREVER ESCREVER
Parâmetros de operação {'modo': 'Append', 'partitionBy': '[]'} {'mode': 'Append', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '548631', 'numOutputBytes': '4093134'} {'numFiles': '1', 'numOutputRows': '548631', 'numOutputBytes': '4094376'}
3 Funcionamento ESCREVER ESCREVER
Parâmetros de operação {'modo': 'Append', 'partitionBy': '[]'} {'mode': 'Append', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '548671', 'numOutputBytes': '4101221'} {'numFiles': '1', 'numOutputRows': '548671', 'numOutputBytes': '4101221'}
2 Funcionamento ESCREVER ESCREVER
Parâmetros de operação {'modo': 'Append', 'partitionBy': '[]'} {'mode': 'Append', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '546530', 'numOutputBytes': '4081589'} {'numFiles': '1', 'numOutputRows': '546530', 'numOutputBytes': '4081589'}
1 Funcionamento ESCREVER ESCREVER
Parâmetros de operação {'modo': 'Append', 'partitionBy': '[]'} {'mode': 'Append', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '1', 'numOutputRows': '548665', 'numOutputBytes': '4101048'} {'numFiles': '1', 'numOutputRows': '548665', 'numOutputBytes': '4101048'}
0 Funcionamento ESCREVER ESCREVER
Parâmetros de operação {'modo': 'Sobrescrever', 'partitionBy': '[]'} {'mode': 'Overwrite', 'partitionBy': '["Mês"]'}
Métricas de operação {'numFiles': '8', 'numOutputRows': '1000000000', 'numOutputBytes': '7700855198'} {'numFiles': '60', 'numOutputRows': '1000000000', 'numOutputBytes': '7447681467'}

Olhando para as métricas de operação da versão 8, vale a pena salientar que a operação de otimização para a tabela não particionada mesclou oito arquivos Parquet afetando cerca de 1 GB de dados, enquanto a operação otimizada da tabela particionada mesclou sete arquivos Parquet afetando apenas cerca de 25 MB de dados. Segue-se que o Direct Lake teria um melhor desempenho com a tabela particionada.

Considerações e limitações

As considerações e limitações para otimizar o desempenho do Direct Lake são as seguintes:

  • Evite padrões de atualização destrutivos em tabelas Delta grandes para preservar o enquadramento incremental no Direct Lake.
  • Tabelas Delta pequenas não precisam ser otimizadas para enquadramento incremental.
  • Defina um tamanho de grupo de linhas entre 1 milhão e 16 milhões de linhas para criar segmentos de coluna no Direct Lake, com 1 milhão a 16 milhões de linhas. Direct Lake prefere grandes segmentos de colunas.
  • Evite colunas de partição de alta cardinalidade porque a transcodificação Direct Lake é menos eficiente com muitos arquivos Parquet pequenos e grupos de linhas do que com menos arquivos Parquet grandes e grupos de linhas.
  • Devido à procura imprevista por recursos de computação e memória, um modelo semântico pode ser recarregado em outro nó de Fabric cluster em estado inativo.
  • O Direct Lake não usa estatísticas delta\Parquet para ignorar grupos de linhas\ficheiros, a fim de otimizar o carregamento de dados.