Column Chunks
Что такое Column Chunk
Внутри каждого row group данные организованы по колонкам. Column Chunk — это непрерывная последовательность байт на диске, содержащая все значения одной колонки для одного row group.
Если таблица имеет 5 колонок и 3 row group, файл содержит 5 × 3 = 15 column chunks. Каждый chunk хранится контигуально — без прерываний другими данными.
Row Group 0 (строки 0–999 999)
Каждый column chunk знает свой byte offset в файле. Ридер использует эти offsets из метаданных для seek + read точно нужных байтов — без сканирования файла целиком.
Projection Pushdown
Projection pushdown — это оптимизация, при которой ридер читает только те column chunks, которые нужны запросу. Если запрос SELECT salary, department FROM ... — ридер открывает файл, читает метаданные, находит offsets для salary и department, и читает только эти два column chunk. Остальные 3 колонки не трогаются.
Без pushdown (все колонки)
С pushdown (2 колонки)
В этом примере projection pushdown экономит 88% I/O. На реальных таблицах с 50–200 колонками, где запрос читает 3–5 колонок, экономия достигает 95–99%.
Метаданные Column Chunk
Каждый column chunk описан структурой ColumnMetaData в Thrift-сериализованном футере файла:
ColumnMetaData {
type: INT64 // физический тип данных
encodings: [RLE_DICTIONARY, PLAIN, RLE] // используемые кодировки
path_in_schema: ["salary"] // путь колонки в схеме
codec: SNAPPY // алгоритм компрессии
num_values: 1000000 // количество значений (включая nulls)
total_uncompressed_size: 8000000 // размер до компрессии
total_compressed_size: 6000000 // размер после компрессии
data_page_offset: 55574532 // offset первой data page
dictionary_page_offset: 55574500 // offset dictionary page (если есть)
statistics: { // min/max для predicate pushdown
min: 35000
max: 250000
null_count: 42
}
}
Вся эта информация доступна без чтения самих данных — она хранится в File Metadata в конце файла. Движки используют statistics для predicate pushdown: если запрос WHERE salary > 300000, а max salary в этом chunk = 250 000 — весь chunk пропускается.
Contiguous Storage и I/O
Column chunk хранится контигуально — все его страницы идут подряд без прерываний. Это критично для производительности:
Parquet: контигуальный chunk
1 seek + 1 sequential read
Один вызов read() читает весь column chunk за один проход — последовательный I/O, максимальная пропускная способностьOS readahead эффективен Да
OS readahead работает оптимально — следующие байты в буфере уже предзагружены. Минимальная латентность.Гипотетический: interleaved
N seeks + N random reads
Данные разных колонок чередуются — нужны seek операции между каждым фрагментом. Случайный I/O — медленно.Readahead бесполезен Нет
Readahead бесполезен — после каждой страницы идут чужие данные, которые нужно пропустить.На HDD разница между sequential и random I/O — 100x. На SSD — 5–10x. На сетевом хранилище (S3, GCS) — каждый seek это новый HTTP Range request, что добавляет 50–200 мс латентности.
Column Pruning vs Row Group Pruning
Parquet предлагает два уровня pruning, которые работают вместе:
| Уровень | Что пропускается | На основе чего | Экономия |
|---|---|---|---|
| Column pruning | Ненужные column chunks | Список колонок в запросе | 50–99% (зависит от ширины таблицы) |
| Row group pruning | Ненужные row groups | min/max статистики | 0–99% (зависит от сортировки данных) |
При запросе SELECT salary FROM employees WHERE year = 2024:
- Column pruning: читаем только
salaryиyear(2 из 50 колонок → 96% экономии) - Row group pruning: пропускаем row groups где
yearmax < 2024 или min > 2024
Ключевые выводы
- Column Chunk — непрерывный блок байтов на диске, содержащий все значения одной колонки для одного row group
- Projection pushdown позволяет читать только нужные column chunks — экономия 50–99% I/O на широких таблицах
- Контигуальное хранение обеспечивает sequential I/O и эффективный OS readahead
- ColumnMetaData в футере файла содержит offset, размер, кодировку и статистики каждого chunk — доступно без чтения данных
- Column pruning + Row group pruning комбинируются для минимизации I/O по обоим измерениям