Row Groups
Структура Parquet-файла
Parquet-файл — это не просто «колонки на диске». Это строго определённый бинарный формат с магическими байтами, row groups и Thrift-сериализованным футером.
Каждый файл начинается и заканчивается 4-байтовой сигнатурой PAR1 (hex: 50 41 52 31). Между ними — row groups с данными и метаданные в конце файла.
PAR1 (4 байта — magic number)
4 байта: 0x50 0x41 0x52 0x31. Magic number для идентификации формата. Без него ридер отклонит файл.File Metadata (Thrift-encoded)
Thrift-сериализованная структура FileMetaData: версия, схема, row groups, key-value metadata, created_byMetadata Length (4 байта, LE)
4 байта little-endian — длина File Metadata в байтах. Ридер читает последние 8 байт файла: эти 4 + PAR1PAR1 (4 байта — magic number)
Повторение magic number в конце файла. Позволяет читать файл с конца: PAR1 → length → metadata → dataРидер открывает Parquet-файл с конца: читает последние 8 байт (metadata length + PAR1), затем по offset — весь File Metadata. Только после этого он знает схему, количество row groups и расположение каждого column chunk.
Что такое Row Group
Row Group — это горизонтальный срез таблицы. Если файл содержит 10 миллионов строк, а размер row group настроен на 128 MB, файл будет содержать несколько row groups, каждый с частью строк.
Ключевые свойства:
- Каждый row group содержит все колонки для своей порции строк
- Внутри row group данные хранятся по колонкам (column chunks)
- Row group — минимальная единица для параллельного чтения
- Размер по умолчанию: 128 MB (несжатых данных)
Логическая таблица
Parquet row groups
Параллельное чтение
Row groups — это механизм параллелизации. Каждый row group можно читать независимо, потому что он содержит полный набор column chunks для своего диапазона строк.
Spark, Trino, DuckDB распределяют row groups между потоками (или нодами кластера). Больше row groups — больше параллелизма.
Thread 0 → RG 0
Первый поток читает Row Group 0. Доступ к данным последовательный внутри группы — минимум seek операций.Thread 1 → RG 1
Второй поток читает Row Group 1 одновременно. Никаких блокировок — row groups не пересекаются.Thread 2 → RG 2
Третий поток читает Row Group 2. Результаты агрегируются после чтения всех групп.Один файл с 1 row group → один поток. Десять файлов по 1 row group → десять потоков. Это одна из причин, почему Spark рекомендует размер файлов 128 MB–1 GB — чтобы обеспечить достаточный параллелизм.
Размер Row Group: trade-offs
Размер row group — это настраиваемый параметр, и он влияет на три вещи одновременно:
Маленький RG (8–32 MB)
Маленькие row groups (8–32 MB): больше параллелизма, но больше overhead метаданных и хуже компрессия Больше параллелизма
Точнее row group pruning
Больше metadata overhead
Хуже компрессия
Стандартный RG (128 MB)
Средний row group (128 MB) — баланс между параллелизмом, компрессией и overhead. Дефолт в большинстве движков. Хороший баланс
Дефолт Spark / PyArrow
Достаточная компрессия
Умеренный overhead
Большой RG (512 MB+)
Большие row groups (512 MB–1 GB): лучшая компрессия, но меньше параллелизма и грубый pruning Лучшая компрессия
Меньше metadata
Меньше параллелизма
Грубый pruning
Настройки размера в разных инструментах:
| Инструмент | Параметр | Значение по умолчанию |
|---|---|---|
| PyArrow | row_group_size | 64 MB (max_row_group_length) |
| Spark | parquet.block.size | 128 MB |
| DuckDB | row_group_size | 122 880 строк |
| Trino (Hive) | parquet.writer.block-size | 128 MB |
DuckDB задаёт размер row group в строках, а не в байтах. При широких таблицах (сотни колонок) row group из 122 880 строк может оказаться значительно больше 128 MB. Следите за размером файла.
Row Group Pruning
Каждый row group хранит статистики в метаданных: min/max значения для каждой колонки. При запросе WHERE year = 2024 движок проверяет статистики каждого row group и пропускает те, где year не попадает в диапазон min–max.
Это работает аналогично partition pruning, но на уровне файла — без физического партиционирования.
Row Group 0: year min=2020, max=2022 → SKIP +
Row Group 1: year min=2023, max=2024 → READ (может содержать 2024)
Row Group 2: year min=2025, max=2025 → SKIP +
Эффективность pruning зависит от сортировки данных. Если строки отсортированы по year, каждый row group будет содержать узкий диапазон — pruning отсечёт большинство групп. Если данные случайные — каждый row group покрывает весь диапазон, и pruning бесполезен.
Ключевые выводы
- Row Group — горизонтальный срез файла, содержащий все колонки для порции строк
- PAR1 magic number (4 байта) обрамляет файл с обоих концов, ридер начинает чтение с конца
- 128 MB — стандартный размер row group, баланс между параллелизмом и компрессией
- Параллелизм масштабируется с количеством row groups — каждый читается независимо
- Row group pruning по min/max статистикам пропускает целые группы строк без чтения данных