Learning Platform
Глоссарий Troubleshooting
Урок 03.04 · 35 мин
Продвинутый
ParquetEncodingsRLEDictionary EncodingDelta EncodingBYTE_STREAM_SPLIT

Encodings в Parquet

Кодирование — до компрессии

Encoding и compression — разные уровни. Encoding трансформирует данные в более компактное представление, используя знание о типе и паттернах данных. Compression (Snappy, Zstd, LZ4) — универсальный побайтовый алгоритм, который работает поверх закодированных данных.

Порядок: Raw values → Encoding → Compression → Disk

Parquet определяет 8 кодировок. Две — deprecated. Шесть — активно используются.

Кодировки Parquet: дерево выбора

Активные кодировки

PLAIN (enum 0)Сырые значения без трансформации. Little-endian для числовых типов, length-prefixed для BYTE_ARRAY. Fallback для всех типов.
RLE_DICTIONARY (enum 8)Словарь уникальных значений + RLE/Bit-Packing индексов. Заменил PLAIN_DICTIONARY (enum 2). Лучшая кодировка для низкой кардинальности.
RLE (enum 3)Run-Length Encoding / Bit-Packing Hybrid. Используется для BOOLEAN и для кодирования dictionary indices, repetition/definition levels.
DELTA_BINARY_PACKED (enum 5)Хранит дельты между последовательными значениями вместо абсолютных. Идеально для отсортированных INT32/INT64 (timestamps, IDs).
DELTA_LENGTH_BYTE_ARRAY (enum 6)Длины строк кодируются delta-кодированием, затем конкатенированные байты. Выгодно для строк одинаковой длины.
BYTE_STREAM_SPLIT (enum 9)Разделяет байты float/double по позициям: все первые байты, все вторые, ... Улучшает компрессию IEEE 754 float.

Deprecated

PLAIN_DICTIONARY (enum 2)Устаревший. Заменён на RLE_DICTIONARY (enum 8). Всё ещё встречается в старых файлах — ридеры обязаны поддерживать.
BIT_PACKED (enum 4)Устаревший. Поглощён RLE (enum 3), который включает bit-packing как подрежим. Не используется в новых файлах.

Ещё существует

DELTA_BYTE_ARRAY (enum 7)Incremental encoding: хранит общий префикс + суффикс для каждой строки. Подходит для BYTE_ARRAY и FIXED_LEN_BYTE_ARRAY. Эффективен для sorted строк с общим префиксом.

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 байт)
RLE/Bit-Packing Hybrid: пример

Кодирование dictionary indices

Исходные индексы (dictionary)1 000 000 индексов в словарь из 4 уникальных значений. Каждый индекс: 0, 1, 2 или 3. Нужно 2 бита на значение.
Гибридный кодировщик анализирует паттерны
RLE-секцияПервые 5 нулей → (value=0, count=5). Затем три единицы → (value=1, count=3). Компактно для повторов.
Bit-Pack секцияЗначения 2, 3 не повторяются подряд — упакованы по 2 бита. 8 значений → 2 байта. Компактно для смешанных данных.

Parquet автоматически переключается между RLE и bit-packing: серия из 8+ одинаковых значений → RLE; смешанные значения → bit-packing с минимальным bitwidth.

Dictionary Encoding (RLE_DICTIONARY)

Самая эффективная кодировка для колонок с повторяющимися значениями. Работает в два этапа:

  1. Dictionary Page: массив уникальных значений, каждому присвоен индекс
  2. Data Pages: массивы индексов, закодированные RLE/Bit-Packing Hybrid
Dictionary Encoding: пошаговый процесс

Вход: 1M значений department (10 уникальных)

1 миллион строковых значений колонки department. 10 уникальных department, каждый повторяется ~100 000 раз.
Шаг 1: построение словаря
Dictionary Page (PLAIN encoding)10 строк × ~12 байт = ~120 байт. Каждое уникальное значение получает индекс 0–9.
Шаг 2: замена значений на индексы
IndicesКаждое значение заменено на 4-битный индекс (0–9 требует 4 бита). 1M × 4 бита = 500 KB до RLE.
Шаг 3: RLE/Bit-Packing индексов

Data Pages: RLE-encoded indices → ~200 KB

RLE сжимает серии повторяющихся индексов. Финальный размер: ~120 байт словарь + ~200 KB индексы = ~200 KB вместо ~12 MB.

Результат: 12 MB → 200 KB (~60x сжатие) — ещё до применения Snappy/Zstd.

WARNING

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:

  1. Вычислить дельты между соседними значениями
  2. Разбить дельты на блоки (по 128 значений)
  3. В каждом блоке: вычислить min_delta, вычесть из всех дельт
  4. Закодировать оставшиеся значения 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 находят повторяющиеся паттерны и сжимают эффективнее.

NOTE

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 или PLAINSplit улучшает компрессию
BOOLEANЛюбаяRLEДва значения → 1 бит
TIP

PyArrow позволяет явно задать кодировку через use_dictionary=False, column_encoding и data_page_version='2.0'. Но в 90% случаев дефолт writer оптимален — ручная настройка нужна только для бенчмарков или специфических workloads.

Ключевые выводы

  1. Encoding трансформирует данные до компрессии — использует знание о типе и паттернах. Compression — универсальный побайтовый алгоритм поверх.
  2. RLE_DICTIONARY (enum 8) — основная кодировка для строк/enum с низкой кардинальностью: словарь + RLE индексов, сжатие 20–100x
  3. RLE/Bit-Packing Hybrid — гибрид для BOOLEAN и dictionary indices: серии → RLE, смешанные → bit-packing с минимальным bitwidth
  4. DELTA_BINARY_PACKED — для отсортированных числовых: дельты между значениями → bit-packing с минимальным bitwidth. Для монотонных timestamp — почти 0 бит на значение.
  5. BYTE_STREAM_SPLIT — для float/double: разделение по байтовым позициям улучшает Snappy/Zstd компрессию на 15–30%
  6. Writer выбирает кодировку автоматически; ручная настройка — для бенчмарков и edge cases
ClickHouse: codecs (Delta, DoubleDelta, Gorilla, T64) ClickHouse: tuning compression и codecs

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Колонка temperature содержит float64-значения: 20.1, 20.3, 20.2, 20.4, 20.1, 20.5... Данные с IoT-датчиков, высокая корреляция между соседними значениями. Какая кодировка оптимальна?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7