Engenharia de dados moderna: Lakehouse com Delta Lake e Python
A arquitetura de dados evoluiu: de Data Warehouse (SQL, estruturado) para Data Lake (qualquer formato, sem controle) e agora para Lakehouse — o melhor dos dois mundos. O Delta Lake é a tecnologia que torna isso possível.
O problema
Data Warehouse (ex: Redshift, BigQuery):
- Dados estruturados, otimizados para queries
- Caro para armazenar grandes volumes
- Não suporta dados não-estruturados
Data Lake (ex: S3, HDFS):
- Barato, suporta qualquer formato
- Sem ACID, sem versionamento
- Difícil de manter qualidade
Lakehouse resolve ambos:
- Armazenamento barato (data lake)
- Transações ACID, versionamento, schema enforcement (data warehouse)
O que é Delta Lake?
Delta Lake é um formato de armazenamento open-source que adiciona uma camada de transações sobre Parquet files em um data lake:
Data Lake (S3/ADLS)
└── delta/
├── _delta_log/
│ ├── 00000.json (transaction log)
│ ├── 00001.json
│ └── 00002.json
├── part-00000.parquet
├── part-00001.parquet
└── part-00002.parquet
O _delta_log é o que garante ACID, versionamento e time travel.
Configuração com PySpark
pip install pyspark delta-spark
from pyspark.sql import SparkSession
from delta import configure_spark_with_delta_pip
builder = (
SparkSession.builder
.appName("Lakehouse")
.config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension")
.config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog")
)
spark = configure_spark_with_delta_pip(builder).getOrCreate()
Escrevendo dados Delta
from pyspark.sql import Row
# Criar DataFrame
dados = [
Row(id=1, nome="Ana", cidade="SP", valor=150.0),
Row(id=2, nome="Bob", cidade="RJ", valor=200.0),
Row(id=3, nome="Carlos", cidade="SP", valor=300.0),
Row(id=4, nome="Diana", cidade="MG", valor=250.0),
]
df = spark.createDataFrame(dados)
# Escrever como Delta
df.write.format("delta").mode("overwrite").save("/data/vendas")
Time Travel
Cada operação cria uma versão. Você pode consultar dados de qualquer ponto no tempo:
# Versão atual
df_atual = spark.read.format("delta").load("/data/vendas")
# Versão específica
df_v0 = spark.read.format("delta").option("versionAsOf", 0).load("/data/vendas")
# Timestamp específico
df_ontem = (
spark.read.format("delta")
.option("timestampAsOf", "2024-01-15")
.load("/data/vendas")
)
# Ver histórico de versões
from delta.tables import DeltaTable
delta_table = DeltaTable.forPath(spark, "/data/vendas")
delta_table.history().show()
UPSERT (MERGE)
Operação essencial para data warehousing — atualiza existentes e insere novos:
from delta.tables import DeltaTable
# Dados novos
novos_dados = spark.createDataFrame([
Row(id=2, nome="Bob", cidade="RJ", valor=250.0), # Atualizar
Row(id=5, nome="Eve", cidade="BA", valor=180.0), # Inserir
])
# Delta table existente
delta_table = DeltaTable.forPath(spark, "/data/vendas")
# MERGE (upsert)
(
delta_table.alias("target")
.merge(novos_dados.alias("source"), "target.id = source.id")
.whenMatchedUpdate(set={
"valor": "source.valor",
"cidade": "source.cidade",
})
.whenNotMatchedInsertAll()
.execute()
)
Schema Evolution
Adicionar colunas sem reescrever dados:
# DataFrame com nova coluna
df_com_categoria = df.withColumn("categoria", lit("geral"))
# Escrever com merge schema
(
df_com_categoria.write
.format("delta")
.option("mergeSchema", "true")
.mode("append")
.save("/data/vendas")
)
Streaming com Delta
Delta Lake suporta streaming nativamente:
# Streaming de dados para Delta
stream = (
spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "localhost:9092")
.option("subscribe", "eventos")
.load()
)
# Processar
from pyspark.sql.functions import from_json, col
schema = "tipo STRING, usuario_id STRING, valor DOUBLE"
processado = (
stream
.selectExpr("CAST(value AS STRING)")
.select(from_json(col("value"), schema).alias("data"))
.select("data.*")
)
# Escrever em Delta (append)
query = (
processado.writeStream
.format("delta")
.outputMode("append")
.option("checkpointLocation", "/checkpoints/vendas")
.start("/data/vendas_streaming")
)
Qualidade de dados com Delta
# Constraints de qualidade
spark.sql("""
ALTER TABLE delta.`/data/vendas`
ADD CONSTRAINT valor_positivo CHECK (valor > 0)
""")
# Tentar inserir dado inválido
df_invalido = spark.createDataFrame([Row(id=6, nome="Teste", cidade="SP", valor=-50)])
df_invalido.write.format("delta").mode("append").save("/data/vendas")
# Erro! Constraint violada
Arquitetura Lakehouse completa
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Sources │ │ Bronze │ │ Silver │
│ (APIs, │────→│ (Raw) │────→│ (Cleaned) │
│ DBs, │ │ Delta │ │ Delta │
│ Files) │ └──────────────┘ └──────┬───────┘
└──────────────┘ │
▼
┌──────────────┐
│ Gold │
│ (Aggregated) │
│ Delta │
└──────┬───────┘
│
┌──────┴───────┐
│ Analytics │
│ (BI, ML) │
└──────────────┘
- Bronze: dados brutos, sem transformação
- Silver: dados limpos, tipados, deduplicados
- Gold: dados agregados, prontos para análise
Conclusão
Delta Lake é a tecnologia que torna o Lakehouse realidade. Com ACID, time travel, schema evolution e streaming, você tem a confiabilidade de um warehouse com a flexibilidade de um lake.
Comece simples: escreva seus dados em formato Delta em vez de Parquet. Adicione time travel e MERGE conforme necessário. Em pouco tempo, terá uma arquitetura de dados profissional.
Caso eu tenha falado alguma besteira, por favor, agradecerei correções e sugestões.