Learning Platform
Глоссарий Troubleshooting
Урок 09.01 · 30 мин
Продвинутый
Information TheoryShannon EntropyConditional EntropyColumnar EncodingCompressibility

Теория информации и кодирование

В Модуле 01 мы рассмотрели четыре базовых кодировки — dictionary, RLE, delta, bit-packing — и увидели, как они комбинируются. Но почему они работают? Почему словарь для строки department даёт 44x сжатие, а для UUID — ничего? Почему delta на timestamps сжимает до почти 0 бит, а на случайных числах бесполезен?

Ответ — в теории информации. Она задаёт точный математический предел: насколько данные можно сжать, не теряя информации. Кодировки работают, потому что колоночные данные содержат мало информации относительно их размера — и теория объясняет, сколько именно и почему.

Энтропия Шеннона: минимум бит на значение

Клод Шеннон (1948) определил энтропию — среднее количество бит, необходимое для кодирования одного значения из источника:

Энтропия Шеннона: H(X)
H(X) = -Σ p(x) · log₂(p(x))
p(x)Вероятность каждого уникального значения в колонке. Для department с 4 значениями: p(Engineering)=0.5, p(Sales)=0.3, p(HR)=0.15, p(Marketing)=0.05
log₂(p(x))Информация (surprisal) одного значения — сколько бит нужно для его идентификации. Редкое значение (p=0.01) даёт log₂(0.01) ≈ 6.6 бит, частое (p=0.5) — всего 1 бит.
H(X)Средневзвешенное — нижняя граница бит на значение для любого кодирования без потерь. Ни одна кодировка не может сжать лучше, чем H(X) бит на значение в среднем.

Ключевое свойство: H(X) — абсолютный нижний предел. Никакое кодирование без потерь (lossless) не может в среднем использовать меньше, чем H(X) бит на значение. Это доказанная теорема, а не эмпирическое наблюдение.

Энтропия на реальных колонках

Посмотрим, как энтропия выглядит для типичных колонок аналитической базы данных:

Спектр энтропии: от нулевой до максимальной
0 бит
is_active
status
country
timestamp
user_id
UUID
128 бит
is_active (boolean)Два значения, 95% = true. H = -0.95·log₂(0.95) - 0.05·log₂(0.05) ≈ 0.29 бит. Можно представить 3.4 значения одним битом в среднем.
status (3 значения)active=60%, inactive=30%, deleted=10%. H = -(0.6·log₂0.6 + 0.3·log₂0.3 + 0.1·log₂0.1) ≈ 1.30 бит. Далеко от 2 бит (максимум для 3 значений).
country (200 стран)Неравномерное распределение: US=30%, DE=15%, ... H ≈ 5.2 бит вместо теоретических log₂(200) = 7.6 бит. Skewed-распределение снижает энтропию.
timestamp (секунды)Сами значения большие (64 бита), но delta-энтропия — энтропия разностей — мала. Если шаг ≈ 1 сек с ±5 вариацией: H(Δ) ≈ 3–4 бит/значение.
user_id (1M уникальных)Равномерно распределённые ID: H = log₂(1M) ≈ 20 бит. Dictionary бесполезен — словарь размером с данные. Но если отсортирован — delta помогает.
UUID (128-бит random)Каждый UUID уникален, криптографически случаен. H = 128 бит — максимум. Ни одна кодировка не поможет. Только general-purpose компрессия может найти паттерны в бинарном представлении.

Два наблюдения из этого спектра:

  1. Большинство аналитических колонок имеют низкую энтропию — status, country, department, boolean-флаги — далеко от теоретического максимума.
  2. Энтропия зависит от модели данных — для timestamps прямая энтропия высока (64 бита), но delta-энтропия (разность соседних значений) — всего 3–4 бит. Кодировка, которая использует правильную модель, приближается к пределу.

Почему колоночные данные так хорошо сжимаются

Колоночное хранение создаёт три свойства, которые радикально снижают энтропию:

Три источника низкой энтропии в колонках

Гомогенность типов

Все значения в колонке имеют один тип (INT64, STRING, FLOAT). Это исключает overhead смешанных типов и позволяет type-specific кодирование: delta для чисел, dictionary для строк.
СледствиеЗнание типа → можно выбрать оптимальную кодировку. Строки → dictionary. Монотонные числа → delta. Boolean → 1-bit RLE. Это невозможно в строковом хранилище.

Локальность значений

Соседние значения в колонке — из одного поля разных строк. Часто похожи: даты идут подряд, статусы повторяются, ID растут монотонно.
СледствиеМалые дельты, длинные серии одинаковых значений, узкий диапазон — всё это снижает условную энтропию H(Xᵢ|Xᵢ₋₁) относительно безусловной H(X).

Кластеризация при записи

Сортировка данных при записи (partition columns, sort keys) группирует одинаковые значения. Кластеризация превращает случайные данные в длинные серии.
СледствиеОтсортированная колонка status: все 'active' подряд, затем 'inactive' подряд. RLE сжимает 500K одинаковых значений до одной пары (value, count). H → 0 для длинных серий.

Именно поэтому row-ориентированные форматы (JSON, CSV) сжимаются хуже: значения разных типов чередуются, разрушая все три свойства. В строке {"id": 42, "name": "Alice", "active": true} — int, string и bool перемешаны. Колоночное хранение отделяет поток id (низкая delta-энтропия) от потока name (dictionary-friendly) от потока active (1-бит RLE).

Условная энтропия и модель кодирования

Ключевая идея: кодировка определяет модель данных, а модель определяет условную энтропию.

Рассмотрим колонку timestamps: [1704067200, 1704067260, 1704067320, ...]

Модели кодирования: одни и те же данные — разная энтропия
Без модели (PLAIN)Каждое значение — 64-бит число. Энтропия = log₂(диапазон). Для unix-timestamp с диапазоном ~10⁹: H ≈ 30 бит. Хранить 64 бита при 30 битах информации — 2x overhead.
Delta-модельМодель: Xᵢ ≈ Xᵢ₋₁ + c. Дельты малы: 60 ± несколько секунд. H(Δ) ≈ 3–4 бит. Delta encoding приближает кодирование к этому пределу.
Delta-of-deltaМодель: Xᵢ ≈ Xᵢ₋₁ + Δ̄ (постоянный шаг). Дельты дельт — вариации вокруг нуля: ±0, ±1, ±2. H(ΔΔ) ≈ 1–2 бит. ORC RLEv2 Delta использует эту модель.

Каждая кодировка — это неявная модель данных:

  • PLAIN — «значения независимы» (никакой модели)
  • Dictionary — «значения из малого алфавита» (модель: p(x) для каждого уникального значения)
  • Delta — «соседние значения похожи» (модель: Xᵢ = Xᵢ₋₁ + Δ)
  • RLE — «одинаковые значения идут подряд» (модель: серии)
  • FOR — «все значения в узком диапазоне» (модель: base + offset)

Кодировка, чья модель лучше соответствует данным, достигает более низкой условной энтропии — и, следовательно, лучшего сжатия.

Кодирование vs компрессия: два слоя сжатия

Почему нужны оба — и кодирование, и компрессия? Потому что они работают с разными видами избыточности:

Кодирование устраняет структурную избыточность, компрессия — остаточную

Raw данные: 1M строк × 11 байт = 11 MB

Колонка department: 1M значений из 4 уникальных. 64-бит указатели. Размер: ~11 MB. Энтропия: 1.75 бит/значение (при равных вероятностях 4 значений).
Encoding: Dictionary + Bit-Packing

Закодировано: 250 KB (44x сжатие)

Dictionary (4 значения = 2 бита/индекс) + bit-packing: 1M × 2 бит = 250 KB + ~40 байт словарь. Достигнуто ~2 бит/значение — близко к энтропии H = 1.75 бит.
Compression: Zstd

Сжато: ~160 KB (1.6x дополнительно)

Zstd находит оставшиеся паттерны в потоке bit-packed индексов: неравномерность p(0)=0.5, p(1)=0.3, p(2)=0.15, p(3)=0.05. Дополнительное 1.5x сжатие.
Без кодирования, только ZstdZstd на сырых строках: ~1.5 MB (7x). Компрессия находит повторяющиеся строки, но не использует знание о типе. 7x вместо 70x.
Кодирование + ZstdDictionary + bit-packing + Zstd: ~160 KB (70x). Кодирование убрало type-specific избыточность, Zstd добрал остаток. Разница: 10x лучше, чем одна компрессия.

Принцип: кодирование эксплуатирует знание о типе данных (dictionary для строк, delta для чисел, RLE для серий). Компрессия обрабатывает остаток — паттерны в потоке байтов, которые кодировка не устранила. Вместе они дают мультипликативный эффект.

Предсказание степени сжатия

Зная энтропию колонки, можно оценить достижимое сжатие:

Формула предсказания: compression ratio ≈ raw_bits / H(model)
КолонкаТип колонки и характеристика распределения значений
Raw битовСколько бит занимает одно значение без кодирования (стандартное хранение)
H (модель)Энтропия при оптимальной модели кодирования — нижний теоретический предел бит на значение
RatioПредсказанный compression ratio = raw / H. На практике overhead кодировки добавляет 20–50%
РеальныйТипичный реальный compression ratio с учётом overhead кодирования, заголовков и не идеальных реализаций
boolean (95% true)Boolean хранится как 1 бит, но энтропия всего 0.29 бит. RLE на отсортированных данных приближается к пределу.
1 битСтандартное хранение boolean: 1 бит на значение (8 бит в строковом формате, 1 бит в колоночном)
0.29 битH = -0.95·log₂(0.95) - 0.05·log₂(0.05) = 0.286 бит/значение
3.4x1 / 0.29 = 3.4x. На практике RLE даёт 3–4x для unsorted, 100x+ для sorted (длинные серии одинаковых значений).
3–100xЗависит от сортировки. Unsorted: ~3x (RLE на случайных сериях). Sorted: 100x+ (одна длинная серия true, затем false).
status (3 значения)Enum-колонка: active/inactive/deleted. Dictionary encoding → 2-bit индексы.
64+ битВ строковом формате: ~8 символов × 8 бит = 64 бит. В колоночном — указатель + строка.
1.3 битНеравномерное распределение: 60%/30%/10%. H = 1.30 бит. Далеко от log₂(3)=1.58 — skew снижает энтропию.
49x64 / 1.3 = 49x. Dictionary + bit-packing приближается к этому. С учётом overhead — 20–40x.
20–40xDictionary encoding (2 бита) + Zstd на bit-packed потоке. Дополнительное сжатие от неравномерности.
timestamp (1с шаг)Unix-timestamp с шагом ~1 секунда ± jitter. Delta-энтропия мала.
64 битint64: 64 бита на значение. Все биты используются — числа порядка 10⁹.
3 бит (Δ)Дельты ≈ 60 ± 5. H(Δ) ≈ 3 бит. Delta-of-delta: ΔΔ ≈ ±5, H(ΔΔ) ≈ 2 бит.
21x64 / 3 = 21x. DELTA_BINARY_PACKED + bit-packing. Parquet достигает 15–20x на реальных timestamps.
15–20xParquet DELTA_BINARY_PACKED + Zstd на реальных event timestamps. Близко к теоретическому пределу.
UUID (v4, random)128-бит криптографически случайных данных. Максимальная энтропия — кодирование бесполезно.
128 бит16 байт = 128 бит. Строковое представление (36 символов) — ещё больше.
122 битUUID v4: 6 фиксированных бит (версия + вариант), 122 случайных. H ≈ 122 бит — почти максимум.
1.05x128 / 122 = 1.05x. Сжатие менее 5%. PLAIN — единственный разумный вариант.
~1.0xНа практике: ни dictionary, ни delta, ни RLE не помогают. Zstd даёт 1.0–1.1x. UUID не сжимаются.

Практическое правило: если H(model) ≪ raw_bits — кодирование даст значительный выигрыш. Если энтропия близка к raw_bits (UUID, random float) — кодирование бесполезно, и даже компрессия поможет минимально.

Эмпирическая энтропия: как измерить

Теоретическая энтропия требует знания распределения p(x). На практике используют эмпирическую энтропию — оценку по реальным данным:

Алгоритм оценки энтропии колонки

Шаг 1: Взять N значений колонки

Берём N значений колонки: все или репрезентативную выборку. Для колонки в миллиард строк — первые 10,000–100,000 достаточно.

Шаг 2: Посчитать частоту каждого уникального значения

Для каждого уникального значения xᵢ считаем количество вхождений count(xᵢ). Оценка вероятности: p̂(xᵢ) = count(xᵢ) / N.

Шаг 3: Вычислить H = -Σ p̂(x) · log₂(p̂(x))

H(X) = -Σ p̂(xᵢ) · log₂(p̂(xᵢ)). Сумма по всем уникальным значениям. Результат — биты на значение.

Шаг 4: Сравнить H для разных моделей (raw, delta, dictionary)

Для delta-модели: вычислить дельты Δᵢ = xᵢ - xᵢ₋₁, затем энтропию дельт H(Δ). Для FOR: вычислить offsets = xᵢ - min(x), затем H(offsets). Выбрать модель с наименьшей H.

Именно этот алгоритм (в упрощённом виде) используют writers форматов. Parquet writer пробует dictionary для каждой колонки и переключается на PLAIN, если словарь превышает порог. DuckDB анализирует первые значения колонки, чтобы выбрать между Dictionary, RLE, BitPacking, FOR и Constant. BtrBlocks сэмплирует данные и перебирает 8 кодировок, выбирая каскад с наименьшим размером.

Пределы: когда кодирование бесполезно

Теория информации говорит чётко: нельзя сжать без потерь ниже энтропии. Но есть нюанс — нельзя без потерь. Для аналитики есть обходные пути:

Когда стандартное кодирование не помогает
UUID v4122 бит истинной случайности. Решение: не сжимать UUID, а хранить как FIXED_LEN_BYTE_ARRAY (16 байт) вместо строки (36 символов). Экономия 56% без кодирования.
Random float64Случайные double — 52 бит мантиссы ≈ случайные. Решение: BYTE_STREAM_SPLIT группирует похожие байты экспоненты, улучшая компрессию на 15–30%.
JSON-blob колонкаПроизвольный JSON — высокая энтропия как строки. Решение: shredding — разбить JSON на отдельные колонки. Каждая колонка сжимается отдельно. Iceberg/Delta Lake поддерживают column mapping для nested data.
Free-text колонкаТекст на естественном языке: H ≈ 1–2 бит/символ (Shannon, 1951). FSST (Module 09) достигает 3x сжатия с random access. LZ4/Zstd: 3–5x но без random access.

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

  1. Энтропия Шеннона H(X) — абсолютный нижний предел сжатия без потерь. Никакая кодировка не может в среднем использовать менее H(X) бит на значение.
  2. Большинство аналитических колонок имеют низкую энтропию: boolean — 0.3 бит, enum — 1.3 бит, timestamps (delta) — 3 бит. Это объясняет, почему колоночные форматы сжимают 10–100x.
  3. Кодировка определяет модель данных. Delta-модель даёт H(Δ) ≪ H(X). Dictionary-модель даёт H(index) ≪ H(string). Правильный выбор модели — ключ к приближению к теоретическому пределу.
  4. Кодирование устраняет type-specific избыточность, компрессия обрабатывает остаточную. Вместе дают мультипликативный эффект: 70x вместо 7x.
  5. Три свойства колонок снижают энтропию: гомогенность типов, локальность значений, кластеризация при записи. Row-ориентированные форматы разрушают все три.
  6. Если H ≈ raw_bits (UUID, random float) — кодирование бесполезно. Используйте компактное представление (binary вместо string) и general-purpose компрессию.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 4. Колонка status содержит 4 уникальных значения с равной вероятностью. Чему равна энтропия Шеннона H(X)?

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

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

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

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