Como construir um data lake simples com MinIO e dbt

1. Introdução ao conceito de Data Lake com ferramentas modernas

Um data lake é um repositório centralizado que armazena dados em seu formato bruto, permitindo análises flexíveis sem a rigidez de esquemas predefinidos. Neste artigo, construiremos um data lake simples utilizando MinIO como armazenamento de objetos compatível com S3 e dbt como ferramenta de transformação de dados.

A combinação MinIO + dbt oferece uma stack leve, open source e extremamente funcional para projetos de dados de pequeno e médio porte. MinIO simula o serviço S3 da AWS localmente, enquanto o dbt permite transformar dados usando SQL puro, com versionamento e documentação automáticos.

Pré-requisitos:
- Docker e Docker Compose instalados
- Python 3.8+ com pip
- Conhecimentos básicos de SQL e linha de comando

2. Configurando o ambiente com MinIO

Vamos iniciar configurando o MinIO via Docker Compose. Crie um arquivo docker-compose.yml:

version: '3.8'
services:
  minio:
    image: minio/minio:latest
    container_name: minio-datalake
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: admin123
    command: server /data --console-address ":9001"
    volumes:
      - ./minio-data:/data

Execute o comando para iniciar o serviço:

docker-compose up -d

Acesse o console do MinIO em http://localhost:9001 com usuário admin e senha admin123. Crie um bucket chamado datalake e gere as Access Keys (vamos usar minioadmin e minioadmin para simplificar).

Para testar, faça upload manual de um arquivo CSV de exemplo:

# Exemplo de dados de vendas (vendas.csv)
id,produto,quantidade,preco,data
1,Notebook,2,3500.00,2024-01-15
2,Mouse,5,150.00,2024-01-16
3,Teclado,3,250.00,2024-01-17

3. Estruturando o data lake em camadas

Um data lake bem organizado segue o padrão de zonas. Vamos definir três camadas principais:

  • raw/: Dados brutos como foram ingeridos, sem transformações
  • staging/: Dados limpos e tipados, prontos para análise
  • analytics/: Agregações e visões de negócio

Estrutura de pastas no MinIO:

datalake/
├── raw/
│   └── vendas/
│       └── ano=2024/
│           └── mes=01/
│               └── vendas.csv
├── staging/
│   └── vendas_limpas/
│       └── ano=2024/
│           └── vendas_limpas.parquet
└── analytics/
    └── vendas_agregadas/
        └── ano=2024/
            └── vendas_agregadas.parquet

Boas práticas:
- Use partições por data para facilitar consultas
- Nomeie arquivos com prefixos que indiquem data/hora
- Versionie esquemas com metadados (arquivo _SUCCESS para indicar completude)

4. Conectando o dbt ao MinIO

Instale o dbt e o adaptador DuckDB (que suporta leitura/escrita em S3):

pip install dbt-core dbt-duckdb

Configure o perfil de conexão em ~/.dbt/profiles.yml:

minio_datalake:
  target: dev
  outputs:
    dev:
      type: duckdb
      path: md:my_database
      external_root: s3://datalake/
      s3_region: us-east-1
      s3_endpoint: localhost:9000
      s3_access_key_id: minioadmin
      s3_secret_access_key: minioadmin
      s3_use_ssl: false
      s3_url_style: path

Crie um projeto dbt:

dbt init datalake_project
cd datalake_project

Teste a conexão:

dbt debug

5. Criando o primeiro pipeline de transformação com dbt

Vamos criar modelos que leem dados do MinIO e escrevem resultados de volta. Primeiro, um modelo para ler o CSV bruto (models/raw/vendas_raw.sql):

{{ config(
    materialized='external',
    location='raw/vendas/ano=2024/mes=01/vendas.parquet',
    format='parquet'
) }}

SELECT * FROM read_csv_auto('s3://datalake/raw/vendas/ano=2024/mes=01/vendas.csv')

Agora, um modelo de staging que limpa e tipa os dados (models/staging/vendas_staging.sql):

{{ config(
    materialized='table',
    schema='staging'
) }}

SELECT
    id::INTEGER AS id_venda,
    produto,
    quantidade::INTEGER AS quantidade,
    preco::DECIMAL(10,2) AS preco_unitario,
    data::DATE AS data_venda,
    quantidade * preco AS valor_total
FROM {{ ref('vendas_raw') }}
WHERE quantidade > 0

Por fim, uma agregação analítica (models/analytics/vendas_agregadas.sql):

{{ config(
    materialized='external',
    location='analytics/vendas/ano=2024/vendas_agregadas.parquet',
    format='parquet'
) }}

SELECT
    produto,
    COUNT(*) AS total_vendas,
    SUM(quantidade) AS total_quantidade,
    SUM(valor_total) AS receita_total,
    AVG(preco_unitario) AS preco_medio
FROM {{ ref('vendas_staging') }}
GROUP BY produto

Execute o pipeline:

dbt run

6. Orquestração e automação do pipeline

Crie um script Python para execução agendada (run_pipeline.py):

import os
import subprocess
from datetime import datetime

# Configurações via variáveis de ambiente
os.environ['MINIO_ENDPOINT'] = 'localhost:9000'
os.environ['MINIO_ACCESS_KEY'] = 'minioadmin'
os.environ['MINIO_SECRET_KEY'] = 'minioadmin'

def run_dbt():
    print(f"Iniciando pipeline em {datetime.now()}")

    # Executa dbt run
    result = subprocess.run(['dbt', 'run'], capture_output=True, text=True)

    if result.returncode == 0:
        print("Pipeline executado com sucesso!")
        print(result.stdout)
    else:
        print("Erro na execução:")
        print(result.stderr)

    # Gera documentação
    subprocess.run(['dbt', 'docs', 'generate'])

if __name__ == "__main__":
    run_dbt()

Para automação, adicione ao cron (Linux) ou Agendador de Tarefas (Windows):

# Executar diariamente às 2h da manhã
0 2 * * * cd /caminho/projeto && python run_pipeline.py

7. Consultas e análises ad hoc sobre o data lake

Com DuckDB, podemos consultar diretamente os arquivos Parquet no MinIO:

# Conectar ao DuckDB
duckdb

# Consultar dados analíticos
SELECT * FROM read_parquet('s3://datalake/analytics/vendas/ano=2024/vendas_agregadas.parquet');

# Análise agregada
SELECT 
    produto,
    receita_total,
    total_quantidade,
    receita_total / total_quantidade AS ticket_medio
FROM read_parquet('s3://datalake/analytics/vendas/ano=2024/vendas_agregadas.parquet')
ORDER BY receita_total DESC;

Comparação de performance entre formatos:

-- CSV (mais lento, sem compressão)
.timer on
SELECT COUNT(*) FROM read_csv_auto('s3://datalake/raw/vendas/ano=2024/mes=01/vendas.csv');

-- Parquet (mais rápido, compressão automática)
SELECT COUNT(*) FROM read_parquet('s3://datalake/raw/vendas/ano=2024/mes=01/vendas.parquet');

8. Considerações finais e próximos passos

Esta abordagem simples com MinIO e dbt é ideal para:
- Prototipagem rápida de pipelines de dados
- Projetos pessoais ou pequenas equipes
- Ambientes de desenvolvimento e teste

Limitações:
- Escala limitada (MinIO single-node para grandes volumes)
- Sem concorrência avançada (dbt executa transformações sequenciais)
- Governança básica (sem catálogo de dados integrado)

Evoluções possíveis:
- Adicionar Apache Iceberg para versionamento de tabelas
- Implementar catálogo de dados com Apache Atlas ou Amundsen
- Substituir DuckDB por Spark para processamento distribuído
- Adicionar camada de orquestração com Airflow ou Prefect

Referências