ClickHouse — это база данных, которую делали для одной задачи: считать аналитику по огромным объёмам данных очень быстро. Агрегатный запрос по миллиарду строк — секунды на одном сервере. Чтобы понять, откуда такая скорость и где у ClickHouse ограничения, нужно разобраться, как он устроен внутри.
Почему обычные базы медленно считают аналитику
Представьте таблицу заказов с тридцатью колонками: id, дата, покупатель, статус, сумма, адрес, промокод, и так далее. В PostgreSQL каждая строка хранится целиком — все тридцать колонок лежат рядом на диске.
Когда вам нужен конкретный заказ — это удобно: одно чтение, и всё готово. Но когда нужно посчитать среднюю сумму заказов по месяцам за два года, PostgreSQL всё равно читает все тридцать колонок каждой строки — хотя реально нужны только две: дата и сумма. Лишние двадцать восемь колонок едут с диска в память впустую.
На таблице в миллион строк это терпимо. На таблице в миллиард — это катастрофа.
Колоночное хранение
ClickHouse хранит данные иначе: все значения одной колонки лежат вместе, в отдельном файле. Все amount — в одном месте, все status — в другом, все даты — в третьем.
Тот же аналитический запрос по двум годам теперь читает ровно два файла из тридцати. Остальные двадцать восемь даже не открываются.
Второй эффект — сжатие. Файл с колонкой status содержит миллион значений из пяти вариантов («новый», «оплачен», «доставлен», «отменён», «возврат»). Такие однородные данные сжимаются в десятки раз лучше, чем пёстрые строки с разнородными полями. Типичный коэффициент сжатия в ClickHouse — 5–20x против 2–3x в строчных базах. Меньше байтов читается с диска — запрос быстрее.
ClickHouse и PostgreSQL: разные задачи
Это не конкуренты, а инструменты для разных ситуаций.
| PostgreSQL | ClickHouse | |
|---|---|---|
| Типичный запрос | «заказ №42», «обнови статус» | «выручка по категориям за год» |
| Чтение | одна строка по индексу | миллионы строк, агрегация |
| Запись | частые мелкие вставки и обновления | редкие большие порции данных |
| Транзакции | полный ACID | не поддерживаются |
| UPDATE/DELETE | дёшево | перезапись кусков таблицы |
| JOIN | любые таблицы | ограниченно, денормализация |
ClickHouse хорошо дополняет PostgreSQL: основные данные и все изменения живут в PG, а поток событий и историческая аналитика уходят в ClickHouse. По таблицам в десятки миллионов строк ClickHouse строит отчёты за секунды там, где PostgreSQL работал бы минуты.
Когда ClickHouse нужен: аналитические запросы GROUP BY по большим таблицам тормозят рабочую базу; дашборды строятся долго; реплика PG под отчёты всё равно не справляется.
Когда пока не нужен: данных меньше десяти миллионов строк (PostgreSQL с нормальными индексами справится сам), нужны частые обновления текущего состояния, некому заниматься ещё одним хранилищем.
MergeTree: как ClickHouse хранит данные
MergeTree — основной механизм хранения в ClickHouse, и понимать его важно: от него зависит, как правильно создавать таблицы и почему некоторые подходы к вставке данных ломают всё.
CREATE TABLE order_events (
event_time DateTime,
order_id UUID,
customer_id UInt64,
event_type LowCardinality(String),
amount Decimal(18, 2)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (event_type, event_time);
Каждая вставка данных создаёт на диске part — неизменяемый отсортированный кусок. Изменить part нельзя. Вместо этого ClickHouse в фоне постепенно сливает мелкие parts в крупные — отсюда и название MergeTree («дерево слияний»).
Из этой механики следуют три важных правила.
Вставлять нужно большими порциями. Тысяча вставок по одной строке — тысяча parts. Фоновые слияния не успевают, таблица начинает отвечать ошибкой TOO_MANY_PARTS. Нормальный режим — порции в десятки или сотни тысяч строк раз в несколько секунд.
Данные по природе неизменяемы. ALTER TABLE ... UPDATE или DELETE — это мутация: фоновое переписывание целых parts. Для редких операций, например удаления данных пользователя по запросу, это допустимо. Как повседневная практика — разрушительно.
Свежие данные не сразу «причёсаны». Движки, которые дедуплицируют или агрегируют строки при слиянии, делают это не сразу после вставки, а когда слияние произойдёт. До того момента в таблице могут сосуществовать несколько версий одной записи.
ORDER BY и как ClickHouse ищет данные
ORDER BY в MergeTree — это не про сортировку результата запроса. Это физический порядок данных внутри part-ов, и это главное архитектурное решение при создании таблицы.
По этому порядку ClickHouse строит разреженный первичный индекс: одна засечка не на каждую строку, а на блок из 8192 строк (это называется гранула). Когда приходит запрос с фильтром WHERE event_type = 'order_paid', ClickHouse смотрит в индекс и читает только те гранулы, где такие значения могут встречаться — остальное пропускается.
Запрос WHERE order_id = '...' по таблице выше прочитает всю таблицу: order_id не входит в ORDER BY, индекс не помогает, ClickHouse вынужден просмотреть всё.
Два важных следствия:
Первичный ключ в ClickHouse — не про уникальность. Это навигатор по гранулам. Дубликаты по ключу совершенно законны, уникальность — забота приложения.
Точечный поиск — не сильная сторона ClickHouse. «Найти одну запись по id» минимум прочитает гранулу из 8192 строк, а без попадания в индекс — всю таблицу. За точечными чтениями — в PostgreSQL.
PARTITION BY — второй уровень отсечения. Партиции по месяцам позволяют запросу за май вообще не трогать данные других месяцев. Удалить старые данные тоже просто: DROP PARTITION. Частая ошибка — делать слишком мелкие партиции, например по дням за несколько лет: получается тысячи партиций, и производительность падает. Месяц — разумный стандарт.
Специализированные движки таблиц
Все «специальные» движки — это тот же MergeTree с дополнительной логикой, которая срабатывает в момент слияния parts.
ReplacingMergeTree — при слиянии оставляет только последнюю версию строки с одинаковым ORDER BY-ключом. Удобен, когда нужно хранить «текущее состояние» сущности: вставляется новая версия, старая исчезнет при очередном слиянии. До слияния обе версии существуют одновременно — читать «чисто» можно через модификатор FINAL или функцию argMax.
SummingMergeTree — при слиянии суммирует числовые колонки строк с одинаковым ключом. Подходит для счётчиков и предагрегированных метрик.
AggregatingMergeTree — то же, но для любых агрегатных состояний (uniqState, quantileState). Основа материализованных представлений.
CollapsingMergeTree — «отмена» строки парной записью со знаком −1. Используется в потоках изменений, где нужно вычитание.
Replicated*MergeTree — любой из перечисленных плюс репликация.
Выбор движка — часть дизайна схемы. События, которые только добавляются — просто MergeTree. Снимки сущностей с обновлением — ReplacingMergeTree. Готовые агрегаты под дашборды — SummingMergeTree или AggregatingMergeTree под материализованное представление.
Чего в ClickHouse нет
Это полезно знать заранее, чтобы не обнаружить на рабочей системе.
Транзакций. Атомарна вставка одного пакета в одну партицию. «Перевести деньги между счетами» здесь не реализуют.
Дешёвых UPDATE и DELETE. Только мутации (перезапись parts) или специальные движки.
Уникальных ограничений и внешних ключей. Целостность данных — ответственность того, кто их поставляет.
Быстрого поиска по произвольному ключу. Гранула — минимальная единица чтения; сценарии типа «ключ–значение» не для ClickHouse.
Частых мелких вставок. Нужны порции данных, иначе TOO_MANY_PARTS.
Каждый из этих пунктов — сознательный выбор: именно отказавшись от этих возможностей, ClickHouse агрегирует миллиарды строк там, где PostgreSQL работал бы минуты.
Коротко
- ClickHouse хранит данные по колонкам, а не по строкам — аналитические запросы читают только нужные колонки, остальные пропускаются.
- Сжатие однородных данных в колонках — 5–20x; меньше I/O — быстрее запросы.
- Каждая вставка создаёт part; ClickHouse сливает parts в фоне. Вставлять нужно большими порциями — тысяча одиночных INSERT убивает производительность.
ORDER BY— это физический порядок данных и основа разреженного индекса. Запросы с фильтром поORDER BY-полям быстрые; по остальным полям — скан таблицы.PARTITION BYпозволяет отсекать целые партиции по времени; месяц — разумный стандарт.- Первичный ключ в ClickHouse — навигатор, не ограничение уникальности.
- ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree — специализации MergeTree с логикой агрегации при слиянии.
- Нет транзакций, нет дешёвых UPDATE/DELETE, нет быстрого точечного поиска — это осознанные компромиссы ради аналитической скорости.
Что почитать дальше
- Моделирование и запросы — ORDER BY на практике, типы, материализованные представления, антипаттерны.
- Интеграция из приложения — драйвер, порции данных, пайплайн из PostgreSQL и Kafka.
- Эксплуатация — репликация, шардинг, TTL, мониторинг.