Колоночное хранение: главная идея аналитики
Один разворот данных «на бок» — и аналитика ускоряется в десятки раз.
Колоночное хранение (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.