DuckDB vs Spark para Queries em Data Lake: Quando Cada Um Ganha
O DataSpoc Lens usa DuckDB para consultar arquivos Parquet em armazenamento na nuvem. O Databricks usa Spark. Ambos podem consultar data lakes. Eles são construídos para escalas e casos de uso fundamentalmente diferentes. Este post dá a você os dados para escolher o correto.
A Diferença Central
DuckDB é um banco de dados analítico in-process. Ele roda dentro do seu processo Python, seu CLI ou seu laptop. Zero infraestrutura. Um processo. Uma máquina.
Spark é um motor de computação distribuída. Ele roda em um cluster de máquinas. Requer infraestrutura: um gerenciador de cluster (YARN, Kubernetes ou Databricks), nós driver, nós worker, configuração.
DuckDB: Your laptop → DuckDB (in-process) → Parquet in S3 → Results
Spark: Your laptop → Spark Driver → Cluster Manager → N Worker Nodes → Parquet in S3 → Shuffle → ResultsComparação de Setup
DuckDB (via DataSpoc Lens)
pip install dataspoc-lensfrom dataspoc_lens import LensClient
lens = LensClient()df = lens.query("SELECT region, SUM(amount) FROM curated_sales GROUP BY region")print(df)Tempo até a primeira query: menos de 30 segundos (instalar + executar).
Spark (via PySpark)
pip install pyspark# Also need: Java 8+, Hadoop config, S3 credentials configfrom pyspark.sql import SparkSession
spark = SparkSession.builder \ .appName("data-lake-query") \ .config("spark.jars.packages", "org.apache.hadoop:hadoop-aws:3.3.4") \ .config("spark.hadoop.fs.s3a.impl", "org.apache.hadoop.fs.s3a.S3AFileSystem") \ .config("spark.hadoop.fs.s3a.aws.credentials.provider", "com.amazonaws.auth.DefaultAWSCredentialsProviderChain") \ .config("spark.driver.memory", "4g") \ .config("spark.sql.parquet.enableVectorizedReader", "true") \ .getOrCreate()
df = spark.read.parquet("s3a://my-data-lake/curated/sales/")df.createOrReplaceTempView("curated_sales")
result = spark.sql("SELECT region, SUM(amount) FROM curated_sales GROUP BY region")result.show()Tempo até a primeira query: 5-15 minutos (instalar Java, configurar Hadoop, depurar autenticação S3, esperar o Spark iniciar).
No Databricks, o setup é mais rápido (serviço gerenciado), mas você paga $0.15-0.75 por DBU-hora a partir do momento que inicia um cluster.
Benchmark: Mesmas Queries, Diferentes Escalas
Executamos 5 queries em 4 tamanhos de dataset em arquivos Parquet no S3. DuckDB rodou em uma única máquina (8 cores, 32GB RAM). Spark rodou em um cluster de 3 nós (8 cores, 32GB RAM cada = 24 cores, 96GB total).
Query 1: Agregação Simples
SELECT region, SUM(amount) as total, COUNT(*) as ordersFROM salesGROUP BY regionORDER BY total DESC| Tamanho do Dataset | DuckDB | Spark | Vencedor |
|---|---|---|---|
| 1M linhas (200MB) | 0.3s | 8.2s | DuckDB (27x) |
| 10M linhas (2GB) | 1.8s | 9.5s | DuckDB (5x) |
| 100M linhas (20GB) | 14s | 18s | DuckDB (1.3x) |
| 1B linhas (200GB) | OOM | 45s | Spark |
Query 2: Join + Agregação
SELECT c.segment, DATE_TRUNC('month', s.sale_date) as month, SUM(s.amount) as revenue, COUNT(DISTINCT s.customer_id) as customersFROM sales sJOIN customers c ON s.customer_id = c.customer_idGROUP BY c.segment, monthORDER BY month, revenue DESC| Tamanho do Dataset | DuckDB | Spark | Vencedor |
|---|---|---|---|
| 1M linhas | 0.5s | 12s | DuckDB (24x) |
| 10M linhas | 3.2s | 14s | DuckDB (4x) |
| 100M linhas | 28s | 22s | Spark (1.3x) |
| 1B linhas | OOM | 68s | Spark |
Query 3: Window Function
SELECT customer_id, sale_date, amount, SUM(amount) OVER (PARTITION BY customer_id ORDER BY sale_date) as running_total, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY sale_date DESC) as recency_rankFROM salesWHERE sale_date >= '2025-01-01'| Tamanho do Dataset | DuckDB | Spark | Vencedor |
|---|---|---|---|
| 1M linhas | 0.8s | 15s | DuckDB (19x) |
| 10M linhas | 6.5s | 18s | DuckDB (3x) |
| 100M linhas | 52s | 35s | Spark (1.5x) |
| 1B linhas | OOM | 120s | Spark |
Query 4: Full Table Scan com Filtro Complexo
SELECT * FROM salesWHERE amount > 100 AND region IN ('NA', 'EU') AND sale_date BETWEEN '2025-06-01' AND '2025-12-31' AND product LIKE '%Pro%'ORDER BY amount DESCLIMIT 1000| Tamanho do Dataset | DuckDB | Spark | Vencedor |
|---|---|---|---|
| 1M linhas | 0.2s | 7s | DuckDB (35x) |
| 10M linhas | 0.9s | 8s | DuckDB (9x) |
| 100M linhas | 6s | 12s | DuckDB (2x) |
| 1B linhas | 45s | 15s | Spark (3x) |
Query 5: Exploração Ad-Hoc
-- "What does this data look like?"SELECT * FROM sales LIMIT 100| Tamanho do Dataset | DuckDB | Spark | Vencedor |
|---|---|---|---|
| Qualquer tamanho | 0.1s | 5-8s | DuckDB (50-80x) |
O overhead do Spark é constante: inicialização do cluster, agendamento de jobs, distribuição de tarefas. Para exploração ad-hoc, DuckDB é sempre mais rápido porque não há cluster para coordenar.
Comparação de Custos
DuckDB (via DataSpoc Lens)
Software: $0 (open-source)Compute: Your laptop (free) or a VM ($50-150/month)Storage: S3/GCS/Azure ($0.02/GB/month)
Monthly cost for 500GB data lake: ~$60Spark no Databricks
Software: Databricks DBU pricing ($0.15-0.75/DBU-hour)Compute: Cluster instances (e.g., 3x i3.xlarge = $0.94/hour)Storage: S3/GCS/Azure ($0.02/GB/month)Cluster uptime: Even "serverless" has startup cost
Monthly cost for 500GB data lake with moderate query load: Cluster: $200-800/month (depending on uptime) DBUs: $150-500/month Storage: $10/month Total: ~$360-1,310/monthSpark no EMR
EMR fee: $0.015-0.27/hour per instanceEC2 instances: 3x m5.xlarge = $0.576/hourStorage: $10/month
Monthly cost (8 hours/day usage): ~$300-500/monthQuando DuckDB Ganha
1. Dados Cabem na Memória (< 100GB)
DuckDB processa dados in-memory em uma única máquina. Para datasets abaixo de 100GB (que cobrem a vasta maioria dos data lakes empresariais), ele é mais rápido que Spark porque não há overhead de cluster.
from dataspoc_lens import LensClient
lens = LensClient()
# 50GB dataset? DuckDB handles it fine on a 64GB machinedf = lens.query(""" SELECT category, DATE_TRUNC('week', event_date) as week, COUNT(*) as events, COUNT(DISTINCT user_id) as users FROM curated_events GROUP BY category, week ORDER BY week DESC, events DESC""")2. Exploração Ad-Hoc e Desenvolvimento
Quando você está explorando dados, escrevendo queries iterativamente e verificando resultados, o tempo de resposta sub-segundo do DuckDB muda como você trabalha:
# Exploration loop — instant feedbacklens.query("SELECT * FROM curated_sales LIMIT 5") # 0.1slens.query("SELECT DISTINCT region FROM curated_sales") # 0.2slens.query("SELECT region, COUNT(*) FROM curated_sales GROUP BY 1") # 0.3s# vs. Spark: 8s, 8s, 9s for the same queries3. CI/CD e Testes
Executar queries analíticas em pipelines CI requer startup rápido e zero infraestrutura:
# GitHub Actions — test your SQL transformations- name: Test data transformations run: | pip install dataspoc-lens python -c " from dataspoc_lens import LensClient lens = LensClient() result = lens.query('SELECT COUNT(*) as cnt FROM curated_sales') assert result['cnt'].iloc[0] > 0, 'Sales table is empty' "4. Integração com Agentes de IA
DuckDB inicia instantaneamente, o que é crítico para agentes de IA que precisam executar múltiplas queries em uma conversa:
# Agent loop: 5 queries in sequence# DuckDB: 0.3s + 0.2s + 0.5s + 0.3s + 0.4s = 1.7s total# Spark: 8s + 8s + 9s + 8s + 9s = 42s total
# Users will not wait 42 seconds for each agent responseQuando Spark Ganha
1. Dados Excedem a Memória de Uma Máquina (> 200GB)
Quando seu dataset é maior do que cabe na RAM, Spark distribui o trabalho em um cluster:
# 2TB dataset — Spark distributes across 20 nodesspark.sql(""" SELECT user_id, SUM(amount) as lifetime_value FROM massive_events -- 2TB, 10 billion rows GROUP BY user_id HAVING lifetime_value > 1000""").write.parquet("s3://bucket/gold/high_value_users/")2. Pipelines ETL de Produção
A tolerância a falhas do Spark (linhagem RDD, execução especulativa, retry de tarefas) o torna confiável para jobs de produção agendados:
# Daily pipeline that processes yesterday's data# If a node dies mid-job, Spark retries the failed tasksspark.sql(""" INSERT OVERWRITE TABLE gold_daily_metrics SELECT DATE(event_time) as day, COUNT(*) as events FROM raw_events WHERE DATE(event_time) = CURRENT_DATE - 1 GROUP BY day""")3. Machine Learning em Escala
Spark MLlib lida com treinamento distribuído em datasets massivos:
from pyspark.ml.classification import RandomForestClassifierfrom pyspark.ml.feature import VectorAssembler
# Train on 1 billion rows — distributed across clusterassembler = VectorAssembler(inputCols=feature_cols, outputCol="features")rf = RandomForestClassifier(numTrees=100, maxDepth=10)pipeline = Pipeline(stages=[assembler, rf])model = pipeline.fit(training_data) # distributed training4. Streaming
Spark Structured Streaming lida com ingestão e processamento de dados em tempo real:
stream = spark.readStream \ .format("kafka") \ .option("kafka.bootstrap.servers", "kafka:9092") \ .option("subscribe", "events") \ .load()
# Process in real-time, write to data lakestream.writeStream \ .format("parquet") \ .option("path", "s3://bucket/raw/events/") \ .option("checkpointLocation", "s3://bucket/checkpoints/events/") \ .start()DuckDB não faz streaming. É um motor de query batch.
A Aposta do DataSpoc: DuckDB para 90% dos Times
A maioria das empresas tem data lakes abaixo de 100GB. A maioria das queries analíticas é ad-hoc: alguém faz uma pergunta, um analista escreve SQL, a resposta volta. Para essa carga de trabalho, DuckDB é estritamente melhor que Spark:
- Mais rápido para datasets abaixo de 100GB
- Mais barato em 10-50x (sem cluster para manter)
- Mais simples de operar (sem JVM, sem YARN, sem configuração de cluster)
- Melhor para agentes de IA (startup instantâneo, queries sub-segundo)
from dataspoc_lens import LensClient
lens = LensClient()
# This is all you need. No cluster. No JVM. No config.df = lens.query("SELECT region, SUM(amount) FROM curated_sales GROUP BY region")print(df)Se você superar o DuckDB — seu lake passa de 200GB, você precisa de streaming, precisa de ML distribuído — o Spark está lá. Os dados ainda são Parquet em um bucket na nuvem. Mudar de motor de query não requer migrar dados.
Essa é a beleza do formato aberto: Parquet não se importa com o que o lê. Comece com DuckDB. Evolua para Spark se e quando precisar. A maioria dos times nunca vai precisar.
Framework de Decisão Rápida
Is your data lake > 200GB? → Yes: Consider Spark → No: Use DuckDB (DataSpoc Lens)
Do you need real-time streaming? → Yes: Spark Structured Streaming → No: DuckDB
Are you building production ETL that runs 24/7? → Yes: Spark (fault tolerance matters) → No: DuckDB
Are you doing ad-hoc analysis or AI agent queries? → Yes: DuckDB (speed matters) → No: Either works
What is your infrastructure budget? → $0-100/month: DuckDB (no cluster needed) → $500+/month: Spark is viablePara 90% dos times de dados, a resposta é DuckDB. O DataSpoc Lens torna isso simples.