Parquet en producción: cómo leer, escribir y optimizar datos para análisis
Parquet es más que un simple formato de almacenamiento; es una herramienta de optimización arquitectónica. Su estructura columnar, el filtrado incorporado en tiempo de lectura, la codificación por diccionario y los metadatos de grupos de filas reducen la entrada/salida en órdenes de magnitud y disminuyen el tamaño de los datos hasta 140 veces en comparación con CSV. Para ingenieros de datos y analistas, esto significa no solo ahorrar espacio en disco, sino también reducir la latencia en consultas interactivas, disminuir la presión sobre la memoria y lograr un rendimiento predecible. Este artículo ofrece orientación práctica para trabajar con Parquet en Python a nivel intermedio o avanzado, desde la lectura básica mediante pandas hasta el control manual sobre el esquema, la partición y la compresión.
Por qué Parquet está reemplazando a CSV en las pipelines de datos modernas
CSV es un formato basado en texto sin semántica: cada fila se procesa como un bloque único, los delimitadores se analizan al vuelo y los tipos se infieren heurísticamente. Parquet, en cambio, es binario, estrictamente tipado y columnar, con metadatos integrados. Las principales diferencias incluyen:
- Almacenamiento columnar: cada columna se almacena por separado, lo que permite leer solo los campos necesarios (por ejemplo,
yearyindicator_value) mientras se ignoran el resto, sin tener que analizar toda la fila. - Grupos de filas: los datos se dividen en bloques (por defecto ~128 MB), cada uno con estadísticas (
min,max,null_count). Al filtraryear == 2023, el motor saltea grupos de filas enteros sin cargarlos en memoria. - Codificación por diccionario: para columnas de texto con alta repetición (regiones, categorías), Parquet almacena automáticamente o de forma explícita un diccionario de valores únicos y referencias a ellos; así, en lugar de “Región de Moscú” × 500.000 registros, utiliza el código
1× 500.000. - Seguridad de tipos: en CSV, un
NULLen un campo numérico convierte toda la columna enfloat64; en Parquet, los tipos se definen rigurosamente en el esquema y se validan al escribir.
La diferencia en la práctica es enorme: un conjunto de datos de incidencia de cáncer de 576 MB se reduce a 4 MB en Parquet (relación de compresión ×144), y el tiempo para leer un subconjunto filtrado pasa de 12–18 segundos a menos de 300 ms.
Lectura: de pandas al control fino con pyarrow
Lectura básica con pandas
Para empezar rápidamente, pd.read_parquet() es suficiente:
import pandas as pd
df = pd.read_parquet(
"data_zis_109_v20260126.parquet",
columns=["object_name", "year", "indicator_value"],
filters=[("year", "=", 2023)]
)
Acceso directo con pyarrow: control sobre el esquema y los metadatos
Cuando se necesita un control preciso, se usa pyarrow.parquet directamente:
import pyarrow.parquet as pq
# Sin cargar los datos—solo el esquema y los metadatos
schema = pq.read_schema("data_zis_109_v20260126.parquet")
meta = pq.read_metadata("data_zis_109_v20260126.parquet")
print(f"Filas: {meta.num_rows:,}, Columnas: {meta.num_columns}")
# Lectura con filtrado y selección de columnas
table = pq.read_table(
"data_zis_109_v20260126.parquet",
columns=["object_name", "object_oktmo", "year", "indicator_value"],
filters=[("year", "=", 2023)]
)
df = table.to_pandas() # Convertir a pandas si es necesario
pf = pq.ParquetFile("data_zis_109_v20260126.parquet")
for batch in pf.iter_batches(batch_size=100_000):
df_batch = batch.to_pandas()
# Procesar el lote
Escritura: de lo sencillo a la serialización de grado productivo
Desafíos de la serialización automática
df.to_parquet() funciona, pero oculta riesgos:
- Determinación implícita de tipos:
int64con NULL →float64,datetime64→string. - Falta de codificación por diccionario para columnas de texto de alta cardinalidad.
- Tamaño inapropiado de los grupos de filas (por defecto 64 MB), lo que reduce la eficiencia del filtrado.
Esquema explícito y codificación controlada
import pyarrow as pa
import pyarrow.parquet as pq
schema = pa.schema([
pa.field("object_name", pa.string()),
pa.field("object_oktmo", pa.dictionary(pa.int32(), pa.string())),
pa.field("year", pa.int32()),
pa.field("indicator_value", pa.float64())
])
table = pa.Table.from_pandas(df, schema=schema, preserve_index=False)
pq.write_table(
table,
"data_zis_new_version.parquet",
compression="zstd",
row_group_size=500_000,
version="2.6",
write_statistics=True
)
Particionamiento y trabajo con conjuntos de archivos
Si los datos están lógicamente divididos (por año, región), se usa write_to_dataset():
pq.write_to_dataset(
table,
root_path="data_zis_partitioned/",
partition_cols=["year"]
)
import pyarrow.dataset as ds
dataset = ds.dataset("data_zis_partitioned/", format="parquet", partitioning="hive")
table = dataset.to_table(filter=ds.field("year") == 2023)
Qué importa
- Parquet no sustituye a una base de datos: es ideal para escenarios de análisis de solo escritura y alta carga de lectura, pero no admite transacciones, UPDATE ni índices.
- La codificación por diccionario ofrece máximos beneficios cuando la cardinalidad está por debajo de 10⁴ y la repetición supera el 30%. Para columnas con valores únicos (UUIDs, marcas temporales), es inútil.
- El tamaño del grupo de filas afecta la precisión del filtrado: demasiado pequeño genera metadatos excesivos; demasiado grande hace que saltar datos innecesarios sea poco eficiente. El rango óptimo es de 100–500 mil filas.
- Las estadísticas (
write_statistics=True) son obligatorias para producción. Sin ellas, el filtrado actúa como un escaneo completo. - Siempre valide el esquema antes de escribir:
pa.Table.from_pandas(df, schema=schema)lanzará un error si los tipos no coinciden—esto protege contra la corrupción silenciosa.
— Editorial Team
Aún no hay comentarios.