DuckDB vs Spark para Consultas en Data Lakes: Cuándo Gana Cada Uno
DataSpoc Lens usa DuckDB para consultar archivos Parquet en almacenamiento cloud. Databricks usa Spark. Ambos pueden consultar data lakes. Están construidos para escalas y casos de uso fundamentalmente diferentes. Este artículo te da los datos para elegir el correcto.
La Diferencia Central
DuckDB es una base de datos analítica en proceso. Se ejecuta dentro de tu proceso Python, tu CLI o tu laptop. Cero infraestructura. Un proceso. Una máquina.
Spark es un motor de computación distribuida. Se ejecuta a través de un cluster de máquinas. Requiere infraestructura: un administrador de cluster (YARN, Kubernetes o Databricks), nodos driver, nodos worker, configuración.
DuckDB: Your laptop → DuckDB (in-process) → Parquet in S3 → Results
Spark: Your laptop → Spark Driver → Cluster Manager → N Worker Nodes → Parquet in S3 → Shuffle → ResultsComparación de Configuración
DuckDB (vía 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)Tiempo hasta la primera consulta: menos de 30 segundos (instalar + ejecutar).
Spark (vía 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()Tiempo hasta la primera consulta: 5-15 minutos (instalar Java, configurar Hadoop, depurar autenticación S3, esperar inicio de Spark).
En Databricks, la configuración es más rápida (servicio gestionado), pero estás pagando $0.15-0.75 por DBU-hora desde el momento en que inicias un cluster.
Benchmark: Mismas Consultas, Diferentes Escalas
Ejecutamos 5 consultas en 4 tamaños de dataset sobre archivos Parquet en S3. DuckDB se ejecutó en una sola máquina (8 cores, 32GB RAM). Spark se ejecutó en un cluster de 3 nodos (8 cores, 32GB RAM cada uno = 24 cores, 96GB total).
Consulta 1: Agregación Simple
SELECT region, SUM(amount) as total, COUNT(*) as ordersFROM salesGROUP BY regionORDER BY total DESC| Tamaño del Dataset | DuckDB | Spark | Ganador |
|---|---|---|---|
| 1M filas (200MB) | 0.3s | 8.2s | DuckDB (27x) |
| 10M filas (2GB) | 1.8s | 9.5s | DuckDB (5x) |
| 100M filas (20GB) | 14s | 18s | DuckDB (1.3x) |
| 1B filas (200GB) | OOM | 45s | Spark |
Consulta 2: Join + Agregación
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| Tamaño del Dataset | DuckDB | Spark | Ganador |
|---|---|---|---|
| 1M filas | 0.5s | 12s | DuckDB (24x) |
| 10M filas | 3.2s | 14s | DuckDB (4x) |
| 100M filas | 28s | 22s | Spark (1.3x) |
| 1B filas | OOM | 68s | Spark |
Consulta 3: Función de Ventana
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'| Tamaño del Dataset | DuckDB | Spark | Ganador |
|---|---|---|---|
| 1M filas | 0.8s | 15s | DuckDB (19x) |
| 10M filas | 6.5s | 18s | DuckDB (3x) |
| 100M filas | 52s | 35s | Spark (1.5x) |
| 1B filas | OOM | 120s | Spark |
Consulta 4: Escaneo Completo de Tabla con Filtro Complejo
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| Tamaño del Dataset | DuckDB | Spark | Ganador |
|---|---|---|---|
| 1M filas | 0.2s | 7s | DuckDB (35x) |
| 10M filas | 0.9s | 8s | DuckDB (9x) |
| 100M filas | 6s | 12s | DuckDB (2x) |
| 1B filas | 45s | 15s | Spark (3x) |
Consulta 5: Exploración Ad-Hoc
-- "What does this data look like?"SELECT * FROM sales LIMIT 100| Tamaño del Dataset | DuckDB | Spark | Ganador |
|---|---|---|---|
| Cualquier tamaño | 0.1s | 5-8s | DuckDB (50-80x) |
La sobrecarga de Spark es constante: inicialización del cluster, programación de trabajos, distribución de tareas. Para exploración ad-hoc, DuckDB siempre es más rápido porque no hay cluster que coordinar.
Comparación de Costos
DuckDB (vía 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 en 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 en 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/monthCuándo Gana DuckDB
1. Los Datos Caben en Memoria (< 100GB)
DuckDB procesa datos en memoria en una sola máquina. Para datasets menores a 100GB (lo cual cubre la gran mayoría de data lakes empresariales), es más rápido que Spark porque no hay sobrecarga 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. Exploración Ad-Hoc y Desarrollo
Cuando estás explorando datos, escribiendo consultas iterativamente y revisando resultados, el tiempo de respuesta sub-segundo de DuckDB cambia cómo trabajas:
# 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 y Testing
Ejecutar consultas analíticas en pipelines de CI requiere inicio rápido y cero infraestructura:
# 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. Integración con Agentes de IA
DuckDB inicia instantáneamente, lo cual es crítico para agentes de IA que necesitan ejecutar múltiples consultas en una conversación:
# 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 responseCuándo Gana Spark
1. Los Datos Exceden la Memoria de Una Sola Máquina (> 200GB)
Cuando tu dataset es más grande de lo que cabe en RAM, Spark distribuye el trabajo a través de un 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 Producción
La tolerancia a fallos de Spark (linaje de RDD, ejecución especulativa, reintento de tareas) lo hace confiable para trabajos de producción programados:
# 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 a Escala
Spark MLlib maneja entrenamiento distribuido en datasets masivos:
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 maneja ingesta y procesamiento de datos en tiempo 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 no hace streaming. Es un motor de consultas batch.
La Apuesta de DataSpoc: DuckDB para el 90% de los Equipos
La mayoría de las empresas tienen data lakes de menos de 100GB. La mayoría de las consultas analíticas son ad-hoc: alguien hace una pregunta, un analista escribe SQL, la respuesta regresa. Para esta carga de trabajo, DuckDB es estrictamente mejor que Spark:
- Más rápido para datasets menores a 100GB
- Más barato por 10-50x (sin cluster que ejecutar)
- Más simple de operar (sin JVM, sin YARN, sin configuración de cluster)
- Mejor para agentes de IA (inicio instantáneo, consultas 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)Si superas DuckDB — tu lake pasa de 200GB, necesitas streaming, necesitas ML distribuido — Spark está ahí. Los datos siguen siendo Parquet en un bucket cloud. Cambiar motores de consulta no requiere migrar datos.
Esa es la belleza del formato abierto: a Parquet no le importa qué lo lee. Empieza con DuckDB. Gradúate a Spark si y cuando lo necesites. La mayoría de los equipos nunca lo necesitarán.
Marco de Decisión 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 el 90% de los equipos de datos, la respuesta es DuckDB. DataSpoc Lens lo hace sin esfuerzo.