Introdução ao Delta Lake: tabelas ACID em data lakes

1. O Problema dos Data Lakes Tradicionais

1.1. Inconsistência de dados e falta de atomicidade em operações de escrita

Data lakes tradicionais baseados em arquivos Parquet ou CSV enfrentam um problema crítico: operações de escrita não são atômicas. Quando um processo falha no meio de uma gravação, o sistema pode ficar com dados parcialmente escritos, corrompendo a integridade do dataset. Por exemplo, ao escrever 100 arquivos Parquet, se o processo morre após o 50º arquivo, não há garantia de que os dados estejam consistentes.

# Exemplo de problema: escrita parcial em data lake tradicional
# Cenário: job Spark falha após escrever 50 de 100 arquivos Parquet
# Resultado: dataset inconsistente, sem atomicidade

1.2. Dificuldade em lidar com atualizações, deleções e fusões de dados

Em data lakes tradicionais, atualizar ou deletar registros específicos é extremamente complexo. Como os arquivos são imutáveis, qualquer modificação exige reescrever todo o dataset. Operações como UPSERT (atualizar se existe, inserir se não) são praticamente inviáveis sem mecanismos adicionais.

# Exemplo: atualizar um registro em data lake tradicional
# Abordagem ingênua: ler tudo, modificar, reescrever tudo
# Problema: alto custo computacional e risco de inconsistência

1.3. Ausência de isolamento entre leituras e escritas concorrentes

Múltiplos processos lendo e escrevendo simultaneamente no mesmo dataset geram problemas de concorrência. Leituras podem ver dados parciais de escritas em andamento, e escritas concorrentes podem sobrescrever umas às outras sem controle.

# Problema de concorrência em data lakes tradicionais
# Processo A lê dados enquanto Processo B escreve
# Leitura pode retornar dados inconsistentes

2. O que é o Delta Lake e sua Arquitetura

2.1. Definição como camada de armazenamento open-source sobre Parquet/S3

Delta Lake é uma camada de armazenamento open-source que adiciona transações ACID a data lakes baseados em Parquet. Ele funciona sobre sistemas de armazenamento como Amazon S3, Azure Data Lake Storage e HDFS, mantendo a compatibilidade com o ecossistema Apache Spark.

2.2. O log de transações (Delta Transaction Log) como mecanismo central

O coração do Delta Lake é o Delta Transaction Log, um registro imutável de todas as operações realizadas na tabela. Cada operação de escrita gera uma nova entrada no log, permitindo rastrear o histórico completo de modificações.

# Estrutura do Delta Transaction Log
# Cada commit é um arquivo JSON no diretório _delta_log
_delta_log/
├── 00000000000000000000.json  # Commit inicial
├── 00000000000000000001.json  # Segundo commit
├── 00000000000000000002.json  # Terceiro commit
└── ...

2.3. Estrutura de diretórios e arquivos: _delta_log, checkpoints e versões

Uma tabela Delta é composta por:
- Arquivos Parquet com os dados
- Diretório _delta_log com o log de transações
- Checkpoints periódicos (arquivos Parquet que resumem o estado da tabela)

# Estrutura de uma tabela Delta
minha_tabela/
├── _delta_log/
│   ├── 00000000000000000000.json
│   ├── 00000000000000000001.json
│   ├── 00000000000000000010.checkpoint.parquet
│   └── ...
├── part-00000-xxx.snappy.parquet
├── part-00001-xxx.snappy.parquet
└── ...

3. Garantias ACID em Data Lakes

3.1. Atomicidade: commits como operações atômicas no log de transações

Cada operação de escrita no Delta Lake é atômica. Ou todas as alterações são aplicadas, ou nenhuma. Isso é garantido pelo log de transações: o commit só é registrado após todas as escritas de dados serem concluídas.

# Exemplo de escrita atômica no Delta Lake
# Se o processo falha, nenhum commit é registrado
# O dataset permanece no estado anterior consistente

3.2. Consistência: validação de esquema e restrições (constraints)

Delta Lake valida automaticamente o esquema dos dados sendo escritos contra o esquema existente. Além disso, suporta constraints como NOT NULL e CHECK para garantir consistência.

# Exemplo de constraint no Delta Lake
# ALTER TABLE vendas ADD CONSTRAINT valor_positivo CHECK (valor > 0)
# Qualquer tentativa de inserir valor <= 0 será rejeitada

3.3. Isolamento e Durabilidade: controle de concorrência otimista e persistência no S3

Delta Lake usa controle de concorrência otimista (OCC). Leituras veem uma snapshot consistente da tabela. Escritas concorrentes são resolvidas através de conflitos detectados no log de transações.

# Controle de concorrência no Delta Lake
# Leitura sempre vê estado consistente (snapshot isolation)
# Escritas concorrentes: uma vence, outras tentam novamente

4. Operações Fundamentais com Delta Lake

4.1. Leitura e escrita: read, write, append e overwrite

# Escrita inicial (overwrite)
df.write.format("delta").mode("overwrite").save("/caminho/tabela")

# Append de novos dados
df.write.format("delta").mode("append").save("/caminho/tabela")

# Leitura
df = spark.read.format("delta").load("/caminho/tabela")

4.2. Atualizações e deleções condicionais: UPDATE e DELETE

# Atualização condicional
UPDATE tabela SET status = 'inativo' WHERE data_fim < '2024-01-01'

# Deleção condicional
DELETE FROM tabela WHERE quantidade = 0

4.3. Merge (UPSERT): combinação de inserção e atualização com MERGE INTO

# Merge (UPSERT) - operação mais poderosa
MERGE INTO alvos AS a
USING fontes AS f ON a.id = f.id
WHEN MATCHED THEN UPDATE SET a.valor = f.valor
WHEN NOT MATCHED THEN INSERT (id, valor) VALUES (f.id, f.valor)

5. Gerenciamento de Versões e Time Travel

5.1. Como o log de transações permite versionamento de dados

Cada commit no log de transações representa uma versão da tabela. Isso permite acessar qualquer estado histórico do dataset.

5.2. Consultas históricas: VERSION AS OF e TIMESTAMP AS OF

# Time travel por versão
df = spark.read.format("delta")
    .option("versionAsOf", 5)
    .load("/caminho/tabela")

# Time travel por timestamp
df = spark.read.format("delta")
    .option("timestampAsOf", "2024-01-15")
    .load("/caminho/tabela")

5.3. Rollback e recuperação de versões anteriores

# Rollback para versão anterior
# Basta restaurar o log de transações para o estado desejado
# O comando RESTORE simplifica isso
RESTORE TABLE tabela TO VERSION AS OF 3

6. Otimização e Manutenção de Tabelas Delta

6.1. Compactação de arquivos pequenos com OPTIMIZE

Arquivos pequenos degradam performance. O comando OPTIMIZE compacta arquivos pequenos em arquivos maiores.

# Otimizar tabela
OPTIMIZE tabela

# Otimizar com Z-ordering por coluna específica
OPTIMIZE tabela ZORDER BY (data)

6.2. Limpeza de versões antigas com VACUUM

O VACUUM remove arquivos de dados de versões antigas da tabela, liberando espaço em storage.

# Vacuum: remove arquivos com mais de 7 dias
VACUUM tabela RETAIN 168 HOURS

6.3. Estatísticas e Z-ordering para aceleração de consultas

Delta Lake coleta estatísticas automaticamente (min, max, null count) para otimizar filtros. Z-ordering reorganiza dados para melhorar a eficiência de consultas com múltiplos filtros.

7. Integração com Ecossistema de Dados

7.1. Uso com Apache Spark (PySpark/Scala) e SQL

Delta Lake é nativamente integrado ao Apache Spark, oferecendo APIs completas em PySpark, Scala e SQL.

# PySpark: criar tabela Delta
spark.sql("CREATE TABLE vendas (id INT, produto STRING, valor DOUBLE) USING delta")

# Inserir dados
spark.sql("INSERT INTO vendas VALUES (1, 'Produto A', 100.0)")

7.2. Conexão com DuckDB e engines analíticos leves

Delta Lake pode ser lido por DuckDB e outras engines analíticas leves através de conectores específicos.

# DuckDB lendo tabela Delta
SELECT * FROM delta_scan('/caminho/tabela')

7.3. Delta Lake como alternativa para warehouses baratos com S3

Delta Lake + S3 oferece uma alternativa econômica a data warehouses tradicionais, com performance competitiva para muitos casos de uso.

8. Conclusão

Delta Lake resolve os problemas fundamentais dos data lakes tradicionais: inconsistência, falta de atomicidade e ausência de isolamento. Com garantias ACID, versionamento (time travel) e operações avançadas como MERGE, ele se tornou o padrão de facto para data lakes modernos. Sua integração com Spark, DuckDB e storage em nuvem como S3 o torna uma escolha versátil e econômica para pipelines de dados.

Referências