DuckDB: Formatos de archivo y optimizaciones de rendimiento
6 min de lectura

DuckDB: Formatos de archivo y optimizaciones de rendimiento

1162 palabras

Últimamente estoy trabajando bastante con DuckDB, y una de las cosas que más me interesan es entender cómo optimizar el rendimiento según el formato de archivo que estemos usando.

No es lo mismo trabajar con Parquet, con CSV comprimido, o con CSV descomprimido. Y las diferencias de rendimiento pueden ser dramáticas.

Vamos a revisar las optimizaciones clave que hay que tener en cuenta cuando trabajamos con diferentes formatos de archivo en DuckDB.

Parquet: ¿Query directo o cargar primero?

DuckDB tiene soporte avanzado para Parquet, incluyendo la capacidad de consultar archivos Parquet directamente sin cargarlos en la base de datos. Pero ¿cuándo deberías hacer una cosa u otra?

A favor de query directo Parquet

Estadísticas básicas disponibles: Parquet usa almacenamiento columnar y contiene estadísticas básicas como zonemaps. Esto permite a DuckDB aplicar optimizaciones como projection pushdown y filter pushdown. Los workloads que combinan proyección, filtrado y agregación funcionan muy bien sobre Parquet.

Consideraciones de almacenamiento: Cargar datos desde Parquet requiere aproximadamente el mismo espacio que el archivo DuckDB. Si el espacio en disco es limitado, query directo sobre Parquet es una buena opción.

En contra de query directo Parquet

Falta de estadísticas avanzadas: El formato de base de datos DuckDB tiene estadísticas hyperloglog que Parquet no tiene. Estas mejoran la precisión de las estimaciones de cardinalidad, especialmente importante en queries con muchos joins.

Tip: Si DuckDB produce un orden de join subóptimo en archivos Parquet, prueba a cargar los Parquet en tablas DuckDB. Las estadísticas mejoradas ayudarán a obtener un mejor orden de join.

Queries repetidas: Si planeas ejecutar múltiples queries sobre el mismo dataset, vale la pena cargar los datos en DuckDB. Las queries serán siempre algo más rápidas, lo que amortiza el tiempo de carga inicial.

Tiempos de descompresión altos: Algunos archivos Parquet usan compresión pesada como gzip. En estos casos, query directo requiere descompresión costosa cada vez que se accede al archivo. Métodos ligeros como Snappy, LZ4 y zstd son más rápidos de descomprimir.

Microbenchmark: TPC-H en DuckDB vs Parquet

Las queries del benchmark TPC-H corren aproximadamente 1.1-5.0× más lento en archivos Parquet que en una base de datos DuckDB.

Best practice: Si tienes espacio disponible, y tu workload es join-heavy y/o planeas ejecutar muchas queries sobre el mismo dataset, carga los archivos Parquet en la base de datos primero.


Tamaño de Row Groups en Parquet

DuckDB funciona mejor con archivos Parquet que tienen row groups de 100K-1M filas cada uno.

¿Por qué? Porque DuckDB solo puede paralelizar sobre row groups. Si un Parquet tiene un solo row group gigante, solo puede ser procesado por un thread.

Puedes usar la función parquet_metadata para ver cuántos row groups tiene un archivo Parquet.

Microbenchmark: Agregación con diferentes tamaños de row group

Tamaño de row groupTiempo de ejecución
9608.77 s
1,9208.95 s
3,8404.33 s
7,6802.35 s
15,3601.58 s
30,7201.17 s
61,4400.94 s
122,8800.87 s
245,7600.93 s
491,5200.95 s
983,0400.97 s
1,966,0800.88 s

Los resultados muestran que:

  • Row groups <5,000 tienen un efecto fuertemente negativo (5-10× más lento)
  • Row groups entre 5,000-20,000 son todavía 1.5-2.5× más lentos que lo ideal
  • Arriba de 100,000 las diferencias son pequeñas (~10% entre el mejor y el peor)

Cuando escribas archivos Parquet, usa la opción row_group_size.


Tamaños de archivos Parquet

DuckDB también puede paralelizar across múltiples archivos Parquet. Es recomendable tener al menos tantos row groups totales como threads de CPU.

Por ejemplo, con una máquina de 10 threads:

  • 10 archivos con 1 row group cada uno = paralelismo completo
  • 1 archivo con 10 row groups = paralelismo completo

Best practice: El rango ideal es 100 MB - 10 GB por archivo Parquet individual.


Hive Partitioning para Filter Pushdown

Cuando queries muchos archivos con condiciones de filtro, el rendimiento puede mejorar usando una estructura de carpetas en formato Hive para particionar los datos a lo largo de las columnas usadas en el filtro.

DuckDB solo leerá las carpetas y archivos que cumplan los criterios del filtro. Esto es especialmente útil para archivos remotos.


CSV: El caso sorprendente de .csv.gz

Aquí viene una de las cosas más interesantes que he aprendido sobre DuckDB y CSV.

Los archivos CSV suelen distribuirse comprimidos con GZIP (.csv.gz). DuckDB puede descomprimir estos archivos on-the-fly.

Y aquí está lo sorprendente: esto es típicamente más rápido que descomprimir los archivos primero y luego cargarlos, debido a la reducción de I/O.

EsquemaTiempo de carga
Cargar desde CSV comprimido GZIP (.csv.gz)107.1 s
Descomprimir (con gunzip paralelo) y cargar desde CSV descomprimido121.3 s

Leer directamente el .csv.gz es ~12% más rápido que descomprimirlo primero.

Esto es una llamada de atención. No estamos hablando de una diferencia pequeña. Estamos hablando de que leer el archivo comprimido directamente es más rápido que el workflow “tradicional” de descomprimir primero.

¿Por qué? Porque el I/O reducido compensa el coste de descompresión. Leer menos datos del disco suele ser más rápido que leer más datos descomprimidos.

Cargar muchos archivos CSV pequeños

El CSV reader ejecuta el CSV sniffer en todos los archivos. Para muchos archivos pequeños, esto puede causar overhead innecesario.

Una optimización es desactivar el sniffer. Asumiendo que todos los archivos tienen el mismo dialecto CSV y mismos nombres/tipos de columnas:

-- Obtener las opciones del sniffer desde un archivo
.mode line
SELECT Prompt FROM sniff_csv('part-0001.csv');

Esto te da algo como:

FROM read_csv('file_path.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true,
columns={'hello': 'BIGINT', 'world': 'VARCHAR'});

Luego puedes ajustar el comando read_csv, aplicando filename expansion (globbing), con las opciones detectadas:

FROM read_csv('part-*.csv', auto_detect=false, delim=',', quote='"', escape='"', new_line='\n', skip=0, header=true,
columns={'hello': 'BIGINT', 'world': 'VARCHAR'});

Resumen de recomendaciones

Parquet

  • ✅ Query directo para workloads de proyección/filtro/agregación
  • ✅ Cargar en DuckDB para muchos joins o queries repetidas
  • ✅ Usar row groups de 100K-1M filas
  • ✅ Mantener archivos entre 100 MB - 10 GB
  • ✅ Preferir Snappy/LZ4/zstd sobre gzip

CSV

  • ✅ Leer .csv.gz directamente (NO descomprimir primero)
  • ✅ Desactivar sniffer para muchos archivos pequeños con mismo esquema
  • ✅ Considerar convertir a Parquet para workloads analíticos

General

  • ✅ Usar Hive partitioning para filtros frecuentes
  • ✅ Consultar parquet_metadata para entender estructura de archivos
  • ✅ Considerar el equilibrio entre espacio y rendimiento

Conclusión

Lo que más me ha llamado la atención trabajando con DuckDB es que las intuiciones tradicionales sobre procesamiento de datos a veces no se aplican.

El caso de .csv.gz es el ejemplo perfecto. Uno pensaría que descomprimir primero sería más rápido, pero la realidad es que leer el archivo comprimido directamente es más rápido.

Y eso es clave cuando trabajamos con datos grandes. Los tiempos de carga y proceso importan, y entender estas optimizaciones puede marcar una diferencia sustancial en el rendimiento de tus pipelines de datos.

Estas optimizaciones forman ya parte de mi checklist estándar cuando trabajo con DuckDB. Conocer el formato de archivo adecuado, el tamaño óptimo de row groups, y cuándo query directo vs cargar datos, marca la diferencia entre un pipeline de datos funcional y uno realmente eficiente.


Referencias

Comentarios

Últimas Entradas

3 min

455 palabras

Lately I’ve been working quite a bit with DuckDB, and one of the things that interests me most is understanding how to optimize performance according to the file format we’re using.

It’s not the same working with Parquet, compressed CSV, or uncompressed CSV. And the performance differences can be dramatic.

Let’s review the key optimizations to keep in mind when working with different file formats in DuckDB.

Parquet: Direct Query or Load First?

DuckDB has advanced Parquet support, including the ability to query Parquet files directly without loading them into the database. But when should you do one or the other?

4 min

796 palabras

El problema: httpfs ignora tus variables de entorno

Si trabajas con DuckDB y la extensión httpfs para leer Parquet remotos, CSVs desde S3 o cualquier recurso HTTP, probablemente asumes que las variables de entorno HTTP_PROXY y HTTPS_PROXY funcionan igual que en cualquier otra herramienta. Curl las respeta. wget las respeta. Python requests las respeta. Node.js las respeta.

DuckDB no.

Me he encontrado con esto trabajando en un entorno corporativo con proxy obligatorio. Tenía un script que leía ficheros Parquet desde Google Cloud Storage usando httpfs, y simplemente no funcionaba. Sin error claro, sin timeout descriptivo, solo silencio. Mientras tanto, un curl al mismo recurso con las mismas variables de entorno devolvía los datos sin problema.

11 min

2298 palabras

Toda consulta empieza con un plan. Toda consulta lenta probablemente empieza con uno malo. Y más a menudo de lo que crees, las estadísticas son las culpables.

Pero ¿cómo funciona realmente? PostgreSQL no ejecuta la consulta para averiguarlo — estima el coste. Lee datos precalculados de pg_class y pg_statistic y hace los cálculos para encontrar la ruta más barata hacia tus datos.

En el escenario ideal, los números que lee son precisos y obtienes el plan que esperas. Pero cuando están desactualizados, la situación se descontrola. El planificador estima 500 filas, planifica un nested loop, y se encuentra con 25,000. Lo que parecía un plan óptimo se convierte en una falla en cascada.

5 min

987 palabras

Hace unos meses, cuando Anthropic lanzó su protocolo MCP (Model Context Protocol), sabía que íbamos a ver integraciones interesantes entre LLMs y bases de datos. Lo que no esperaba era ver algo tan pulido y funcional como AgentHouse de ClickHouse tan pronto.

Tengo planificado probar esta demo en breve, pero ya solo leyendo sobre ella me parece fascinante la idea de poder preguntarle a una base de datos cosas como “¿cuáles son los repositorios más populares de GitHub este mes?” y obtener no solo una respuesta, sino visualizaciones automáticas.

10 min

1945 palabras

Apache Iceberg v3: Revolución en Datos Geoespaciales para el Analytics Moderno

La reciente ratificación de la especificación Apache Iceberg v3 marca un hito significativo en el ecosistema de datos abiertos, especialmente en el ámbito de los datos geoespaciales. Esta actualización no solo consolida a Iceberg como el estándar líder en formatos de tabla abiertos, sino que introduce capacidades geoespaciales nativas que prometen transformar cómo manejamos datos de ubicación y mapeo a gran escala.