duckdbsparkcomparisondata-lakeperformance

DuckDB vs Spark para Queries em Data Lake: Quando Cada Um Ganha

Michael San Martim · 2026-04-24

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 → Results

Comparação de Setup

DuckDB (via DataSpoc Lens)

Terminal window
pip install dataspoc-lens
from 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)

Terminal window
pip install pyspark
# Also need: Java 8+, Hadoop config, S3 credentials config
from 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 orders
FROM sales
GROUP BY region
ORDER BY total DESC
Tamanho do DatasetDuckDBSparkVencedor
1M linhas (200MB)0.3s8.2sDuckDB (27x)
10M linhas (2GB)1.8s9.5sDuckDB (5x)
100M linhas (20GB)14s18sDuckDB (1.3x)
1B linhas (200GB)OOM45sSpark

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 customers
FROM sales s
JOIN customers c ON s.customer_id = c.customer_id
GROUP BY c.segment, month
ORDER BY month, revenue DESC
Tamanho do DatasetDuckDBSparkVencedor
1M linhas0.5s12sDuckDB (24x)
10M linhas3.2s14sDuckDB (4x)
100M linhas28s22sSpark (1.3x)
1B linhasOOM68sSpark

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_rank
FROM sales
WHERE sale_date >= '2025-01-01'
Tamanho do DatasetDuckDBSparkVencedor
1M linhas0.8s15sDuckDB (19x)
10M linhas6.5s18sDuckDB (3x)
100M linhas52s35sSpark (1.5x)
1B linhasOOM120sSpark

Query 4: Full Table Scan com Filtro Complexo

SELECT * FROM sales
WHERE 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 DESC
LIMIT 1000
Tamanho do DatasetDuckDBSparkVencedor
1M linhas0.2s7sDuckDB (35x)
10M linhas0.9s8sDuckDB (9x)
100M linhas6s12sDuckDB (2x)
1B linhas45s15sSpark (3x)

Query 5: Exploração Ad-Hoc

-- "What does this data look like?"
SELECT * FROM sales LIMIT 100
Tamanho do DatasetDuckDBSparkVencedor
Qualquer tamanho0.1s5-8sDuckDB (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: ~$60

Spark 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/month

Spark no EMR

EMR fee: $0.015-0.27/hour per instance
EC2 instances: 3x m5.xlarge = $0.576/hour
Storage: $10/month
Monthly cost (8 hours/day usage): ~$300-500/month

Quando 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 machine
df = 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 feedback
lens.query("SELECT * FROM curated_sales LIMIT 5") # 0.1s
lens.query("SELECT DISTINCT region FROM curated_sales") # 0.2s
lens.query("SELECT region, COUNT(*) FROM curated_sales GROUP BY 1") # 0.3s
# vs. Spark: 8s, 8s, 9s for the same queries

3. 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 response

Quando 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 nodes
spark.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 tasks
spark.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 RandomForestClassifier
from pyspark.ml.feature import VectorAssembler
# Train on 1 billion rows — distributed across cluster
assembler = VectorAssembler(inputCols=feature_cols, outputCol="features")
rf = RandomForestClassifier(numTrees=100, maxDepth=10)
pipeline = Pipeline(stages=[assembler, rf])
model = pipeline.fit(training_data) # distributed training

4. 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 lake
stream.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 viable

Para 90% dos times de dados, a resposta é DuckDB. O DataSpoc Lens torna isso simples.

Recomendados