Como construir um warehouse analítico barato com DuckDB e S3

1. Por que DuckDB + S3 é uma alternativa viável a warehouses tradicionais

Warehouses analíticos tradicionais como Snowflake, Redshift e BigQuery oferecem poder computacional imenso, mas com custos que podem escalar rapidamente. Para times enxutos, projetos de médio porte ou análises ad hoc, o custo de licenciamento e gerenciamento de clusters pode ser proibitivo. DuckDB combinado com S3 surge como uma alternativa elegante: zero custo de licenciamento, arquitetura serverless e sem necessidade de gerenciar infraestrutura.

DuckDB é um banco de dados SQL embarcado, otimizado para consultas analíticas, que roda no mesmo processo da aplicação. Ele não exige servidores dedicados — você executa queries diretamente em arquivos Parquet armazenados no S3. O resultado é um warehouse analítico funcional com custo apenas de armazenamento S3 (centavos por GB/mês) e processamento (quando você executa as queries localmente ou em uma instância EC2 mínima).

Casos de uso ideais incluem: análises exploratórias, dashboards internos, pipelines de dados de times de dados pequenos (1-5 pessoas), e processamento de datasets de até centenas de gigabytes. Para cenários onde a concorrência de usuários é baixa e o volume de dados não exige clusters distribuídos, DuckDB + S3 entrega performance comparável a soluções caras.

2. Configuração do ambiente: instalando DuckDB e conectando ao S3

A instalação é trivial. No terminal, para a CLI:

# macOS (Homebrew)
brew install duckdb

# Linux (download direto)
curl -L https://github.com/duckdb/duckdb/releases/download/v1.1.3/duckdb_cli-linux-amd64.zip -o duckdb.zip
unzip duckdb.zip
./duckdb

Para Python (recomendado para pipelines):

pip install duckdb

Com o DuckDB instalado, ative a extensão httpfs para acesso ao S3:

INSTALL httpfs;
LOAD httpfs;

Configure as credenciais AWS. A forma mais segura é usar variáveis de ambiente:

SET s3_region='us-east-1';
SET s3_access_key_id='SUA_ACCESS_KEY';
SET s3_secret_access_key='SUA_SECRET_KEY';

Ou, se preferir usar perfil IAM configurado via AWS CLI, DuckDB respeita as credenciais do perfil padrão.

3. Estrutura de dados no S3: organização de buckets e formatos de arquivo

A organização do bucket impacta diretamente a performance das consultas. Adote particionamento por colunas de filtro frequentes, como data, região ou categoria:

s3://meu-warehouse/vendas/
├── ano=2024/
│   ├── mes=01/
│   │   ├── vendas_2024_01_01.parquet
│   │   └── vendas_2024_01_02.parquet
│   └── mes=02/
│       └── vendas_2024_02_01.parquet
└── ano=2025/
    └── mes=01/
        └── vendas_2025_01_01.parquet

O formato Parquet é obrigatório para performance. Ele armazena dados em colunas, com compressão por padrão (Snappy), e permite que DuckDB leia apenas as colunas necessárias. Evite CSV ou JSON para dados além de alguns MBs — o desempenho degrada drasticamente.

4. Ingestão e transformação de dados com DuckDB

Carregar dados do S3 é direto com funções como read_parquet():

CREATE TABLE vendas_brutas AS
SELECT * FROM read_parquet('s3://meu-warehouse/vendas/**/*.parquet');

Para CSVs brutos (fonte comum de dados externos):

CREATE TABLE clientes AS
SELECT * FROM read_csv_auto('s3://meu-warehouse/raw/clientes_*.csv');

Transformações com SQL puro são poderosas. Exemplo de pipeline de limpeza e agregação:

WITH vendas_limpas AS (
    SELECT
        id_venda,
        cliente_id,
        data,
        valor,
        CASE WHEN valor < 0 THEN 0 ELSE valor END AS valor_corrigido
    FROM vendas_brutas
    WHERE data >= '2024-01-01'
),
resumo_mensal AS (
    SELECT
        strftime(data, '%Y-%m') AS mes,
        COUNT(*) AS total_vendas,
        SUM(valor_corrigido) AS receita,
        AVG(valor_corrigido) AS ticket_medio
    FROM vendas_limpas
    GROUP BY mes
)
SELECT * FROM resumo_mensal ORDER BY mes;

Para exportar resultados transformados de volta para S3 como Parquet:

COPY (SELECT * FROM resumo_mensal) TO 's3://meu-warehouse/analytics/resumo_mensal.parquet' (FORMAT PARQUET);

5. Consultas analíticas eficientes: otimização e tuning

Use EXPLAIN ANALYZE para entender o plano de execução:

EXPLAIN ANALYZE SELECT COUNT(*) FROM vendas_brutas WHERE ano = 2024;

Dicas práticas de otimização:

  • Filtragem precoce: sempre filtre o máximo possível antes de joins ou agregações.
  • Projeção de colunas: evite SELECT * em tabelas largas — especifique apenas as colunas necessárias.
  • Ordenação: dados ordenados por colunas de filtro (ex: data) aceleram consultas que usam esses filtros.
  • Paralelismo: DuckDB usa múltiplos threads por padrão. Ajuste conforme seu hardware:
SET threads = 4;
SET memory_limit = '2GB';

O cache local também ajuda: DuckDB mantém dados recentes em memória, então consultas repetidas são mais rápidas.

6. Automatizando pipelines com scripts e orquestração leve

Um pipeline diário em Python com DuckDB:

import duckdb
import os
from datetime import datetime, timedelta

# Conecta ao DuckDB (banco efêmero ou persistente)
con = duckdb.connect()

# Configura S3
con.execute("INSTALL httpfs; LOAD httpfs;")
con.execute(f"SET s3_region='us-east-1'")
con.execute(f"SET s3_access_key_id='{os.environ['AWS_ACCESS_KEY_ID']}'")
con.execute(f"SET s3_secret_access_key='{os.environ['AWS_SECRET_ACCESS_KEY']}'")

# Data de processamento (D-1)
data_processamento = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
ano, mes, dia = data_processamento.split('-')

# Pipeline: lê CSV bruto, transforma, escreve Parquet
con.execute(f"""
    CREATE TABLE daily_sales AS
    SELECT
        id,
        cliente,
        data,
        valor,
        CURRENT_TIMESTAMP AS processed_at
    FROM read_csv_auto('s3://meu-warehouse/raw/vendas_{ano}{mes}{dia}.csv')
    WHERE valor IS NOT NULL;
""")

con.execute(f"""
    COPY daily_sales TO 's3://meu-warehouse/vendas/ano={ano}/mes={mes}/vendas_{data_processamento}.parquet'
    (FORMAT PARQUET, COMPRESSION SNAPPY);
""")

print(f"Pipeline concluído para {data_processamento}")
con.close()

Orquestre com cron (Linux/Mac):

0 6 * * * /usr/bin/python3 /home/user/pipeline_diario.py >> /var/log/duckdb_pipeline.log 2>&1

Ou com GitHub Actions para pipelines mais complexos — zero custo de infra.

7. Governança e segurança: permissões, versionamento e backups

Política IAM mínima para o bucket S3:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::meu-warehouse",
                "arn:aws:s3:::meu-warehouse/*"
            ]
        }
    ]
}

Ative o versionamento no bucket S3 para permitir rollback de dados corrompidos. Para backup de metadados e esquemas:

-- Exportar esquema de todas as tabelas
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'main'
ORDER BY table_name, ordinal_position;

Use SHOW TABLES e DESCRIBE nome_tabela para documentação rápida.

8. Limitações e quando migrar para um warehouse tradicional

DuckDB não é bala de prata. Suas limitações principais:

  • Memória RAM: DuckDB é in-memory. Se seu dataset não couber na RAM disponível, a performance cai drasticamente (swap para disco). Para datasets > 100GB, considere Snowflake ou Redshift.
  • Concorrência: DuckDB não é multi-usuário nativo. Cada processo DuckDB é single-session. Para múltiplos usuários simultâneos, você precisaria de uma camada de pooling (ex: usando duckdb em modo servidor experimental) ou migrar.
  • Escala: Consultas que exigem processamento distribuído (ex: shuffle de dados massivos entre nós) são inviáveis.

Cenários para migrar: times com mais de 10 analistas concorrentes, datasets acima de 1TB, ou necessidade de integração com ferramentas de BI que exigem conexão JDBC/ODBC nativa (DuckDB oferece, mas com limitações de concorrência).

Para a maioria dos casos de médio porte, porém, DuckDB + S3 é uma alternativa sólida, barata e surpreendentemente rápida. Você constrói um warehouse analítico funcional por centavos, sem abrir mão de SQL poderoso e performance.

Referências