05 · Parquet y Hive partitioning¶
Cuaderno asociado
cuadernos/04_parquet_hive.ipynb. Volver al 00 - Índice de la demo.
Parquet es el formato donde DuckDB despliega todo su potencial: lee solo las columnas y los row groups que necesita, y con la 2.0 además lo hace con E/S asíncrona.
Cómo se organiza un dataset Hive¶
flowchart TD
ROOT["datos/ventas_hive/"]
ROOT --> Y24["year=2024/"]
ROOT --> Y25["year=2025/"]
Y24 --> M241["month=11/"]
Y24 --> M242["month=12/"]
Y25 --> M251["month=01/"]
M241 --> F1["part-0.parquet"]
M242 --> F2["part-0.parquet"]
M251 --> F3["part-0.parquet"]
subgraph lectura["Lectura de datos con glob **"]
direction TB
G["read_parquet('.../**/*.parquet')"]
G --> P1["Detecta year y month<br/>como columnas virtuales"]
G --> P2["Pruning: si filtro<br/>year=2025, no abre 2024/"]
end
La clave: el nombre de la carpeta es el dato. year=2024 no es un directorio, es la columna year con valor 2024.
Leer particiones¶
-- Con '**' en el glob, la detección de particiones es automática
SELECT year, month, count(*) AS filas, sum(importe) AS total
FROM read_parquet('datos/ventas_hive/**/*.parquet')
GROUP BY ALL
ORDER BY ALL;
A tener en cuenta
Con ** en la ruta, DuckDB 2.0 activa hive partitioning por defecto. Añadir hive_partitioning = true es explícito y no molesta; olvidarlo cuando usas * en un solo nivel falla.
Saber de qué fichero viene cada fila¶
SELECT year, month, id, filename
FROM read_parquet('datos/ventas_hive/**/*.parquet', filename = true)
LIMIT 5;
_metadata.file_path no está disponible en esta compilación
En DuckDB 2.0.0 la forma estable es la opción filename = true, que añade una columna filename. La ruta tipo _metadata.file_path provoca Binder Error: Referenced table "_metadata" not found. Usa filename.
Escribir particiones¶
COPY (SELECT * FROM ventas)
TO 'datos/ventas_hive'
(FORMAT PARQUET, PARTITION_BY (year, month), OVERWRITE_OR_IGNORE, COMPRESSION zstd);
DuckDB crea el árbol year=.../month=.../ y escribe un fichero por combinación. Es el patrón lakehouse mínimo viable, sin Spark ni nada parecido.
Inspeccionar el fichero, no solo los datos¶
SELECT row_group_id, row_group_num_rows, path_in_schema, total_compressed_size
FROM parquet_metadata('datos/ventas_hive/year=2025/month=01/part-0.parquet');
SELECT num_rows, format_version, created_by
FROM parquet_file_metadata('datos/ventas_hive/year=2025/month=01/part-0.parquet');
Esto es oro para depurar por qué una consulta va lenta: si hay un row group gigante o una columna inflada, lo ves aquí.
¿Por qué va rápido: el pushdown?¶
sequenceDiagram
participant U as Usuario
participant D as DuckDB
participant P as Fichero Parquet
U->>D: SELECT producto, sum(importe) WHERE year=2025
D->>D: Lee metadatos (min/max por row group)
D->>P: Descarta row groups de 2024
D->>P: Lee SOLO columnas producto e importe
P-->>D: Páginas comprimidas necesarias
D->>D: Agrega (posible spilling a disco)
D-->>U: Resultado
Tres capas de descarte: partición (carpeta), row group (estadísticas) y columna (layout columnar). Si tu consulta no mejora, revisa el orden: filtrar por la columna particionada es lo más barato.
La 2.0 añade E/S asíncrona¶
La concurrencia de E/S se separa de la de procesamiento: mientras se procesan páginas ya recibidas, hay más lecturas en vuelo. En pruebas sobre Overture Maps en S3, la mejora medida fue de ~3× frente a la 1.5. En local se nota menos; en almacenamiento de objetos es donde muerde.
Ejercicios¶
- Crea un dataset particionado por
yearymonthcon 2 años × 2 meses y compruébalo confilename = true. - Consulta solo
year=2025y explica cómo saber que DuckDB no leyó 2024 (pista:EXPLAIN ANALYZE). - Compara tamaño en disco con
COMPRESSION snappyyzstd. - ¿Por qué
SELECT *sobre un Parquet de 200 columnas es peor que seleccionar 3, si el formato es columnar?
Soluciones en cuadernos/99_soluciones.ipynb.