Encodings в Parquet
Кодирование — до компрессии
Encoding и compression — разные уровни. Encoding трансформирует данные в более компактное представление, используя знание о типе и паттернах данных. Compression (Snappy, Zstd, LZ4) — универсальный побайтовый алгоритм, который работает поверх закодированных данных.
Порядок: Raw values → Encoding → Compression → Disk
Parquet определяет 8 кодировок. Две — deprecated. Шесть — активно используются.
Активные кодировки
Deprecated
Ещё существует
RLE / Bit-Packing Hybrid
RLE (Run-Length Encoding) в Parquet — это гибрид двух алгоритмов, переключающийся между ними на лету:
Run-Length Encoding — для длинных серий одинаковых значений:
Вход: 1, 1, 1, 1, 1, 1, 1, 1
RLE: (value=1, count=8) → 2 числа вместо 8
Bit-Packing — для последовательностей разных значений:
Вход: 0, 1, 2, 3, 4, 5, 6, 7 (значения 0–7 требуют 3 бита)
Packed: 8 значений × 3 бита = 24 бита = 3 байта (вместо 32 байт)
Кодирование dictionary indices
Parquet автоматически переключается между RLE и bit-packing: серия из 8+ одинаковых значений → RLE; смешанные значения → bit-packing с минимальным bitwidth.
Dictionary Encoding (RLE_DICTIONARY)
Самая эффективная кодировка для колонок с повторяющимися значениями. Работает в два этапа:
- Dictionary Page: массив уникальных значений, каждому присвоен индекс
- Data Pages: массивы индексов, закодированные RLE/Bit-Packing Hybrid
Вход: 1M значений department (10 уникальных)
1 миллион строковых значений колонки department. 10 уникальных department, каждый повторяется ~100 000 раз.Data Pages: RLE-encoded indices → ~200 KB
RLE сжимает серии повторяющихся индексов. Финальный размер: ~120 байт словарь + ~200 KB индексы = ~200 KB вместо ~12 MB.Результат: 12 MB → 200 KB (~60x сжатие) — ещё до применения Snappy/Zstd.
Writer автоматически отключает dictionary encoding, если словарь превышает dictionary_page_size_limit. Для UUID-колонки (каждое значение уникально) словарь будет размером с сами данные — бесполезно. Writer переключается на PLAIN.
Delta Encoding (DELTA_BINARY_PACKED)
Delta encoding хранит разности между последовательными значениями вместо абсолютных. Идеально для монотонно возрастающих данных: timestamps, auto-increment ID, sorted числовые колонки.
Значения: 1000, 1001, 1002, 1005, 1005, 1010
Дельты: 1000, +1, +1, +3, +0, +5
Дельты обычно маленькие числа → нужно меньше бит → bit-packing сжимает эффективнее.
Алгоритм DELTA_BINARY_PACKED:
- Вычислить дельты между соседними значениями
- Разбить дельты на блоки (по 128 значений)
- В каждом блоке: вычислить
min_delta, вычесть из всех дельт - Закодировать оставшиеся значения bit-packing с минимальным bitwidth
Для отсортированных timestamps с шагом 1 секунда: все дельты = 1, min_delta = 1, остатки = 0 → 0 бит на значение (кроме заголовков блоков).
BYTE_STREAM_SPLIT
Специализированная кодировка для FLOAT и DOUBLE. IEEE 754 числа с плавающей точкой плохо сжимаются обычными кодировками — мантисса выглядит как случайные байты.
BYTE_STREAM_SPLIT разделяет 4-байтовые float по позициям:
Значения: [1.5, 2.7, 3.1] (каждое — 4 байта: b0 b1 b2 b3)
Stream 0: [b0₁, b0₂, b0₃] — все первые байты
Stream 1: [b1₁, b1₂, b1₃] — все вторые байты
Stream 2: [b2₁, b2₂, b2₃] — все третьи байты
Stream 3: [b3₁, b3₂, b3₃] — все четвёртые байты
Байты одной позиции часто похожи (одинаковый порядок экспоненты) → Snappy/Zstd находят повторяющиеся паттерны и сжимают эффективнее.
BYTE_STREAM_SPLIT даёт 15–30% лучшую компрессию для float/double данных по сравнению с PLAIN + Snappy. Для научных данных (сенсоры, координаты, измерения) — значимая экономия на петабайтных датасетах.
Выбор кодировки: кто решает
Writer выбирает кодировку автоматически на основе типа данных и кардинальности:
| Тип данных | Кардинальность | Кодировка | Почему |
|---|---|---|---|
| STRING | Низкая (< 10K уникальных) | RLE_DICTIONARY | Словарь компактен |
| STRING | Высокая (UUID, email) | PLAIN или DELTA_BYTE_ARRAY | Словарь бесполезен |
| INT64 (sorted) | Любая | DELTA_BINARY_PACKED | Дельты маленькие |
| INT64 (random) | Низкая | RLE_DICTIONARY | Повторяющиеся значения |
| INT64 (random) | Высокая | PLAIN | Ни одна кодировка не помогает |
| FLOAT/DOUBLE | Любая | BYTE_STREAM_SPLIT или PLAIN | Split улучшает компрессию |
| BOOLEAN | Любая | RLE | Два значения → 1 бит |
PyArrow позволяет явно задать кодировку через use_dictionary=False, column_encoding и data_page_version='2.0'. Но в 90% случаев дефолт writer оптимален — ручная настройка нужна только для бенчмарков или специфических workloads.
Ключевые выводы
- Encoding трансформирует данные до компрессии — использует знание о типе и паттернах. Compression — универсальный побайтовый алгоритм поверх.
- RLE_DICTIONARY (enum 8) — основная кодировка для строк/enum с низкой кардинальностью: словарь + RLE индексов, сжатие 20–100x
- RLE/Bit-Packing Hybrid — гибрид для BOOLEAN и dictionary indices: серии → RLE, смешанные → bit-packing с минимальным bitwidth
- DELTA_BINARY_PACKED — для отсортированных числовых: дельты между значениями → bit-packing с минимальным bitwidth. Для монотонных timestamp — почти 0 бит на значение.
- BYTE_STREAM_SPLIT — для float/double: разделение по байтовым позициям улучшает Snappy/Zstd компрессию на 15–30%
- Writer выбирает кодировку автоматически; ручная настройка — для бенчмарков и edge cases