Apache Parquet: El Formato de Archivo Columnar para Analítica de Big Data
¿Qué es Apache Parquet?
Apache Parquet es un formato de archivo de almacenamiento columnar de código abierto diseñado para consultas analíticas eficientes sobre grandes conjuntos de datos. Desarrollado originalmente por Twitter y Cloudera en 2013 y ahora un proyecto de nivel superior de la Apache Software Foundation, Parquet se ha convertido en el estándar de facto para lagos de datos, analítica en la nube (AWS Athena, Google BigQuery, Azure Synapse) y el ecosistema de ingeniería de datos (Apache Spark, Dask, pandas, DuckDB, Polars).
Los archivos llevan la extensión .parquet y son autodescriptivos — la información de esquema está incrustada en el pie de página de metadatos del archivo.
Almacenamiento por Filas vs. Almacenamiento por Columnas
La idea fundamental detrás de Parquet es la diferencia entre cómo se almacenan los datos versus cómo se consultan:
Orientado a filas (CSV, JSON, Avro): todas las columnas de la fila 1 se almacenan juntas, luego todas las columnas de la fila 2, etc.
[id=1, nombre="Alicia", edad=30, salario=75000]
[id=2, nombre="Roberto", edad=25, salario=62000]
Orientado a columnas (Parquet): todos los valores de la columna 1 se almacenan juntos, luego todos los de la columna 2, etc.
id: [1, 2, 3, ...]
nombre: ["Alicia", "Roberto", "Carol", ...]
edad: [30, 25, 35, ...]
salario: [75000, 62000, 91000, ...]
Para consultas analíticas como SELECT AVG(salario) WHERE edad > 28, un formato columnar lee solo las columnas salario y edad — omitiendo completamente id y nombre. En una tabla con 1.000 columnas, esto puede reducir la E/S en un 99%.
Estructura de un Archivo Parquet
Archivo:
├── Grupo de Filas 0 ← lote de filas (predeterminado: 128 MB)
│ ├── Trozo de Columna: id
│ ├── Trozo de Columna: nombre
│ ├── Trozo de Columna: edad
│ └── Trozo de Columna: salario
├── Grupo de Filas 1
│ └── ...
└── Pie de Página (Footer) ← metadatos (esquema, estadísticas, desplazamientos)
├── Esquema ← nombres de campos, tipos, nulabilidad
└── Metadatos de Grupos de Filas ← estadísticas min/max por columna
Grupos de Filas (Row Groups)
Los grupos de filas particionan el archivo en cortes horizontales (128 MB por defecto). Permiten:
- Filtrado de grupos de filas: el pie de página contiene estadísticas min/max por columna por grupo de filas. Si
salario_min=50000ysalario_max=70000para un grupo de filas, una consultaWHERE salario > 80000omite todo el grupo de filas sin leerlo. - Paralelismo: diferentes ejecutores en Spark/Dask pueden leer diferentes grupos de filas concurrentemente.
Tipos de Datos y Codificaciones
Tipos físicos (primitivos de almacenamiento en disco):
BOOLEAN,INT32,INT64,FLOAT,DOUBLE,BYTE_ARRAY,FIXED_LEN_BYTE_ARRAY
Tipos lógicos (anotaciones semánticas):
STRING(UTF-8 BYTE_ARRAY)DATE(INT32, días desde la época)TIMESTAMP(INT64, microsegundos desde la época)DECIMAL(con precisión+escala)LIST,MAP,STRUCT— tipos anidadosUUID(FIXED_LEN_BYTE_ARRAY de 16 bytes)
Codificaciones (por trozo de columna, elegidas adaptivamente):
- Diccionario: valores reemplazados por índices de diccionario (excelente para cadenas de baja cardinalidad)
- RLE: para secuencias repetitivas
- Delta: almacena diferencias entre enteros consecutivos (marcas de tiempo, IDs ordenados)
Compresión
Parquet soporta compresión por trozo de columna:
SNAPPY— muy rápido, ~60–70% del tamaño originalGZIP— archivos más pequeños (~50%), descompresión más lentaZSTD— excelente ratio (~50%) con descompresión rápida — recomendado para nuevos archivosLZ4— descompresión más rápida, ratio moderado
Trabajar con Parquet en Python
pandas + PyArrow
import pandas as pd
df = pd.DataFrame({
'id': [1, 2, 3, 4, 5],
'nombre': ['Alicia', 'Roberto', 'Carol', 'David', 'Eva'],
'edad': [30, 25, 35, 28, 32],
'salario': [75000.0, 62000.0, 91000.0, 58000.0, 84000.0]
})
# Escribir en Parquet (compresión ZSTD)
df.to_parquet('empleados.parquet', compression='zstd', index=False)
# Leer de vuelta
df2 = pd.read_parquet('empleados.parquet')
# Leer solo columnas específicas (omite otras columnas completamente)
df_salario = pd.read_parquet('empleados.parquet', columns=['nombre', 'salario'])
# Leer con filtro de filas (inserción de predicados en las estadísticas de Parquet)
df_senior = pd.read_parquet(
'empleados.parquet',
filters=[('edad', '>', 28)]
)
DuckDB (SQL sobre archivos Parquet)
-- Consultar Parquet directamente con SQL (sin cargar en memoria)
SELECT departamento, AVG(salario) as salario_medio
FROM read_parquet('empleados_*.parquet')
WHERE edad > 25
GROUP BY departamento
ORDER BY salario_medio DESC;
import duckdb
conn = duckdb.connect()
resultado = conn.execute("""
SELECT anio, SUM(ingresos) as ingresos_totales
FROM read_parquet('ventas/**.parquet')
GROUP BY anio
ORDER BY anio
""").df()
Polars (soporte nativo rápido de Parquet)
import polars as pl
# Escaneo lazy — no se cargan datos hasta collect()
lazy = pl.scan_parquet('gran_conjunto_datos/*.parquet')
resultado = (
lazy
.filter(pl.col('edad') > 28)
.select(['nombre', 'salario', 'departamento'])
.group_by('departamento')
.agg(pl.col('salario').mean().alias('salario_medio'))
.collect()
)
Particionamiento Hive
Los grandes conjuntos de datos Parquet se organizan típicamente usando particionamiento al estilo Hive — una convención de nomenclatura de directorios donde los valores de partición se codifican en los componentes de la ruta:
datos_ventas/
├── anio=2022/
│ ├── mes=01/
│ │ └── datos.parquet
│ └── mes=02/
│ └── datos.parquet
└── anio=2023/
└── mes=01/
└── datos.parquet
Los motores de consulta (Spark, Athena, DuckDB, Polars) reconocen automáticamente esta estructura y omiten particiones irrelevantes.
Parquet en el Ecosistema de Datos
- Apache Spark: soporte nativo de Parquet; formato predeterminado para DataFrames escritos en almacenamiento
- AWS Athena: SQL sobre archivos Parquet en S3; el coste es proporcional a los bytes escaneados
- Google BigQuery: soporta tablas Parquet externas
- Apache Hudi / Delta Lake / Apache Iceberg: los tres formatos de tabla ACID usan Parquet como formato de archivo de datos subyacente
- MLflow: almacena artefactos de modelos junto a datos de entrenamiento en Parquet
Consejos Prácticos
- Usa compresión ZSTD — mejor ratio que Snappy con descompresión lo suficientemente rápida
- Tamaño del grupo de filas 128–256 MB es el punto óptimo
- Particiona por columnas filtradas frecuentemente (fecha, región, categoría) — evita particionar por columnas de muy alta cardinalidad como user_id
- Usa
write_statistics=True(predeterminado) para almacenar estadísticas min/max — esencial para la inserción de predicados - Ordena antes de escribir: si los datos están ordenados por una columna filtrada frecuentemente, la inserción de predicados es dramáticamente más efectiva
- Evita archivos pequeños: muchos archivos Parquet pequeños (< 32 MB) perjudican el rendimiento de las consultas
El diseño columnar de Parquet, la inserción de predicados, la compresión por columna y el rico sistema de tipos lo convierten en el formato universal para cargas de trabajo analíticas a cualquier escala.
Conversiones relacionadas
Conversiones frecuentes del catálogo: