Cómo paginar un archivo Parquet en DuckDB sin perder filas ni velocidad

Fuentes: Paging Through a Parquet File in DuckDB: file_row_number or OFFSET?

Cuando una API tiene que devolver el contenido de un archivo Parquet muy grande y las respuestas están limitadas en tamaño —por ejemplo, 6 MB en AWS Lambda o 32 MiB en Cloud Run—, lo habitual es paginar con LIMIT y OFFSET. Pero esa forma de escribir la consulta es la equivocada. DuckDB ofrece la opción file_row_number al leer un Parquet, que permite filtrar por un rango de posiciones físicas en lugar de saltar OFFSET. En pruebas con un archivo de 20 millones de filas distribuidas en 163 row groups, la versión con rango de filas terminó 2,53 veces más rápido que OFFSET en las 37 ejecuciones realizadas. La clave es que DuckDB puede deducir, a partir del footer del archivo, qué row groups contienen el rango pedido y omitir el resto sin descomprimirlo. Si el archivo está escrito como un único row group, la ventaja se reduce a 1,25x y el rendimiento general cae entre tres y cuatro veces, por lo que conviene revisar la estructura con parquet_metadata antes de optimizar nada. Sorprendentemente, DuckDB reescribe internamente muchas consultas con OFFSET hacia un esquema basado en file_row_number y hace un semi-join con los datos, de modo que paginar con OFFSET no es cuadrático, como suele temerse. La reescritura, sin embargo, tiene un umbral: solo se activa con LIMIT de un millón de filas o menos, controlado por LIMIT_MAX_VAL y late_materialization_max_rows. Superado ese límite, la consulta pasa a descomprimir todo lo anterior a la página. El problema más grave de LIMIT/OFFSET no es la velocidad: al no incluir ORDER BY, no garantiza qué filas se devuelven. DuckDB conserva por defecto el orden de inserción, pero esa opción puede desactivarse. Con varios hilos y esa configuración desactivada, paginar todo el archivo devolvió exactamente 20 millones de filas, pero con 6,1 millones ausentes y hasta 4,9 millones duplicadas, hasta cinco copias de una misma fila. Un simple conteo no detecta el fallo. La recomendación práctica es escribir siempre el rango con file_row_number y no depender de una reescritura interna del optimizador que no se ve en el texto de la consulta.