Construindo um Modern Banking Data Lakehouse End-to-End: Da Ingestão com PySpark e Apache Iceberg à Orquestração com Airflow e dbt
Um guia prático e aprofundado sobre como desenhar, implementar e orquestrar uma plataforma de dados bancária resiliente em produção, aplicando Arquitetura Medalhão, streaming com PyArrow, quarentena com PySpark, tabelas ACID com Apache Iceberg e modelagem semântica com dbt.
No setor financeiro e bancário moderno, a infraestrutura de dados vai muito além de relatórios operacionais: ela é o alicerce crítico para detecção de fraudes em tempo real, modelos de concessão de crédito e score de risco, auditoria regulatória estrita (BACEN/LGPD) e hiperpersonalização de serviços financeiros.
Contudo, construir uma plataforma de dados bancária impõe desafios de engenharia que quebram abordagens tradicionais de data warehouse:
- Volume massivo e granularidade temporal: Milhões de transações diárias distribuídas em múltiplos canais assíncronos (PIX, TED, cartões de débito/crédito, depósitos e saques).
- Integridade referencial rígida: Transações e pagamentos jamais podem referenciar contas fantasmas, cartões inexistentes ou comércios não cadastrados.
- Garantia de Data Quality não bloqueante: Dados malformados ou inconsistentes não devem derrubar os pipelines de produção nem poluir a camada analítica final.
- Auditoria regulatória, ACID e Time Travel: A obrigação legal de auditar e reproduzir o estado histórico de saldos e transações em qualquer data passada com isolamento transacional completo.
Para endereçar e solucionar esses desafios sob condições reais de produção, desenhei e implementei o Banking Data Lakehouse (Banking Pipeline) — uma plataforma de dados moderna, conteinerizada e 100% reproduzível.
1. Visão Geral da Arquitetura End-to-End
O projeto foi concebido sob o consagrado padrão da Arquitetura Medalhão (Bronze -> Silver -> Gold), combinada aos recursos avançados de um Modern Lakehouse baseado em Apache Iceberg e MinIO (storage compatível com S3):
FONTES DE DADOS
Motor Sintético de Domínio Bancário
│
▼
INGESTÃO EM STREAMING
PyArrow Chunked Batch Writer
│
▼
🥉 BRONZE
Raw Parquet (year=YYYY/month=MM)
│
▼
APACHE SPARK ENGINE
Limpeza Distribuída & Validações
│
┌─────────┴─────────┐
│ │
▼ ▼
SILVER QUARENTENA
Dados Validados Registros Inválidos / DLQ
│
▼
🥇 GOLD
Marts Analíticos & Agregações 360°
│
┌───────────┴───────────┐
▼ ▼
APACHE ICEBERG DBT CORE
Lakehouse ACID (MinIO) DuckDB Camada Semântica
│ │
└───────────┬───────────┘
▼
APACHE AIRFLOW
Orquestração CeleryExecutor & Circuit BreakersStack Tecnológica e Papel de Cada Ferramenta
- Gerador de Domínio Sintético: Python, Faker, NumPy, Pandas (geração relacional determinística respeitando distribuições assimétricas reais).
- Ingestão em Streaming: PyArrow (processamento em lotes chunked de baixo uso de memória com gravação direta em Parquet comprimido com Snappy).
- Processamento Distribuído: Apache Spark 3.5 / PySpark (joins distribuídos, quarentena com anti-joins e deduplicação idempotente por janelas).
- Armazenamento & Formato de Tabela: Apache Iceberg + MinIO (tabelas ACID com isolamento de snapshots, particionamento oculto e evolução de schema segura).
- Modelagem Semântica & Contratos: dbt Core + DuckDB (camadas modulares Staging/Intermediate/Marts com testes automatizados de integridade).
- Orquestração de Workflows: Apache Airflow 2.10 (CeleryExecutor, políticas de retry automático, execução em containers e circuit breakers de qualidade).
- Infraestrutura: Docker & Docker Compose orquestrando 7 microsserviços isolados.
2. Modelagem Relacional do Domínio & Síntese de Dados
Para espelhar a dinâmica de um banco digital moderno, modelamos um ecossistema com 7 entidades inter-relacionadas com cardinalidade estrita:
┌──────────────┐
│ CUSTOMER │ (100.000 clientes)
└──────┬───────┘
│
┌─────────┴─────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ ACCOUNT │ │ LOAN │ (30.000 contratos)
└──────┬──────┘ └──────┬──────┘
│ │
┌─────────┴─────────┐ ▼
▼ ▼ ┌──────────────┐
┌─────────────┐ ┌──────────┴──┐│ LOAN_PAYMENT │ (~740k parcelas)
│ CARD │ │ TRANSACTION │└──────────────┘
└─────────────┘ └──────┬──────┘ (5.000.000 eventos)
(120k cartões) │
▼
┌──────────────┐
│ MERCHANT │ (10.000 estabelecimentos)
└──────────────┘Volumetria e Distribuições Realistas
- Clientes (100k): Segmentados em perfis socioeconômicos (
BASIC,STANDARD,PREMIUM) que ditam limites de crédito e volume transacional. - Contas (~150k): Contas correntes (
CHECKING) e poupança (SAVINGS) com controle de status ativo/bloqueado. - Transações (5.000.000 de registros): Eventos multi-canal (PIX, TED, DOC, compras no débito/crédito, depósitos e saques) distribuídos ao longo de múltiplos períodos fiscais.
- Realismo da Síntese: Em vez de dados puramente randômicos, as transações seguem uma distribuição log-normal / Pareto, simulando o comportamento real do consumidor: alta frequência de transações de baixo valor (alimentação, transporte) contrapondo baixa frequência de transações de alto valor (eletrônicos, aluguel, investimentos).
3. Camada Bronze: Ingestão em Streaming Sem Estouro de Memória
Um erro frequente em engenharia de dados é carregar datasets de milhões de linhas diretamente na memória com pd.read_csv() ou pd.to_parquet(), resultando em crashes fatais por Out-Of-Memory (OOM) em máquinas com recursos limitados.
Para garantir alta vazão com consumo estritamente controlado de RAM (menos de 300MB), a ingestão foi projetada utilizando PyArrow Chunked Streaming RecordBatches:
# Padrão de ingestão em streaming com PyArrow
import pyarrow as pa
import pyarrow.parquet as pq
def stream_records_to_bronze(generator, batch_size=100_000, output_path='data/bronze/transactions'):
schema = get_transaction_pyarrow_schema()
writer = None
for batch in generator.iter_batches(batch_size):
arrow_table = pa.Table.from_batches([batch], schema=schema)
if writer is None:
writer = pq.ParquetWriter(f'{output_path}/data.parquet', schema, compression='SNAPPY')
writer.write_table(arrow_table)
if writer:
writer.close()Otimizações Estratégicas de Armazenamento:
- Armazenamento Colunar Parquet: Compressão Snappy resulta em uma redução de ~4.5x no tamanho do arquivo em disco em relação a CSV/JSON brutos.
- Particionamento Temporal (
year=YYYY/month=MM): Consultas analíticas que filtram períodos específicos utilizam o mecanismo de partition pruning para pular mais de 90% dos arquivos irrelevantes durante o scan do Spark.
4. Camada Silver: Limpeza Distribuída e Arquitetura de Quarentena
A camada Silver é onde o Spark transforma dados brutos e não confiáveis em datasets estruturados, validados e normalizados.
O Conceito de Quarentena (Dead Letter Queue de Dados)
Em pipelines tradicionais corporativos, registros com chaves estrangeiras inválidas ou campos inconsistentes costumam gerar dois comportamentos prejudiciais:
- Descartar silenciosamente (Drop): Elimina rastreabilidade, mascara falhas de sistemas de origem e quebra auditorias regulatórias.
- Falhar todo o pipeline (Fail): Uma única transação com payload corrompido paralisa as operações analíticas de toda a empresa.
No Banking Pipeline, criamos o padrão de Dual-Sink Quarantine Routing no Spark:
# Roteamento dual-sink de quarentena com PySpark
from pyspark.sql import functions as F
# 1. Validação de Integridade Referencial via Left Anti Join
df_valid_tx = df_raw_tx.join(df_accounts, 'account_id', 'inner')
df_invalid_tx = df_raw_tx.join(df_accounts, 'account_id', 'left_anti') \
.withColumn('rejection_reason', F.lit('FOREIGN_KEY_NOT_FOUND_ACCOUNT'))
# 2. Validação de Regras de Negócio do Domínio
df_business_valid = df_valid_tx.filter(
(F.col('amount') > 0) &
(F.col('currency') == 'BRL') &
(F.col('status').isin('COMPLETED', 'PENDING', 'REVERSED'))
)
# 3. Gravação simultânea dos válidos na Silver e inconsistentes na Quarentena
df_business_valid.write.mode('overwrite').parquet('data/silver/transactions')
df_invalid_tx.write.mode('append').parquet('data/quarantine/transactions')Principais Invariantes Validadas:
- Integridade Relacional: Existência de conta validada para cada transação; existência de estabelecimento comercial validada para compras em cartão.
- Regras do Domínio Bancário: Contas poupança (
SAVINGS) não podem emitir cartões de crédito; cartões de crédito devem possuir limite positivo (credit_limit > 0); valores de transação devem ser estritamente positivos (amount > 0). - Deduplicação Idempotente: Deduplicação baseada em funções de janela
ROW_NUMBER() OVER (PARTITION BY transaction_id ORDER BY ingested_at DESC)garantindo execuções idempotentes.
5. Camada Gold: Modelagem Dimensional e Visão 360°
Com dados saneados na camada Silver, o Spark calcula as visões analíticas agregadas e tabelas de fatos/dimensões prontas para consumo:
customer_360: Perfil consolidado do cliente unindo saldo total de contas, quantidade de cartões ativos, volume financeiro movimentado na vida, exposição em empréstimos e score RFM (Recência, Frequência, Valor Monetário).daily_transaction_summary: Agregações diárias por canal de pagamento (PIX vs. TED vs. Cartão), volumetria financeira total, ticket médio e taxa de autorização de transações.customer_transaction_metrics: Métricas de comportamento de gastos (horários de pico de consumo, maior transação individual e categorias de comércios preferidas).merchant_performance: Análise de parceiros comerciais com faturamento bruto, taxa de estorno (chargeback) e volumetria de vendas.loan_portfolio: Saúde da carteira de crédito com índices de inadimplência (Default Rate a 30/60/90+ dias), total de capital concedido e índice de recuperação.
6. Modern Lakehouse com Apache Iceberg e MinIO Object Storage
Arquivos Parquet tradicionais gravados em pastas possuem limitações severas: não oferecem garantias transacionais ACID, sofrem com concorrência de leitura/escrita e exigem listagens lentas de diretórios para descobrir arquivos de partição.
Evoluímos a plataforma para um verdadeiro Lakehouse de Tabelas Abertas utilizando Apache Iceberg com MinIO (S3):
# Criação de tabela ACID no Apache Iceberg com particionamento oculto
df_gold_summary.writeTo('iceberg.banking.daily_transaction_summary') \
.tableProperty('write.format.default', 'parquet') \
.tableProperty('write.parquet.compression-codec', 'snappy') \
.partitionedBy(F.days('transaction_date')) \
.createOrReplace()┌────────────────────────────────────────────────────────┐
│ ESTRUTURA DE METADADOS ICEBERG │
├────────────────────────────────────────────────────────┤
│ 1. Catalog Pointer (Aponta para metadata JSON atual) │
│ └── v3.metadata.json │
│ ├── Schema & Especificação de Partição │
│ └── Manifest List (Snapshot #104928) │
│ ├── snap-manifest-1.avro │
│ │ ├── data_file_a.parquet (min/max stats) │
│ │ └── data_file_b.parquet (min/max stats) │
│ └── snap-manifest-2.avro │
│ └── data_file_c.parquet │
└────────────────────────────────────────────────────────┘Por que o Apache Iceberg é Superior aos Formatos Legados:
- Transações ACID via Controle de Concorrência Otimista (OCC): Commits atômicos eliminam leituras parciais ou inconsistentes enquanto novos lotes estão sendo gravados.
- Isolamento de Snapshots & Time Travel: Capacidade de auditar e reproduzir relatórios em qualquer ponto histórico (
SELECT * FROM table FOR SYSTEM_TIME AS OF '2026-06-01'). - Particionamento Oculto (Hidden Partitioning): A otimização de queries via poda de partições ocorre de forma transparente sem que analistas precisem criar colunas sintéticas nas consultas SQL.
- Estatísticas Min/Max nos Manifestos: Os manifestos do Iceberg guardam os limites de cada coluna, permitindo pular arquivos inteiros no plano de execução sem ler o cabeçalho físico do Parquet.
7. Camada Semântica & Contratos de Dados com dbt + DuckDB
Para separar a manipulação pesada de dados brutos das regras analíticas de negócio, integramos o dbt (data build tool) com o DuckDB como motor analítico SQL in-process ultrarrápido:
Estrutura do Projeto dbt:
├── models/
│ ├── staging/ # Conversão de tipos, renomeação canônica e limpeza
│ │ ├── stg_accounts.sql
│ │ └── stg_transactions.sql
│ ├── intermediate/ # Joins de regras de negócio e enriquecimento
│ │ └── int_customer_transaction_summary.sql
│ └── marts/ # Tabelas Fato e Dimensões para BI
│ ├── fct_daily_transactions.sql
│ └── dim_customers_360.sqlContratos de Dados & Testes Automatizados
Todos os modelos do dbt possuem testes declarados em arquivos schema.yml:
- Unicidade e Não-Nulos: Garantia de integridade em Primary Keys de contas e transações.
- Relacionamentos e Chaves Estrangeiras: Validação de que
fct_daily_transactions.account_idexiste compulsoriamente emdim_accounts. - Accepted Values: Validação de enums de status transacional (
['COMPLETED', 'PENDING', 'REVERSED']).
8. Orquestração em Produção com Apache Airflow & Circuit Breakers
A automação do fluxo completo com observabilidade e tolerância a falhas é gerenciada por uma DAG no Apache Airflow:
[generate_data] ──> [build_bronze] ──> [build_silver] ──> [silver_qa]
│
▼
[build_iceberg] <── [gold_qa] <── [build_gold] <──────────────┘Destaques da Orquestração:
- Disparo Isolado em Containers: O Celery Worker do Airflow dispara as tarefas dentro do container dedicado do Spark via Docker Socket integration (
spark_exec). - Quality Gates como Circuit Breakers (
silver_qa,gold_qa): Tarefas de validação analisam volumetrias, desvios de schema e taxas de nulos. Se anomalias forem detectadas, o pipeline é interrompido imediatamente antes de persistir dados no Iceberg. - Políticas de Retry Resilientes: Configurações de exponential backoff garantem recuperação automática contra instabilidades transitórias de rede e I/O.
9. Desafios de Engenharia & Lições Práticas
- Ajuste de Partições de Shuffle no Spark (
spark.sql.shuffle.partitions): Em testes locais com 4GB de heap, as 200 partições padrão do Spark geravam overhead massivo de escalonamento em tabelas menores de dimensão. Reduzir dinamicamente para 8–16 partições reduziu o tempo total da pipeline em 64%. - Configuração S3A do MinIO: A integração do Spark 3.5 com o MinIO exigiu a configuração explícita de
fs.s3a.path.style.access = true, desativação defs.s3a.fast.upload.buffer=bytebuffere o uso de catálogos em memória para eliminar a dependência pesada de um metastore Hive dedicado. - Engenharia Defensiva com Quarentena: O isolamento de registros defeituosos extinguiu quedas acidentais no pipeline e forneceu um repositório auditável de DLQ para análise forense de inconsistências.
10. Conclusão & Próximos Passos
O Banking Data Lakehouse demonstra na prática que a engenharia de dados moderna reside em desenhar arquiteturas de software resilientes, auditáveis e automatizadas.
Próximas Evoluções da Plataforma:
- [ ] Ingestão de eventos de transações em tempo real com Apache Kafka e Spark Structured Streaming.
- [ ] Criação de dashboards de negócio conectados via Metabase / Superset.
- [ ] Feature Store para modelos de detecção de fraude em tempo real com XGBoost / Feast.
Confira o código-fonte completo, configurações Docker Compose, DAGs do Airflow e modelos dbt no GitHub!