Durante casi diez años asumimos un axioma incuestionable: si tus datos superan la memoria RAM de tu portátil, necesitas un clúster distribuido. Si querías consultar un repositorio analítico almacenado en almacenamiento de objetos, abrías una consola en Databricks, levantabas un clúster de Amazon EMR con cuatro nodos worker de Apache Spark y esperabas tres minutos a que arrancase la máquina virtual de Java antes de ejecutar la primera línea de SQL.

Aquel peaje tenía sentido cuando el almacenamiento era un pantano de datos desordenado y las máquinas de oficina contaban con cuatro núcleos y ocho gigabytes de memoria. El hardware contemporáneo ha alterado esa ecuación. Cualquier servidor modesto de nube o estación de trabajo actual integra dieciséis núcleos, sesenta y cuatro gigabytes de memoria y discos NVMe capaces de transferir varios gigabytes por segundo. La masa crítica de los análisis empresariales cotidianos no se mueve en petabytes; se mueve en cientos de gigabytes.

Obligar a esos volúmenes a cruzar la sobrecarga de serialización en red de un clúster distribuido ya no es una necesidad arquitectónica; es un despilfarro operativo.

La madurez del formato de tabla abierta

El verdadero motivo por el que recurríamos a motores pesados residía en la fragilidad de los ficheros sueltos. Trabajar directamente sobre carpetas de ficheros Parquet en S3 implicaba lidiar con lecturas inconsistentes si un proceso de ingesta fallaba a mitad de camino, o sufrir lentitud extrema si el directorio acumulaba miles de particiones pequeñas.

Apache Iceberg resolvió ese vacío introduciendo una capa de metadatos estructurada en árbol. En lugar de tratar el almacenamiento como un sistema de ficheros tradicional, Iceberg define tablas mediante ficheros de manifiesto en formato JSON y Avro. Esto aporta:

  1. Aislamiento de instantáneas (ACID): Las lecturas siempre ven un estado consistente congelado en el tiempo. Si una escritura falla, los nuevos ficheros quedan huérfanos sin afectar a las consultas activas.
  2. Poda de particiones oculta: El motor no recorre rutas físicas del disco. Consulta las estadísticas min-max de los metadatos y descarta la inmensa mayoría de los ficheros Parquet antes de solicitar un solo byte por red.
  3. Evolución del esquema: Añadir, renombrar o eliminar columnas ocurre a nivel de metadatos sin reescribir terabytes de almacenamiento histórico.

Con el almacenamiento estructurado y gobernado por un estándar abierto, la pregunta evidente era: ¿de verdad necesitamos coordinadores de Spark para leer esos metadatos y aplicar filtros vectorizados?

Vectorización en proceso: el salto técnico de DuckDB

Aquí entra DuckDB. Concebido como el "SQLite del análisis de datos", es un motor OLAP que corre embebido dentro del mismo proceso que tu aplicación (sea un script de Python, un binario Go o una sesión interactiva).

A diferencia del procesamiento clásico por filas que explicábamos cuando analizábamos el límite físico de los Data Warehouses, DuckDB implementa un modelo de ejecución vectorial por bloques (morsel-driven parallelism). En vez de interpretar una tupla cada vez, procesa vectores de miles de valores en bloques que encajan en las cachés L1/L2/L3 de la CPU, aprovechando instrucciones SIMD del procesador moderno.

Cuando combinas la extensión nativa de Iceberg de DuckDB con su cliente HTTP para almacenamiento en la nube (httpfs), el motor lee el manifiesto de la tabla, calcula los rangos de bytes exactos que necesita mediante peticiones HTTP Range y descarga únicamente las columnas y bloques relevantes.

Veamos una consulta real ejecutada en un entorno local contra una tabla Iceberg alojada en un bucket S3:

import duckdb

# Conexión local en memoria (sin demonios de fondo ni puertos abiertos)
con = duckdb.connect()

# Instalación y carga de extensiones para almacenamiento en la nube e Iceberg
con.execute("""
    INSTALL httpfs;
    LOAD httpfs;
    INSTALL iceberg;
    LOAD iceberg;
""")

# Configuración de credenciales de acceso al almacenamiento de objetos
con.execute("""
    SET s3_region='eu-west-1';
    SET s3_access_key_id='TU_ACCESS_KEY';
    SET s3_secret_access_key='TU_SECRET_KEY';
""")

# Consulta directa sobre los metadatos de la tabla Iceberg
# DuckDB lee la versión más reciente del manifiesto y poda ficheros automáticamente
query = """
    EXPLAIN ANALYZE
    SELECT 
        categoria_producto,
        COUNT(*) AS total_operaciones,
        ROUND(SUM(importe_total), 2) AS volumen_ventas,
        ROUND(AVG(tiempo_entrega_horas), 1) AS promedio_entrega
    FROM iceberg_scan('s3://lakehouse-corporativo/iceberg/telemetria_pedidos', allow_moved_paths = true)
    WHERE fecha_registro >= '2025-01-01'
      AND pais_entrega = 'ES'
    GROUP BY categoria_producto
    ORDER BY volumen_ventas DESC;
"""

resultado = con.execute(query).fetchdf()
print(resultado)

Al inspeccionar el plan de ejecución con EXPLAIN ANALYZE, observas que DuckDB ni siquiera descarga los Parquet correspondientes a otros países o años anteriores. Evalúa las estadísticas almacenadas en el fichero de manifiesto metadata/*.json de Iceberg, descarta los bloques ajenos y canaliza el flujo de datos directamente a la memoria de la máquina.

Una agregación sobre una tabla de 180 GB con doscientos millones de filas se resuelve en apenas siete segundos en una única máquina de ocho núcleos, consumiendo menos de 4 GB de RAM. En Spark, el mero arranque de los ejecutores y la sincronización del plan lógico en el driver habría consumido más de minuto y medio.

Dónde crujen las costuras: los límites del enfoque

Defender la arquitectura de nodo único no significa caer en la ingenuidad de creer que los clústeres distribuidos carecen de utilidad. Adoptar DuckDB con Iceberg introduce compromisos específicos que conviene calibrar con frialdad:

  1. Concurrencia de escritura: DuckDB está diseñado primariamente para lectura analítica. Si tienes cincuenta microservicios intentando escribir transacciones simultáneas contra la misma tabla Iceberg, DuckDB no gestiona bloqueos distribuidos a nivel de catálogo. Para ingestión concurrente intensiva, motores como Apache Flink, Trino o un pipeline en Spark siguen siendo insustituibles.
  2. Higiene de metadatos: Iceberg genera nuevos ficheros de manifiesto en cada confirmación (commit). Si realizas inserciones frecuentes en lotes diminutos, acumularás miles de pequeños fragmentos de Parquet y metadatos huérfanos. DuckDB no ejecutará por ti los procedimientos de mantenimiento; requieres procesos periódicos de compactación y purga de instantáneas (expire_snapshots).
  3. El techo de la memoria y el desbordamiento a disco: DuckDB soporta operaciones que exceden la memoria disponible mediante volcado a disco (out-of-core execution), pero cuando una unión (JOIN) entre dos tablas gigantescas sin particionar fuerza un intercambio masivo de datos hacia el disco local, la penalización de I/O puede superar el coste de red de un clúster distribuido bien balanceado.

La sobriedad técnica frente a la inercia

Durante años pagamos facturas desmesuradas en almacenes de datos en la nube y plataformas de cómputo porque no disponíamos de herramientas ligeras capaces de procesar formatos abiertos sin montar una infraestructura faraónica.

La unión de Apache Iceberg como estándar de almacenamiento desacoplado y motores analíticos vectorizados como DuckDB demuestra que el péndulo de la arquitectura de datos se mueve hacia la simplificación. Antes de desplegar clústeres complejos con decenas de máquinas para responder preguntas analíticas habituales, evalúa el tamaño real de tus datos filtrados. Con frecuencia descubrirás que caben holgadamente en el procesador que tienes delante.