Ir para o conteúdo principal

Explorando o Delta Lake: Desvendando o Log de Transações

Diving Into Delta Lake: Unpacking The Transaction Log

Publicado: 21 de agosto de 2019

Produto12 min de leitura

Logotipo do Delta Lake.
O log de transações é fundamental para entender o Delta Lake porque é o elo comum que percorre muitos de seus recursos mais importantes, incluindo transações ACID, tratamento de metadados escalonável, viagem no tempo e muito mais. Neste artigo, exploraremos o que é o log de transações do Delta Lake, como ele funciona no nível do arquivo e como ele oferece uma solução elegante para o problema de várias leituras e gravações simultâneas.

O que é o log de transações do Delta Lake?

O log de transações do Delta Lake (também conhecido como DeltaLog) é um registro ordenado de cada transação que já foi realizada em uma tabela Delta Lake desde o início.

Para que serve o log de transações?

Fonte única de verdade

O Delta Lake é construído sobre o Apache Spark™ para permitir que vários leitores e gravadores de uma determinada tabela trabalhem na tabela ao mesmo tempo. Para mostrar aos usuários visualizações corretas dos dados em todos os momentos, o log de transações do Delta Lake serve como uma fonte única de verdade - o repositório central que rastreia todas as alterações que os usuários fazem na tabela.

Quando um usuário lê uma tabela Delta Lake pela primeira vez ou executa uma nova consulta em uma tabela aberta que foi modificada desde a última vez que foi lida, o Spark verifica o log de transações para ver quais novas transações foram lançadas na tabela e, em seguida, atualiza a tabela do usuário final com essas novas alterações. Isso garante que a versão de uma tabela de um usuário esteja sempre sincronizada com o registro mestre a partir da consulta mais recente e que os usuários não possam fazer alterações divergentes e conflitantes em uma tabela.

A implementação da atomicidade no Delta Lake

Uma das quatro propriedades das transações ACID, a atomicidade, garante que as operações (como um INSERT ou UPDATE) realizadas em seu data lake sejam concluídas totalmente ou não sejam concluídas. Sem essa propriedade, é muito fácil para uma falha de hardware ou um bug de software fazer com que os dados sejam gravados apenas parcialmente em uma tabela, resultando em dados confusos ou corrompidos.

O log de transações é o mecanismo pelo qual o Delta Lake consegue oferecer a garantia de atomicidade. Para todos os efeitos, se não estiver registrado no log de transações, nunca aconteceu. Ao registrar apenas as transações que são executadas de forma completa e usando esse registro como a única fonte de verdade, o log de transações do Delta Lake permite que os usuários raciocinem sobre seus dados e tenham tranquilidade sobre sua confiabilidade fundamental, em escala de petabytes.

Como funciona o log de transações?

Dividindo as transações em commits atômicos

Sempre que um usuário executa uma operação para modificar uma tabela (como um INSERT, UPDATE ou DELETE), o Delta Lake divide essa operação em uma série de etapas discretas compostas por uma ou mais das ações abaixo.

  • Adicionar arquivo - adiciona um arquivo de dados.
  • Remover arquivo - remove um arquivo de dados.
  • Atualizar metadados - Atualiza os metadados da tabela (por exemplo, alterar o nome, o esquema ou o particionamento da tabela).
  • Definir transação - Registra que um trabalho de streaming estruturado confirmou um micro-lote com o ID fornecido.
  • Alterar protocolo - habilita novos recursos alternando o log de transações do Delta Lake para o protocolo de software mais recente.
  • Informações de commit - Contém informações sobre o commit, qual operação foi feita, de onde e em que horário.

Essas ações são então registradas no log de transações como unidades atômicas ordenadas conhecidas como commits.

Por exemplo, suponha que um usuário crie uma transação para adicionar uma nova coluna a uma tabela e adicionar mais dados a ela. O Delta Lake dividiria essa transação em suas partes componentes e, assim que a transação for concluída, adicionaria-as ao log de transações como os seguintes commits:

  1. Atualizar metadados - alterar o esquema para incluir a nova coluna
  2. Adicionar arquivo - para cada novo arquivo adicionado

O log de transações do Delta Lake no nível do arquivo

Quando um usuário cria uma tabela Delta Lake, o log de transações dessa tabela é criado automaticamente no subdiretório _delta_log. À medida que ele faz alterações nessa tabela, essas alterações são registradas como commits atômicos ordenados no log de transações. Cada commit é gravado como um arquivo JSON, começando com 000000.json. Alterações adicionais na tabela geram arquivos JSON subsequentes em ordem numérica crescente, de modo que o próximo commit seja gravado como 000001.json, o seguinte como 000002.json e assim por diante.

Diagrama da estrutura de arquivos do log de transações do Delta Lake.

Então, como um exemplo, talvez possamos adicionar registros adicionais à nossa tabela dos arquivos de dados 1.parquet e 2.parquet. Essa transação seria automaticamente adicionada ao log de transações, salva em disco como commit 000000.json. Então, talvez mudemos de ideia e decidamos remover esses arquivos e adicionar um novo arquivo (3.parquet). Essas ações seriam registradas como o próximo commit no log de transações, como 000001.json, conforme mostrado abaixo.

Diagrama ilustrando dois commits que executam operações no mesmo arquivo.

Mesmo que 1.parquet e 2.parquet não façam mais parte da nossa tabela Delta Lake, sua adição e remoção ainda são registradas no log de transações porque essas operações foram realizadas em nossa tabela - apesar do fato de que elas acabaram se cancelando. O Delta Lake ainda retém commits atômicos como esses para garantir que, caso precisemos auditar nossa tabela ou usar a "viagem no tempo" para ver como era nossa tabela em um determinado ponto no tempo, possamos fazê-lo com precisão.

Além disso, o Spark não remove os arquivos do disco imediatamente, mesmo que tenhamos removido os arquivos de dados subjacentes da nossa tabela. Os usuários podem excluir os arquivos que não são mais necessários usando VACUUM.

Recomputando rapidamente o estado com arquivos de checkpoint

Depois de fazer vários commits no log de transações, o Delta Lake salva um arquivo de checkpoint no formato Parquet no mesmo subdiretório _delta_log. O Delta Lake gera automaticamente o checkpoint conforme necessário para manter um bom desempenho de leitura.

Diagrama ilustrando a estrutura de arquivos do log de transações do Delta Lake, incluindo diretórios de partição.

Esses arquivos de checkpoint salvam todo o estado da tabela em um ponto no tempo - no formato Parquet nativo que é rápido e fácil para o Spark ler. Em outras palavras, eles oferecem ao leitor do Spark uma espécie de "atalho" para reproduzir totalmente o estado de uma tabela que permite que o Spark evite o reprocessamento do que poderia ser milhares de pequenos arquivos JSON ineficientes.

Para acelerar, o Spark pode executar uma operação listFrom para visualizar todos os arquivos no log de transações, pular rapidamente para o arquivo de checkpoint mais recente e processar apenas os commits JSON feitos desde que o arquivo de checkpoint mais recente foi salvo.

Para demonstrar como isso funciona, imagine que criamos commits até 000007.json, conforme mostrado no diagrama abaixo. O Spark está atualizado por meio deste commit, tendo armazenado automaticamente em cache a versão mais recente da tabela na memória. Enquanto isso, porém, vários outros gravadores (talvez seus colegas de equipe excessivamente ansiosos) gravaram novos dados na tabela, adicionando commits até 0000012.json.

Para incorporar essas novas transações e atualizar o estado da nossa tabela, o Spark executará uma operação listFrom version 7 para ver as novas alterações na tabela.

Diagrama ilustrando como o Spark lê arquivos de checkpoint recentes para calcular rapidamente o estado da tabela.

Em vez de processar todos os arquivos JSON intermediários, o Spark pode pular para o arquivo de checkpoint mais recente, pois ele contém todo o estado da tabela no commit nº 10. Agora, o Spark só precisa executar o processamento incremental de 0000011.json e 0000012.json para ter o estado atual da tabela. O Spark então armazena em cache a versão 12 da tabela na memória. Ao seguir este fluxo de trabalho, o Delta Lake consegue usar o Spark para manter o estado de uma tabela atualizado em todos os momentos de forma eficiente.

Lidando com várias leituras e gravações simultâneas

Agora que entendemos como o log de transações do Delta Lake funciona em um nível alto, vamos falar sobre simultaneidade. Até agora, nossos exemplos cobriram principalmente cenários em que os usuários confirmam transações linearmente, ou pelo menos sem conflito. Mas o que acontece quando o Delta Lake está lidando com várias leituras e gravações simultâneas?

A resposta é simples. Como o Delta Lake é alimentado pelo Apache Spark, não é apenas possível que vários usuários modifiquem uma tabela ao mesmo tempo - é esperado. Para lidar com essas situações, o Delta Lake emprega o controle de simultaneidade otimista.

O que é controle de simultaneidade otimista?

O controle de simultaneidade otimista é um método de lidar com transações simultâneas que pressupõe que as transações (alterações) feitas em uma tabela por diferentes usuários podem ser concluídas sem entrar em conflito umas com as outras. É incrivelmente rápido porque, ao lidar com petabytes de dados, há uma grande probabilidade de que os usuários estejam trabalhando em diferentes partes dos dados, permitindo que eles concluam transações não conflitantes simultaneamente.

Por exemplo, imagine que você e eu estamos trabalhando juntos em um quebra-cabeça. Contanto que ambos estejamos trabalhando em diferentes partes dele - você nos cantos e eu nas bordas, por exemplo - não há razão para que não possamos trabalhar em nossa parte do quebra-cabeça maior ao mesmo tempo e terminar o quebra-cabeça duas vezes mais rápido. É só quando precisamos das mesmas peças, ao mesmo tempo, que há um conflito. Isso é controle de simultaneidade otimista.

É claro que, mesmo com o controle de simultaneidade otimista, às vezes os usuários tentam modificar as mesmas partes dos dados ao mesmo tempo. Felizmente, o Delta Lake tem um protocolo para isso.

Resolvendo conflitos de forma otimista

Para oferecer transações ACID, o Delta Lake tem um protocolo para descobrir como os commits devem ser ordenados (conhecido como o conceito de serialização em bancos de dados) e determinar o que fazer no caso de dois ou mais commits serem feitos ao mesmo tempo. O Delta Lake lida com esses casos implementando uma regra de exclusão mútua, então tentando resolver qualquer conflito de forma otimista. Este protocolo permite que o Delta Lake cumpra o princípio ACID de isolamento, que garante que o estado resultante da tabela após várias gravações simultâneas seja o mesmo que se essas gravações tivessem ocorrido em série, isoladamente umas das outras.

Em geral, o processo prossegue assim:

  1. Registre a versão inicial da tabela.
  2. Registre leituras/gravações.
  3. Tente um commit.
  4. Se outra pessoa ganhar, verifique se algo que você leu foi alterado.
  5. Repita.

Para ver como tudo isso se desenrola em tempo real, vamos dar uma olhada no diagrama abaixo para ver como o Delta Lake gerencia conflitos quando eles surgem. Imagine que dois usuários leem da mesma tabela e, em seguida, cada um tenta adicionar alguns dados a ela.

Ilustrando o controle de simultaneidade otimista mostrando dois usuários com commits conflitantes.

  • O Delta Lake registra a versão inicial da tabela (versão 0) que é lida antes de fazer qualquer alteração.
  • Os usuários 1 e 2 tentam anexar alguns dados à tabela ao mesmo tempo. Aqui, encontramos um conflito porque apenas um commit pode vir em seguida e ser registrado como 000001.json.
  • O Delta Lake lida com esse conflito com o conceito de "exclusão mútua", o que significa que apenas um usuário pode fazer o commit 000001.json com sucesso. O commit do usuário 1 é aceito, enquanto o do usuário 2 é rejeitado.
  • Em vez de lançar um erro para o usuário 2, o Delta Lake prefere lidar com esse conflito otimisticamente. Ele verifica se algum novo commit foi feito na tabela e atualiza a tabela silenciosamente para refletir essas alterações, então simplesmente tenta novamente o commit do usuário 2 na tabela recém-atualizada (sem nenhum processamento de dados), confirmando com sucesso 000002.json.

Na grande maioria dos casos, essa reconciliação acontece de forma silenciosa, contínua e bem-sucedida. No entanto, no caso de haver um problema irreconciliável que o Delta Lake não consegue resolver de forma otimista (por exemplo, se o usuário 1 excluiu um arquivo que o usuário 2 também excluiu), a única opção é lançar um erro.

Como uma nota final, como todas as transações feitas em tabelas Delta Lake são armazenadas diretamente em disco, este processo satisfaz a propriedade ACID de durabilidade, o que significa que persistirá mesmo em caso de falha do sistema.

UM LÍDER 5X

Gartner®: Databricks, líder em banco de dados em nuvem

Outros casos de uso

Viagem no tempo

Cada tabela é o resultado da soma total de todos os commits registrados no log de transações do Delta Lake - nada mais e nada menos. O log de transações fornece um guia de instruções passo a passo, detalhando exatamente como ir do estado original da tabela ao seu estado atual.

Portanto, podemos recriar o estado de uma tabela em qualquer ponto no tempo, começando com uma tabela original e processando apenas os commits feitos antes desse ponto. Esta poderosa capacidade é conhecida como "viagem no tempo", ou versionamento de dados, e pode ser uma salvação em várias situações. Para obter mais informações, leia a postagem do blog Apresentando a viagem no tempo do Delta para data lakes em larga escala, ou consulte a documentação de viagem no tempo do Delta Lake.

Linha de dados e depuração

Como o registro definitivo de cada alteração já feita em uma tabela, o log de transações do Delta Lake oferece aos usuários uma linhagem de dados verificável que é útil para fins de governança, auditoria e conformidade. Ele também pode ser usado para rastrear a origem de uma alteração inadvertida ou um bug em um pipeline de volta à ação exata que o causou. Os usuários podem executar DESCRIBE HISTORY para ver os metadados sobre as alterações que foram feitas.

Resumo do log de transações do Delta Lake

Neste blog, mergulhamos nos detalhes de como funciona o log de transações do Delta Lake, incluindo:

  • O que é o log de transações, como ele é estruturado e como os commits são armazenados como arquivos em disco.
  • Como o log de transações serve como uma única fonte de verdade, permitindo que o Delta Lake implemente o princípio da atomicidade.
  • Como o Delta Lake calcula o estado de cada tabela - incluindo como ele usa o log de transações para alcançar o checkpoint mais recente.
  • Usando o controle de simultaneidade otimista para permitir várias leituras e gravações simultâneas, mesmo com as tabelas mudando.
  • Como o Delta Lake usa a exclusão mútua para garantir que os commits sejam serializados corretamente e como eles são repetidos silenciosamente em caso de conflito.
Interessado no Delta Lake de código aberto?
Visite o hub online do Delta Lake para saber mais, baixar o código mais recente e participar da comunidade Delta Lake.

Relacionado

Artigos desta série:
Mergulhando no Delta Lake #1: Descompactando o log de transações
Mergulhando no Delta Lake #2: Aplicação e evolução do esquema
Mergulhando no Delta Lake #3: Internos do DML (Update, Delete, Merge)

Artigos relacionados:
O que é um data lake?
Produzindo machine learning com Delta Lake

(Esta publicação no blog foi traduzida utilizando ferramentas baseadas em inteligência artificial) Publicação original

Nunca perca uma postagem da Databricks

Inscreva-se nas categorias de seu interesse e receba as últimas postagens na sua caixa de entrada