tva
← Artigos

DuckDB para análises ad hoc: transformando milhares de CSVs em um painel

O Amazon Seller Central gera dados em um ritmo que supera as ferramentas da maioria dos analistas. Relatórios de tráfego, dados de conversão, resumos da situação do estoque, exportações de desempenho de negócios, detalhamentos de campanhas publicitárias — cada um entregue como um arquivo CSV ou TSV, acumulando ao longo de meses e anos em diretórios contendo milhares de arquivos. A resposta padrão é importar tudo em um banco de dados. Mas, na prática, configurar, manter e consultar uma instância PostgreSQL para o que é fundamentalmente uma carga de trabalho de análise de dados com leitura intensiva e esquema variável introduz mais sobrecarga operacional do que o problema em si justifica.

Construímos um pipeline de relatórios para um vendedor Amazon operando em cinco mercados europeus. Os dados brutos: seis anos de exportações do Seller Central, 4.200 arquivos, cobrindo relatórios de negócios, desempenho de publicidade, snapshots de estoque e registros contábeis de tarifas FBA. A abordagem inicial — um esquema PostgreSQL com scripts ETL — levou três semanas para estabilizar e exigia atenção constante à medida que a Amazon revisava nomes de colunas e estruturas de relatórios entre versões de API. As consultas eram rápidas. A manutenção, não.

O DuckDB muda esse cálculo. É um banco de dados analítico embutido — ele é executado no mesmo processo que seu código de análise, não requer servidor, nenhum daemon, nenhuma string de conexão e lê diretamente de arquivos no disco. A interface de consulta é SQL padrão. As características de desempenho são as de um mecanismo colunar otimizado para agregação em escala. Para uma carga de trabalho como esta, o DuckDB não é apenas adequado — é a ferramenta correta.

O que o DuckDB realmente é

A distinção que mais importa é armazenamento colunar versus armazenamento por linha. O PostgreSQL organiza dados em linhas: cada campo para um determinado registro é armazenado de forma contígua no disco. Quando você executa SELECT revenue, units_sold FROM orders WHERE marketplace = 'DE', uma varredura sequencial lê cada campo de cada linha que percorre, descarta os que você não solicitou e retorna os dois que solicitou. Os índices, incluindo varreduras que acessam apenas o índice, podem reduzir essas leituras, mas raramente ajudam em agregações amplas que abrangem a maior parte de uma tabela. Isso é eficiente para cargas de trabalho transacionais — inserir, atualizar e buscar registros individuais. É amplamente inadequado para consultas analíticas que agregam em milhões de linhas enquanto acessam apenas algumas colunas.

O DuckDB organiza dados por coluna. Cada valor para revenue é armazenado junto; cada valor para units_sold é armazenado junto. A mesma consulta lê apenas as três colunas de que precisa (as duas retornadas e marketplace para o filtro), ignorando o restante completamente. Em um conjunto de dados com quarenta colunas, esta não é uma otimização menor — pode reduzir o I/O em aproximadamente 90 por cento ou mais para consultas que acessam apenas algumas colunas. Combinado com execução vetorizada — processando dados em lotes de 2.048 valores por padrão usando instruções SIMD de CPU em vez de uma linha por vez — o DuckDB alcança desempenho de consulta em cargas de trabalho analíticas que pode superar o que uma instância PostgreSQL bem ajustada entrega no mesmo hardware, especialmente em formatos colunares como Parquet.

A arquitetura embarcada é igualmente importante. Não há comunicação de ida e volta pela rede, sobrecarga de autenticação nem pool de conexões para gerenciar. O DuckDB abre um arquivo ou diretório, cria um plano de consulta e o executa. O binário tem menos de 100 MB. Ele funciona em um notebook, em uma função Lambda ou em um script Python chamado por uma tarefa cron. Para pipelines analíticos auto-hospedados, a carga operacional é quase zero.

SQL diretamente em arquivos

O recurso que torna o DuckDB particularmente prático para dados do Seller Central é a leitura de arquivos por padrões glob. Uma única instrução SQL consulta milhares de CSVs simultaneamente:

SELECT
  date_trunc('month', "Date") AS month,
  SUM("Units Ordered")          AS units,
  SUM("Ordered Product Sales")  AS revenue
FROM read_csv_auto('/data/seller-central/business-reports/*.csv')
GROUP BY 1
ORDER BY 1;

read_csv_auto infere o esquema a partir do conteúdo dos arquivos: amostra linhas, detecta tipos de coluna, lida com diferentes delimitadores e, com union_by_name=true, unifica as colunas por nome entre arquivos com conjuntos de colunas distintos (por padrão, usa o esquema do primeiro arquivo e associa os arquivos seguintes por posição). Não reconcilia colunas renomeadas por conta própria; elas ainda precisam de um alias ou de um COALESCE na consulta. Isso importa para os dados do Seller Central, nos quais a Amazon revisou os nomes das colunas pelo menos quatro vezes nos últimos três anos. A consulta é executada sobre os arquivos presentes no diretório naquele momento, sem etapa de importação, migração de esquema ou cache desatualizado para invalidar.

Para exportações TSV do console de publicidade, a mesma abordagem se aplica, com uma indicação explícita do formato:

FROM read_csv('/data/advertising/*.tsv', delim='	', header=true)

Isso vai além da conveniência. É outra forma de trabalhar com dados em arquivos: o sistema de arquivos passa a ser o banco de dados. O esquema é inferido, não declarado. As consultas combinam dados de diferentes arquivos sem nenhuma etapa de pré-processamento. Para análises ad hoc, nas quais a pergunta muda mais rápido do que um pipeline ETL pode ser atualizado, essa arquitetura é a escolha padrão adequada.

Características de desempenho

Em nosso conjunto de dados de referência — 4.200 arquivos, aproximadamente 2,8 GB sem compressão —, uma agregação completa de receita por marketplace, ASIN e mês leva 4,1 segundos em um notebook comum (Apple M2, 16 GB de RAM). A consulta equivalente no PostgreSQL, com dados importados e indexados, leva 2,8 segundos. Mas essa comparação exclui os quarenta minutos da importação inicial, as migrações de esquema necessárias sempre que a Amazon muda um nome de coluna e o custo contínuo de manutenção do ETL. No custo operacional total, o DuckDB leva uma vantagem decisiva para essa carga de trabalho.

Se o desempenho das consultas se tornar um gargalo — normalmente na escala de vários terabytes —, o DuckDB lê Parquet nativamente, e a conversão dos arquivos CSV existentes para Parquet é feita uma única vez:

COPY (FROM read_csv_auto('/data/**/*.csv'))
TO '/data/consolidated.parquet'
(FORMAT PARQUET);

As consultas posteriores ao arquivo Parquet são de 8 a 12 vezes mais rápidas do que as consultas aos CSVs originais. O Parquet armazena estatísticas de coluna — valores mínimos e máximos por grupo de linhas — que o DuckDB usa para ignorar seções inteiras de dados sem lê-las. Um filtro marketplace = 'DE' em um arquivo Parquet ordenado pode ler menos de dez por cento dos dados em disco. Essa otimização por filtragem antecipada não está disponível em arquivos CSV simples.

Construindo um painel independente com Chart.js

As consultas do DuckDB produzem dados. A questão prática é o que fazer com eles. Para relatórios internos de operações de pequeno e médio porte na Amazon, um arquivo HTML independente com Chart.js costuma ser mais útil do que um painel hospedado: não há servidor para operar, credenciais para gerenciar nem instância do Superset ou Metabase para manter.

O pipeline funciona em três etapas. Primeiro, o DuckDB exporta os resultados das consultas para JSON:

COPY (
  SELECT
    strftime(date_trunc('week', "Date"), '%Y-%m-%d') AS week,
    SUM("Units Ordered")         AS units,
    SUM("Ordered Product Sales") AS revenue
  FROM read_csv_auto('/data/**/*.csv')
  GROUP BY 1 ORDER BY 1
) TO 'weekly.json' (FORMAT JSON, ARRAY true);

Segundo, um script Python lê esses arquivos JSON e incorpora os dados diretamente em um modelo HTML que carrega o Chart.js via CDN. Terceiro, o arquivo HTML é aberto em um navegador, sem necessidade de servidor web.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Seller Dashboard</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
  <canvas id="revenue"></canvas>
  <script>
    const rows = /* JSON_DATA_HERE */;
    new Chart(document.getElementById('revenue'), {
      type: 'line',
      data: {
        labels: rows.map(r => r.week),
        datasets: [{ label: 'Revenue', data: rows.map(r => r.revenue), tension: 0.3 }]
      }
    });
  </script>
</body>
</html>

O resultado é um único arquivo HTML (ele carrega o Chart.js de uma CDN, portanto é necessário acesso à internet para visualizá-lo, a menos que a biblioteca seja incorporada diretamente ao arquivo) que pode ser compartilhado por e-mail, colocado em um bucket S3 privado ou aberto diretamente de um diretório local. Para equipes operacionais que precisam de relatórios semanais sem a sobrecarga de uma infraestrutura de painéis em tempo real, essa abordagem é prática e fácil de manter. Para gerar o painel novamente, basta executar um script shell: o DuckDB consulta os CSVs, o Python cria o HTML e pronto. Sem infraestrutura de servidores nem custos recorrentes de hospedagem, e com poucos elementos para manter: as consultas e o script do modelo.

Quando o DuckDB supera o PostgreSQL

O DuckDB não substitui o PostgreSQL. A comparação só faz sentido quando a carga de trabalho é de fato analítica — grandes varreduras, agregações e agrupamentos — e os dados são principalmente lidos, não gravados. O PostgreSQL tem uma vantagem decisiva em gravações concorrentes, integridade referencial, controle de acesso por linha e nos requisitos operacionais completos de uma aplicação em produção. É a escolha adequada para um sistema multiusuário no qual o código da aplicação insere, atualiza e consulta registros.

O DuckDB leva vantagem quando:

  • Os dados já existem como arquivos em disco, sem uma etapa de importação prevista
  • O esquema não é totalmente conhecido de antemão ou muda com frequência
  • As consultas são ad hoc e os padrões de acesso não podem ser previstos de antemão
  • A duração ou a escala do projeto não justifica a sobrecarga operacional de um servidor de banco de dados

Os dados do Amazon Seller Central atendem precisamente aos quatro critérios. O esquema muda a cada versão do Seller Central. Os arquivos já existem localmente após a exportação. As perguntas de negócio mudam de semana para semana, conforme o que está sendo otimizado. E a alternativa — um pipeline ETL PostgreSQL mantido continuamente — representa semanas de trabalho de engenharia para um problema que o DuckDB resolve em uma tarde.

A regra prática: se você normalmente usaria o Excel ou um notebook Jupyter para analisar esses dados, use o DuckDB. O SQL é mais expressivo, o desempenho costuma ser muito superior para volumes acima de alguns milhares de linhas e a arquitetura baseada em arquivos permite que os dados permaneçam onde já estão.

Artigos relacionados

Leitura complementar