Колоночное хранение: главная идея аналитики

Один разворот данных «на бок» — и аналитика ускоряется в десятки раз.

Колоночное хранение (column store) — способ, при котором значения одной колонки лежат на диске подряд, отдельно от других колонок.

Разворачиваем таблицу на бок

Логически таблица — это сетка. Но физически на диск её можно разложить двумя способами. Строковый (как в PostgreSQL):

row 1: [1, "Аня",  300]
row 2: [2, "Боря", 500]
row 3: [3, "Вика", 200]

Колоночный (как в ClickHouse) — каждая колонка отдельным файлом:

id:     [1, 2, 3]
name:   ["Аня", "Боря", "Вика"]
amount: [300, 500, 200]

Теперь запрос «сумма по amount» читает только файл amount[300, 500, 200] — и больше ничего. Файлы id и name вообще не трогаются.

Два бонуса колоночного формата

1. Читаем только нужные колонки

Если запрос использует 2 колонки из 30, мы читаем 2 файла из 30. Объём чтения падает пропорционально — отсюда и ускорение в десятки раз по сравнению со строковым store.

2. Сжатие работает в разы лучше

Когда рядом лежат однотипные значения одной колонки, в них масса повторов и закономерностей. Колонка browser — это сотни одинаковых строк "Chrome"; колонка дат — почти упорядоченные числа. Алгоритмы сжатия (LZ4, ZSTD) и приёмы вроде delta-кодирования «схлопывают» такие данные в разы сильнее, чем разнородную строку целиком. Меньше данных на диске — ещё меньше чтения.

Маленькая иллюстрация сжатия

Покажем идею «дельты» (храним не сами числа, а разницы) на чистом Python:

timestamps = [1000, 1001, 1003, 1004, 1006]
deltas = [timestamps[0]]
for i in range(1, len(timestamps)):
    deltas.append(timestamps[i] - timestamps[i-1])
print("Исходные:", timestamps)
print("Дельты:  ", deltas)

Вывод:

Исходные: [1000, 1001, 1003, 1004, 1006]
Дельты:   [1000, 1, 2, 1, 2]

Маленькие дельты вроде [1, 2, 1, 2] жмутся гораздо сильнее больших исходных чисел. В колонке такие закономерности встречаются постоянно — поэтому колоночные базы хранят данные компактно.

Как работает под капотом

ClickHouse режет каждую колонку на блоки-«гранулы» (по умолчанию 8192 значения), сжимает их по отдельности и хранит к ним засечки (marks), чтобы прыгать сразу к нужному блоку. Векторизованные функции обрабатывают распакованную колонку пачками — процессор работает на полную.

Частые ошибки

  • Ждать ускорения при SELECT *. Если выбрать все колонки, колоночный формат теряет преимущество — читается всё. Берите только нужные поля.
  • Точечные правки строк. Колоночное хранение плохо подходит для UPDATE одной строки — данные размазаны по файлам колонок.

Итоги

  • Колонки хранятся отдельными файлами — читаем только нужные.
  • Однотипные данные в колонке сжимаются в разы лучше.
  • Колоночный формат — фундамент скорости ClickHouse.
Проверьте себя
1. Главное преимущество колоночного хранения для аналитики — это…
AВозможность читать только нужные колонки, не трогая остальные
BУскорение точечных UPDATE отдельных строк
CОтказ от SQL в пользу своего языка
DПолное отсутствие необходимости в диске
2. Почему данные в колоночном формате сжимаются лучше?
AКолонки всегда короче строк
BРядом лежат однотипные похожие значения с повторами и закономерностями
CСжатие применяется только к числам
DКолоночные базы не хранят текст
3. Какой запрос сводит на нет преимущество колоночного хранения?
ASELECT с двумя колонками
BSELECT * (все колонки)
CSELECT с GROUP BY
DSELECT одной колонки