Boolean, NULL и вложенные кодировки
Три типа данных, которые кажутся тривиальными — boolean, NULL и nested structures — на практике порождают удивительно разные архитектурные решения в каждом формате. Boolean: 1 бит информации, но хранить можно 5 разными способами. NULL: отсутствие данных, но каждый формат тратит разное количество байт на его представление. Nested: вложенные структуры, которые требуют дополнительных метаданных для восстановления иерархии.
В Модуле 02 мы детально разобрали Dremel-алгоритм для Parquet. В Модуле 03 — потоки PRESENT и Byte RLE в ORC. Теперь — кросс-форматное сравнение: какой формат тратит сколько на boolean, NULL и nested, и почему.
Boolean Encoding: 1 бит данных, N способов хранения
Boolean — простейший тип: true/false, 1 бит информации. Но способ кодирования определяет overhead:
Parquet: RLE для boolean
ORC: Boolean RLE (Byte RLE)
Arrow: Validity Bitmap
DuckDB: адаптивный
Overhead на 1 миллион boolean значений
Парадокс Arrow: для random boolean данных отсутствие компрессии оптимально. RLE/Byte RLE добавляют header overhead при коротких runs. Arrow bitmap — ровно 1 бит/значение без overhead. Но для skewed данных (95% true) — Arrow тратит 125 KB, где Parquet/ORC/DuckDB обходятся 15–20 KB.
NULL Handling: три архитектуры
NULL — не значение, а отсутствие значения. Но каждый формат должен как-то записать, что в позиции N данных нет. Три принципиально разных подхода:
Parquet: Definition Levels
Overhead: 1 бит/значение (RLE для плоских)
ORC: PRESENT Stream
Overhead: 1 бит/значение (Byte RLE)
Arrow: Validity Buffer
Overhead: 1 бит/значение + wasted space на NULL-позициях
Ключевое архитектурное различие
Bytes-per-NULL сравнение
При 90% NULL: Parquet/ORC хранят 0.8 MB, Arrow — 8.1 MB (10x разница). Для sparse колонок (sensor data с частыми пропусками, optional fields в wide tables) это определяет выбор формата для long-term storage. Arrow оптимизирован для in-memory compute (O(1) access), не для storage efficiency.
Nested Data: Dremel vs Recursive Streams
Вложенные данные — самая сложная часть кодирования. Рассмотрим структуру:
message User {
required string name;
optional group address {
required string city;
optional string zip;
}
repeated group phones {
required string number;
optional string label;
}
}
Parquet: Dremel Definition/Repetition Levels
Parquet использует Dremel-алгоритм (Google, 2010), детально разобранный в Модуле 02, Урок 06. Ключевая идея: два дополнительных массива на каждую leaf-колонку — definition level и repetition level:
ORC: Recursive PRESENT + LENGTH Streams
ORC использует другой подход — рекурсивные потоки без глобальных levels:
STRUCT (optional address)
LIST (repeated phones)
Dremel vs Recursive: сравнение
Arrow: Nested через Buffer Architecture
Arrow представляет вложенные данные через composition of array types — каждый уровень имеет свои буферы:
Преимущество Arrow: каждый уровень — самостоятельный array с O(1) random access через offsets. Нет FSM для реконструкции, нет recursive stream scan. Но: больше буферов, больше allocations, и validity bitmaps на каждом уровне (даже если NULL нет).
Ключевые выводы
- Boolean encoding — RLE оптимален для skewed данных (95/5), uncompressed bitmap — для random. Arrow bitmap проигрывает 6–10x на skewed, но оптимален на random (нет header overhead).
- Parquet/ORC хранят только non-null data (sparse) — экономия пропорциональна % NULL. Arrow хранит все позиции (dense) — O(1) access, но 10x overhead на 90%-null колонках.
- Avro union для nullable — 1 byte per value (union discriminator), хуже bitmap по overhead, но проще для streaming decode.
- Dremel (Parquet) кодирует всю вложенность в два integer-массива per leaf: компактно для глубокой вложенности, но сложный FSM для реконструкции. Phantom entries для пустых lists.
- ORC recursive streams — отдельный PRESENT + LENGTH на каждом уровне. Проще для понимания, гранулярный skip по PRESENT, но больше I/O-потоков при глубокой вложенности.
- Arrow nested = composition of typed arrays — каждый уровень самостоятелен с O(1) access. Максимальная скорость compute, но максимальный overhead на storage.