ClickHouse — колоночная OLAP-СУБД из Яндекса (Open Source с 2016). В 2026 стала стандартом для real-time analytics в РФ: Avito, Wildberries, Ozon, Тинькофф, ВКонтакте, Yandex.Cloud — все используют CH в продакшне для дашбордов с миллиардами строк и sub-second latency.
Этот гайд — практические паттерны: какие ENGINE когда выбирать, как избежать ловушек FINAL, зачем нужны materialized views и ARRAY JOIN. С примерами SQL и подводными камнями.
Что узнаешь
- Архитектура ClickHouse — почему она быстрее Postgres в 100×
- Семейство MergeTree: ReplacingMergeTree, AggregatingMergeTree, SummingMergeTree
- Когда НЕ использовать FINAL (и что вместо)
- Materialized views как INSERT-trigger
- ARRAY JOIN, ARRAY функции, nested types
- Partitioning, ORDER BY (primary key), skip indexes
Шаг 1: Чем ClickHouse отличается от Postgres
Postgres (row-store): оптимизирован для OLTP — частые UPDATE/DELETE, транзакции, JOIN сложных моделей. Аналитический SUM по 1B строкам — медленный.
ClickHouse (column-store): оптимизирован для OLAP — append-mostly, агрегации, фильтрация. Аналитический SUM по 1B строкам — секунды. Цена: нет UPDATE (только ALTER TABLE ... DELETE через mutations), нет полноценных JOIN, eventual consistency.
Postgres: запрос -> читает все колонки строки -> фильтрует
CH: запрос -> читает ТОЛЬКО колонки в SELECT -> компрессия x10
Когда CH:
- Дашборды на сырых events (10M-1B+ строк)
- Логи, телеметрия, time-series
- Маркетинговые когорты, retention curves
- A/B-тесты на больших выборках
Когда НЕ CH:
- OLTP (заказы, балансы, корзины) — там Postgres
- Графовые запросы — там Neo4j или AGE
- Текстовый поиск — там Elasticsearch
См. подборку 16 ClickHouse-вопросов с собесов — все паттерны ниже спрашивают на собесах в Яндекс/Авито.
Шаг 2: ENGINE = MergeTree — основа
Каждая таблица в CH должна иметь ENGINE. MergeTree — базовый.
create table events (
event_time DateTime,
event_date Date materialized toDate(event_time),
user_id UInt64,
event_type LowCardinality(String),
amount Float64
)
engine = MergeTree
partition by toYYYYMM(event_date)
order by (event_date, user_id, event_time)
ttl event_date + interval 90 day;
Что здесь важно:
PARTITION BY— куски данных по месяцам. Запрос сWHERE event_date = '2024-01-15'читает только январь.ORDER BY— primary key (sparse index). Запросы сWHERE event_date AND user_idмолниеносны.LowCardinality(String)— для колонок с <10k уникальных значений (status, country, plan) экономит память ×5.MATERIALIZED— вычисляемая колонка, хранится физически.TTL— авто-удаление старых данных.
Шаг 3: ReplacingMergeTree — дедупликация
Если события могут дублироваться (at-least-once delivery):
create table user_state (
user_id UInt64,
subscription_tier String,
updated_at DateTime,
version UInt64
)
engine = ReplacingMergeTree(version)
order by user_id;
ReplacingMergeTree(version) — при merge оставляет запись с максимальным version для одинакового ORDER BY ключа.
Подвох: дедупликация происходит в фоне. Между merge'ами в таблице могут быть дубли.
-- ❌ Дубли возможны:
select * from user_state where user_id = 123;
-- 123 | basic | 2024-01-01 | 1
-- 123 | pro | 2024-01-15 | 2
-- ✅ Правильный read (без FINAL):
select user_id, argMax(subscription_tier, version) as tier
from user_state
where user_id = 123
group by user_id;
Шаг 4: Почему FINAL это анти-паттерн (и что вместо)
SELECT ... FINAL форсит дедупликацию на лету. Удобно, но очень дорого — игнорирует индексы, scan всех parts:
-- ❌ Медленно (FINAL):
select user_id, balance from accounts final where user_id = 123;
-- Reads all parts, sort, merge
-- ✅ Быстро (argMax):
select user_id, argMax(balance, version) as balance
from accounts
where user_id = 123
group by user_id;
Правило: FINAL — только в ad-hoc запросах для проверки. В production-дашбордах и пайплайнах — argMax/anyLast + GROUP BY.
Шаг 5: Materialized views — INSERT-triggers
CH materialized views работают как INSERT-триггеры: при вставке в source таблицу CH автоматически выполняет SELECT и пишет в destination.
-- Source: сырые events
create table events (
event_time DateTime,
user_id UInt64,
amount Float64
) engine = MergeTree order by event_time;
-- Destination: дневные агрегаты
create table daily_revenue (
day Date,
revenue AggregateFunction(sum, Float64)
)
engine = AggregatingMergeTree
order by day;
-- MV — триггер при INSERT в events
create materialized view daily_revenue_mv to daily_revenue as
select
toDate(event_time) as day,
sumState(amount) as revenue
from events
group by day;
-- INSERT'ы в events автоматически апдейтят daily_revenue
insert into events values ('2024-01-15 10:00:00', 1, 100.0);
-- Read с финализацией:
select day, sumMerge(revenue) as total_revenue
from daily_revenue
group by day;
Подводные:
- MV видит только новый блок данных, не вся table. Поэтому backfill для existing data нужен явный INSERT.
- Не работает с JOIN'ами на большие правые таблицы (broadcast).
- Window functions в MV — почти не работает (CH видит только текущий блок).
Шаг 6: ARRAY JOIN — разворачивание массивов
CH нативно поддерживает массивы как колонки. Полезно для tags, properties, mtu paths.
create table user_events (
user_id UInt64,
event_time DateTime,
tags Array(String),
properties Map(String, String)
) engine = MergeTree order by event_time;
insert into user_events values
(1, '2024-01-15 10:00:00', ['organic', 'mobile', 'ru'], {'plan': 'pro', 'utm': 'fb'}),
(2, '2024-01-15 10:01:00', ['paid', 'desktop', 'us'], {'plan': 'basic'});
-- ARRAY JOIN — каждый элемент массива становится строкой:
select user_id, tag
from user_events
array join tags as tag;
-- 1 | organic
-- 1 | mobile
-- 1 | ru
-- 2 | paid
-- ...
-- Через колонку map:
select user_id, properties['plan'] as plan
from user_events;
ARRAY функции (hasAny, arrayMap, arrayFilter, arrayReduce, arraySort) — стандарт для event tracking.
Шаг 7: Skip indexes — точечная оптимизация
Когда primary key (ORDER BY) не покрывает все фильтры, добавь skip index:
alter table events add index idx_user_id user_id type minmax granularity 4;
alter table events add index idx_country country type set(100) granularity 4;
alter table events add index idx_tag_bf tags type bloom_filter(0.01) granularity 4;
Типы:
minmax— для numeric range queriesset(N)— для low-cardinality (<100 unique)bloom_filter— для approximate substring matching
Skip indexes не альтернатива ORDER BY — они только помогают пропускать data parts, которые гарантированно не содержат данные.
Шаг 8: Distributed для шардинга
В кластере используется Distributed ENGINE для прозрачной агрегации:
-- На каждом шарде:
create table events_local on cluster '{cluster}' (...)
engine = ReplicatedMergeTree(...)
partition by ... order by ...;
-- На координаторе:
create table events on cluster '{cluster}' as events_local
engine = Distributed('{cluster}', currentDatabase(), events_local, sipHash64(user_id));
Запросы к events идут на все шарды параллельно, агрегируются на координаторе.
FAQ
CH vs PostgreSQL?
CH для analytics (OLAP), Postgres для transactions (OLTP). Это разные tools.
Можно UPDATE в ClickHouse?
Через ALTER TABLE ... UPDATE ... — это mutation, тяжёлая операция. В production используют CollapsingMergeTree или ReplacingMergeTree для логических updates.
Что быстрее: ClickHouse или BigQuery?
Зависит от паттерна. CH быстрее на per-query latency (sub-second). BQ удобнее на ad-hoc больших аналитических задачах через serverless. CH дешевле при предсказуемой нагрузке.
Стоит ли учить ClickHouse в 2026?
Да. В РФ — must-have для аналитика Senior+. Каждый второй стартап и middle компания используют. На собесах спрашивают в 80% случаев для DA/PA.
Где практиковать ClickHouse?
Есть ClickHouse Play — публичный playground. Или подними локально через Docker: docker run -d --name ch -p 8123:8123 clickhouse/clickhouse-server.
dbt работает с CH?
Да, есть dbt-clickhouse — full support. См. наш dbt-гайд.
Что дальше
- 🎯 SQL-тренажёр — 480+ задач включая 18 ClickHouse-specific (windows, arrays, agg states)
- 🧠 3000+ вопросов с собесов — 16 верифицированных CH-вопросов из реальных собесов
- 📚 dbt практический гайд — оркестрация трансформаций на CH
- ⚡ PostgreSQL vs ClickHouse — сравнение и benchmarks
- 🌊 Apache Iceberg vs Delta vs Hudi — современный lakehouse под CH
- 🔍 Airflow DAG patterns — расписание ClickHouse-пайплайнов
Источники
- clickhouse.com/docs — официальная документация
- ClickHouse Blog — best practices от core team
- GitHub: ClickHouse/ClickHouse — open source
ClickHouse окупается на втором дашборде. Open SQL-тренажёр — решай CH-задачи и через месяц будешь писать MV и аналитические DAU/Retention queries без поиска по докам.