Справочник ключевых терминов курса Clickhouse.
Базовая единица хранения данных в MergeTree: неизменяемый каталог на диске, содержащий столбцы для одного диапазона вставки. Каждый INSERT создаёт один или несколько партов в зависимости от настроек. Парты объединяются в фоновом режиме процессом слияния (merge), уменьшая общее число файлов и улучшая эффективность поиска.
-- После двух INSERT образуются два отдельных парта:
INSERT INTO hits VALUES (1, '2024-01-01', 'page_view');
INSERT INTO hits VALUES (2, '2024-01-02', 'click');
-- SELECT system.parts показывает оба парта до слияния
SELECT name, rows, active FROM system.parts WHERE table = 'hits';Минимальная единица чтения в MergeTree: набор из index_granularity (по умолчанию 8192) строк внутри одного парта. Разреженный первичный индекс хранит одну запись на гранулу, что позволяет быстро определить диапазон гранул для чтения. Гранулы читаются целиком — нет возможности прочитать отдельную строку без чтения всей гранулы.
-- Настройка размера гранулы при создании таблицы:
CREATE TABLE events (
event_time DateTime,
user_id UInt64,
event_type String
) ENGINE = MergeTree()
ORDER BY (user_id, event_time)
SETTINGS index_granularity = 8192;Бинарный файл рядом с каждым столбцовым файлом парта, связывающий позиции в первичном индексе с физическими смещениями в файлах .bin. Для каждой гранулы .mrk2 хранит: смещение в сжатом блоке, смещение внутри распакованного блока и размер гранулы. Файлы меток позволяют ClickHouse пропускать нерелевантные гранулы, не читая данные столбца.
-- Просмотр структуры парта (информация о метках):
SELECT
column,
marks_bytes,
data_compressed_bytes
FROM system.columns
WHERE table = 'events' AND database = currentDatabase();Первичный индекс MergeTree хранит одно значение ключа на каждые index_granularity строк, а не для каждой строки. Файл primary.idx значительно меньше обычного индекса и полностью помещается в памяти. Поиск по индексу сначала бинарным поиском определяет диапазон гранул, затем соответствующие метки указывают на позиции в файлах данных.
-- Анализ эффективности индекса через EXPLAIN:
EXPLAIN indexes = 1
SELECT count() FROM events
WHERE user_id = 42 AND event_time >= '2024-01-01';
-- Поле 'Granules' показывает, сколько гранул пройденоФоновый процесс ClickHouse, объединяющий несколько партов одного партиционного ключа в один более крупный парт. Слияния уменьшают число файлов, применяют логику движка (например, дедупликацию в ReplacingMergeTree) и улучшают производительность чтения. Частота и приоритеты слияний управляются настройками merge_tree.
-- Принудительное слияние всех партов в таблице:
OPTIMIZE TABLE events FINAL;
-- Проверка активных и неактивных партов:
SELECT name, active, rows
FROM system.parts
WHERE table = 'events'
ORDER BY active DESC, name;Асинхронная операция изменения или удаления данных в MergeTree через перезапись партов. Команды ALTER TABLE ... UPDATE/DELETE создают мутации, которые выполняются в фоне, перезаписывая парты. Мутации тяжелее лёгкого DELETE: они перечитывают весь столбец и создают новые парты, что может временно удвоить дисковое пространство.
-- Запуск мутации UPDATE:
ALTER TABLE events UPDATE event_type = 'click_v2'
WHERE event_type = 'click';
-- Отслеживание статуса мутации:
SELECT command, is_done, latest_failed_part
FROM system.mutations
WHERE table = 'events';Формат хранения парта, при котором каждый столбец сохраняется в отдельные файлы (.bin и .mrk2). Используется для партов больше порога min_bytes_for_wide_part (10 МБ по умолчанию). Широкий формат оптимален для аналитических запросов, читающих несколько столбцов, так как позволяет полностью пропускать нечитаемые столбцы.
-- Настройка порога для широкого формата:
CREATE TABLE events (
event_time DateTime,
user_id UInt64,
payload String
) ENGINE = MergeTree()
ORDER BY event_time
SETTINGS min_bytes_for_wide_part = 10485760;Формат хранения парта, при котором все столбцы упакованы в один общий файл data.bin. Используется для маленьких партов (менее min_bytes_for_wide_part). Компактный формат ускоряет запись и уменьшает накладные расходы файловой системы при большом числе мелких INSERT, но менее эффективен при аналитических запросах.
-- Проверка формата хранения партов:
SELECT name, part_type, rows, bytes_on_disk
FROM system.parts
WHERE table = 'events' AND active = 1
ORDER BY bytes_on_disk;Файл checksums.txt внутри каждого парта содержит хеши (SHA256) всех файлов данного парта. ClickHouse проверяет контрольные суммы при чтении и репликации, чтобы обнаружить повреждение данных. При несоответствии контрольных сумм парт помечается как сломанный и перемещается в detached/.
-- Проверка целостности данных:
CHECK TABLE events;
-- Просмотр сломанных партов:
SELECT name, reason
FROM system.detached_parts
WHERE table = 'events';Процесс записи данных в MergeTree включает несколько этапов: валидацию данных, сортировку строк по первичному ключу, кодирование и сжатие столбцов, запись файлов парта и обновление метаданных. INSERT является синхронным (если async_insert = 0): клиент получает подтверждение только после записи всех файлов на диск.
-- Батчевая вставка с async_insert:
SET async_insert = 1;
SET wait_for_async_insert = 0;
INSERT INTO events VALUES (now(), 42, 'page_view');
-- Данные буферизуются и вставляются пакетами
-- для снижения нагрузки на файловую системуОбёртка типа данных, преобразующая строковые или числовые значения в словарное кодирование. Для столбцов с ограниченным числом уникальных значений (менее 10 000) LowCardinality снижает использование памяти и ускоряет GROUP BY и фильтрацию. Внутри хранится индекс (словарь) + массив позиций, что значительно компактнее повторяющихся строк.
-- Создание таблицы с LowCardinality:
CREATE TABLE events (
event_time DateTime,
user_id UInt64,
event_type LowCardinality(String),
country LowCardinality(String)
) ENGINE = MergeTree()
ORDER BY (event_time, user_id);Обёртка типа данных, добавляющая поддержку значения NULL через отдельный битовый массив. ClickHouse хранит Nullable(T) как два файла: файл данных типа T и файл маски null-значений. Nullable значительно замедляет вычисления — большинство функций возвращают NULL при любом NULL-аргументе, а также блокирует использование столбца в ORDER BY ключе.
-- Избегайте Nullable там, где возможно:
-- Плохо:
CREATE TABLE t (val Nullable(Int64)) ENGINE = MergeTree() ORDER BY tuple();
-- Лучше: использовать значение-часовой
CREATE TABLE t (val Int64 DEFAULT 0) ENGINE = MergeTree() ORDER BY val;Строковый тип фиксированной длины N байт, хранящийся без заголовка длины. FixedString(N) эффективнее String для данных постоянной длины: хешей, UUID, кодов стран. Строки короче N дополняются нулевыми байтами, а строки длиннее N вызывают ошибку вставки. Сравнения работают быстрее, чем у String, из-за предсказуемого размера.
-- Хранение MD5-хешей как FixedString:
CREATE TABLE url_visits (
url_hash FixedString(16), -- MD5 как 16 байт
user_id UInt64,
visit_time DateTime
) ENGINE = MergeTree()
ORDER BY (url_hash, visit_time);Тип временной метки с точностью до субсекундного уровня (точность задаётся параметром scale: 0–9). DateTime64(3) хранит миллисекунды, DateTime64(9) — наносекунды. Внутри хранится как Int64 (количество единиц с эпохи Unix), что обеспечивает высокую скорость сравнений. Требует указания часового пояса при создании для корректных timezone-операций.
-- Таблица событий с наносекундной точностью:
CREATE TABLE telemetry (
event_ns DateTime64(9, 'UTC'),
sensor_id UInt32,
value Float64
) ENGINE = MergeTree()
ORDER BY (sensor_id, event_ns);Тип данных для хранения промежуточных состояний агрегатных функций в бинарном формате. Используется совместно с AggregatingMergeTree и комбинаторами -State/-Merge для реализации инкрементальных предварительных агрегаций. Значение типа AggregateFunction нельзя прочитать напрямую — его нужно финализировать через функции вида uniqMerge(), sumMerge().
-- Хранение состояния uniq() для pre-aggregation:
CREATE TABLE daily_uniq (
date Date,
country LowCardinality(String),
unique_users AggregateFunction(uniq, UInt64)
) ENGINE = AggregatingMergeTree()
ORDER BY (date, country);Тип данных для хранения массивов структур, фактически реализованный как набор параллельных массивов. Nested(col1 Type1, col2 Type2) разворачивается в столбцы Array(Type1) и Array(Type2) с одинаковой длиной. Nested полезен для событий с переменным числом полей, но имеет ограничения: не поддерживает вложенность глубже одного уровня и требует осторожности при JOIN.
-- Nested для хранения параметров событий:
CREATE TABLE events (
event_time DateTime,
user_id UInt64,
params Nested(
key String,
value String
)
) ENGINE = MergeTree()
ORDER BY (user_id, event_time);
-- Запрос: arrayFirst с условием по params.keyПеречисляемые типы данных с фиксированным набором строковых значений, хранимых как числа (Int8 или Int16 соответственно). Enum8 поддерживает до 255 значений, Enum16 — до 65535. Занимают минимальный объём памяти при хранении статусов, категорий и других ограниченных наборов. При необходимости расширить список значений требуется ALTER TABLE.
-- Enum для статуса заказа:
CREATE TABLE orders (
order_id UInt64,
status Enum8('pending'=1, 'confirmed'=2, 'shipped'=3, 'delivered'=4, 'cancelled'=5),
created_at DateTime
) ENGINE = MergeTree()
ORDER BY (created_at, order_id);Упрощённый вариант AggregateFunction для агрегаций, где промежуточное состояние совпадает с итоговым значением (sum, min, max, any). В отличие от AggregateFunction не требует сериализации состояния: значение хранится как обычный скалярный тип. SimpleAggregateFunction занимает меньше места и работает быстрее для простых накопительных метрик.
-- SummingMergeTree с SimpleAggregateFunction:
CREATE TABLE revenue_daily (
date Date,
product_id UInt32,
total_revenue SimpleAggregateFunction(sum, Decimal(18, 2))
) ENGINE = AggregatingMergeTree()
ORDER BY (date, product_id);Основной и наиболее универсальный движок хранения ClickHouse для OLAP-нагрузок. MergeTree сортирует данные по ключу ORDER BY, строит разреженный первичный индекс, поддерживает партиционирование и TTL. Является базовым классом для всего семейства движков: ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree, CollapsingMergeTree и их реплицированных вариантов.
CREATE TABLE page_views (
view_date Date,
user_id UInt64,
page_url String,
duration_ms UInt32
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(view_date)
ORDER BY (user_id, view_date)
TTL view_date + INTERVAL 1 YEAR;Движок семейства MergeTree, который при слиянии оставляет только последнюю (или максимальную по версии) строку для каждого уникального ключа ORDER BY. Дедупликация происходит не при INSERT, а только при слиянии партов — до слияния дубликаты могут быть видны в SELECT. Для гарантированного результата необходимо использовать FINAL или OPTIMIZE TABLE FINAL.
-- Дедупликация строк продукта по version:
CREATE TABLE products (
product_id UInt64,
name String,
price Decimal(10, 2),
version UInt64 -- timestamp или счётчик
) ENGINE = ReplacingMergeTree(version)
ORDER BY product_id;
-- Дедупликация НЕ при INSERT!
SELECT * FROM products FINAL;Движок, суммирующий числовые столбцы строк с одинаковым ключом ORDER BY при слиянии. Идеален для хранения предварительно агрегированных счётчиков и метрик. Суммирование применяется только к числовым столбцам, не входящим в первичный ключ; строковые столбцы — произвольный вариант. Как и ReplacingMergeTree, суммирует только при слиянии, поэтому запросы рекомендуется писать с GROUP BY.
-- Таблица счётчиков событий:
CREATE TABLE event_counts (
event_date Date,
user_id UInt64,
event_type LowCardinality(String),
count UInt64
) ENGINE = SummingMergeTree(count)
ORDER BY (event_date, user_id, event_type);
-- Запрос всегда через GROUP BY + sum():
SELECT user_id, sum(count) FROM event_counts GROUP BY user_id;Движок для хранения промежуточных состояний агрегатных функций в столбцах типа AggregateFunction и SimpleAggregateFunction. При слиянии партов движок вызывает функции merge для каждого столбца с одинаковым ключом ORDER BY. Используется как хранилище для материализованных представлений с pre-aggregation паттерном.
-- Материализованное представление для DAU:
CREATE MATERIALIZED VIEW dau_mv
TO dau_daily
AS SELECT
toDate(event_time) AS date,
uniqState(user_id) AS unique_users
FROM events
GROUP BY date;
-- Чтение через Merge-финализатор:
SELECT date, uniqMerge(unique_users) FROM dau_daily GROUP BY date;Движок, реализующий схему «отмены» строк через столбец знака (sign = 1 для вставки, sign = -1 для удаления). При слиянии пары строк с противоположными знаками и одинаковым первичным ключом аннулируют друг друга. Позволяет эффективно обновлять изменяемые объекты без мутаций, но требует точного контроля знака при вставке.
-- Обновление записи через collapse:
CREATE TABLE user_state (
user_id UInt64,
balance Decimal(10, 2),
sign Int8
) ENGINE = CollapsingMergeTree(sign)
ORDER BY user_id;
-- Отмена старой записи + вставка новой:
INSERT INTO user_state VALUES (42, 100.00, -1);
INSERT INTO user_state VALUES (42, 150.00, 1);Расширение CollapsingMergeTree с дополнительным столбцом версии, обеспечивающим корректную отмену строк даже при нарушении порядка вставки. Пары строк с одинаковым ключом ORDER BY, противоположным знаком и одинаковой версией аннулируют друг друга. Решает проблему CollapsingMergeTree при распределённых вставках, где порядок строк не гарантирован.
CREATE TABLE user_state_versioned (
user_id UInt64,
balance Decimal(10, 2),
version UInt64, -- монотонный счётчик
sign Int8
) ENGINE = VersionedCollapsingMergeTree(sign, version)
ORDER BY user_id;Модификатор SELECT, принудительно применяющий логику слияния (дедупликацию, суммирование, свёртку) к данным во время выполнения запроса. FINAL обеспечивает актуальный результат для ReplacingMergeTree и CollapsingMergeTree без необходимости ожидать фоновых слияний. Начиная с ClickHouse 23.2+, FINAL может выполняться параллельно, что существенно снижает производительность только при большом числе активных партов.
-- Запрос с дедупликацией:
SELECT product_id, name, price
FROM products FINAL
WHERE category = 'electronics'
ORDER BY price DESC
LIMIT 100;Первичный ключ сортировки MergeTree, определяющий физический порядок строк на диске и структуру разреженного индекса. Правильный выбор ORDER BY является наиболее критичным решением при проектировании схемы ClickHouse: кардинальность столбцов должна возрастать слева направо. Также задаёт ключ идентичности строк для движков с семантикой слияния.
-- Пример хорошего ORDER BY для аналитики событий:
-- tenant_id (низкая кардинальность) → date (средняя) → user_id (высокая)
CREATE TABLE events (
tenant_id UInt32,
event_date Date,
user_id UInt64,
event_type LowCardinality(String)
) ENGINE = MergeTree()
ORDER BY (tenant_id, event_date, user_id);Оптимизация ClickHouse, применяющая фильтрацию до чтения всех столбцов: сначала читается только столбец из условия PREWHERE, отфильтровываются строки, и лишь потом читаются остальные столбцы только для выживших строк. Автоматически применяется оптимизатором при optimize_move_to_prewhere = 1. Эффективна для высокоселективных фильтров по столбцам малого размера.
-- Явное использование PREWHERE:
SELECT url, duration_ms
FROM page_views
PREWHERE user_id = 42
WHERE event_date >= '2024-01-01';
-- Сначала фильтрация по user_id (маленький столбец),
-- затем чтение url и duration_ms только для найденных строкМодель выполнения запросов ClickHouse, обрабатывающая данные блоками (vectors) по несколько тысяч строк, а не построчно. Блочная обработка позволяет эффективно использовать SIMD-инструкции процессора (AVX-512 и пр.) и кеш L1/L2. Это является одним из ключевых факторов производительности ClickHouse по сравнению с классическими СУБД с построчной обработкой.
-- Настройка размера блока для векторной обработки:
SET max_block_size = 65536; -- строк в блоке (по умолчанию)
-- Проверка использования SIMD:
SELECT sum(value) FROM large_table
WHERE category = 'A';
-- Внутри: sum() применяется к блоку 65536 строк за разКоманда для визуализации физического плана выполнения запроса в виде DAG (направленного ациклического графа) процессоров. Показывает, как данные движутся через конвейер: от источников (MergeTreeSource) через трансформации (FilterStep, AggregatingStep) к стокам. Ключевой инструмент для диагностики узких мест производительности и проверки правильности параллелизации.
EXPLAIN PIPELINE graph = 1, compact = 0
SELECT user_id, count() as cnt
FROM events
WHERE event_date >= '2024-01-01'
GROUP BY user_id
ORDER BY cnt DESC
LIMIT 10;Процесс исключения нерелевантных гранул из чтения на основе разреженного первичного индекса. При выполнении запроса с фильтром по ORDER BY-ключу ClickHouse бинарным поиском определяет диапазон гранул, которые могут содержать нужные строки, и читает только их. Правильный ORDER BY может снизить число читаемых гранул с миллионов до единиц.
-- EXPLAIN покажет число пропущенных гранул:
EXPLAIN indexes = 1
SELECT count() FROM events
WHERE user_id BETWEEN 1000 AND 2000;
-- Поле 'Parts' и 'Granules' покажут эффективность фильтрацииДополнительные индексы MergeTree, позволяющие пропускать гранулы, не содержащие нужных данных, для столбцов не входящих в первичный ключ. Типы: minmax (диапазон значений), set (множество уникальных значений), bloom_filter (вероятностная фильтрация), ngrambf_v1/tokenbf_v1 (полнотекстовый). Занимают место, но могут резко снизить объём читаемых данных для подходящих запросов.
-- Bloom filter для фильтрации по URL:
CREATE TABLE access_log (
event_time DateTime,
user_id UInt64,
url String,
INDEX url_bloom url TYPE bloom_filter(0.01) GRANULARITY 4
) ENGINE = MergeTree()
ORDER BY (user_id, event_time);Тип skip-индекса ClickHouse на основе вероятностной структуры данных Bloom Filter. Для каждого блока гранул хранит компактный битовый фильтр: если значение гарантированно не встречается в блоке, гранула пропускается. Параметр false_positive (например, 0.01) контролирует допустимый процент ложных срабатываний. Эффективен для точечных поисков по строковым и UUID-столбцам.
-- Bloom filter с 1% ложных срабатываний:
CREATE TABLE sessions (
session_id UUID,
user_id UInt64,
INDEX session_bloom session_id TYPE bloom_filter(0.01) GRANULARITY 1
) ENGINE = MergeTree()
ORDER BY user_id;
-- Запрос: WHERE session_id = '...' использует bloom_filterПростейший тип skip-индекса, хранящий минимальное и максимальное значение столбца для каждого блока гранул. Позволяет пропускать блоки, чей диапазон [min, max] не пересекается с условием запроса. Minmax наиболее эффективен для монотонно возрастающих или хорошо упорядоченных столбцов (временные метки, числовые идентификаторы).
-- Minmax индекс для числового столбца:
CREATE TABLE metrics (
metric_time DateTime,
host_id UInt32,
value Float64,
INDEX value_minmax value TYPE minmax GRANULARITY 8
) ENGINE = MergeTree()
ORDER BY (host_id, metric_time);
-- Запрос WHERE value > 100.0 пропустит гранулы где max < 100Переработанный компонент анализа и оптимизации запросов ClickHouse (включён по умолчанию с 23.9), заменивший legacy-анализатор. Новый анализатор корректно обрабатывает сложные подзапросы, CTE и VIEW, поддерживает более агрессивные оптимизации (pushdown предикатов в подзапросы). Для некоторых edge-cases поведение может отличаться от legacy-анализатора.
-- Принудительное включение нового анализатора:
SET allow_experimental_analyzer = 1;
-- Или для конкретного запроса:
SELECT /*+ SET allow_experimental_analyzer = 1 */
user_id, count()
FROM (SELECT user_id FROM events WHERE event_date = today())
GROUP BY user_id;ClickHouse поддерживает несколько алгоритмов соединения, выбираемых настройкой join_algorithm: hash (хеш-таблица в памяти), parallel_hash (параллельный hash), partial_merge (слияние частями с диска при нехватке памяти), grace_hash (гибридный disk-spill), auto (автовыбор). Правильный выбор алгоритма критичен для производительности при больших join-ах.
-- Принудительный выбор алгоритма:
SET join_algorithm = 'grace_hash';
SELECT o.order_id, u.name
FROM orders AS o
JOIN users AS u ON o.user_id = u.user_id
WHERE o.order_date >= '2024-01-01';Объект ClickHouse, автоматически записывающий результат запроса в целевую таблицу при каждой вставке в исходную. В отличие от традиционных СУБД, ClickHouse MV обновляются инкрементально: триггер срабатывает только на вставляемый блок, а не перестраивает всё представление. MV не хранят данные сами по себе — они маршрутизируют данные в отдельную целевую таблицу.
-- Инкрементальное MV для подсчёта событий по дням:
CREATE MATERIALIZED VIEW events_daily_mv
TO events_daily
AS SELECT
toDate(event_time) AS date,
user_id,
count() AS cnt
FROM events
GROUP BY date, user_id;Встроенная в парт альтернативная физическая укладка данных, автоматически поддерживаемая ClickHouse при вставках и слияниях. Проекция хранит те же данные с другим ORDER BY (или с предварительной агрегацией), позволяя оптимизатору выбирать наиболее эффективный порядок чтения. В отличие от MV, проекция прозрачна для пользователя: SELECT работает с основной таблицей.
-- Добавление проекции с другим порядком сортировки:
ALTER TABLE events
ADD PROJECTION events_by_type
(
SELECT * ORDER BY (event_type, event_time)
);
-- Материализация проекции для существующих данных:
ALTER TABLE events MATERIALIZE PROJECTION events_by_type;Тип материализованного представления (доступен с ClickHouse 23.12+), перестраивающий свои данные целиком по расписанию (REFRESH EVERY N SECONDS/MINUTES/HOURS). В отличие от инкрементального MV, обновляемое MV атомарно заменяет содержимое: старые данные заменяются новыми в одной транзакции. Подходит для агрегатов, требующих точного пересчёта по всей истории.
-- Обновляемое MV с ежечасным пересчётом:
CREATE MATERIALIZED VIEW hourly_stats
REFRESH EVERY 1 HOUR
AS SELECT
toStartOfHour(event_time) AS hour,
count() AS total_events
FROM events
GROUP BY hour;Пара суффиксов агрегатных функций ClickHouse для работы с промежуточными состояниями. Функция с суффиксом -State возвращает бинарное состояние агрегата (тип AggregateFunction), которое сохраняется в AggregatingMergeTree. Функция с суффиксом -Merge принимает эти состояния и финализирует результат. Паттерн используется в MV для инкрементальной предварительной агрегации.
-- Сохранение состояния uniq через -State:
INSERT INTO dau_daily
SELECT
toDate(event_time) AS date,
uniqState(user_id) AS unique_users
FROM events
GROUP BY date;
-- Чтение через -Merge:
SELECT date, uniqMerge(unique_users) FROM dau_daily GROUP BY date;Архитектурный паттерн ClickHouse: инкрементальное MV накапливает агрегированные данные в AggregatingMergeTree, снижая объём хранения и ускоряя аналитические запросы на порядки. Ключевые компоненты: исходная таблица (сырые события), MV с -State агрегатами, целевая таблица AggregatingMergeTree. Запросы читают только целевую таблицу через -Merge финализаторы.
-- Полный пример MV + AggregatingMergeTree:
CREATE TABLE revenue_agg (
date Date,
product_id UInt32,
total_revenue AggregateFunction(sum, Decimal(18, 2)),
order_count AggregateFunction(count)
) ENGINE = AggregatingMergeTree() ORDER BY (date, product_id);Механизм срабатывания материализованного представления в ClickHouse: при каждой вставке в исходную таблицу SELECT запрос MV применяется только к вставленному блоку строк. Это означает, что GROUP BY в MV агрегирует только новые строки, а не всю историю — поэтому целевая таблица должна быть AggregatingMergeTree, умеющим объединять состояния при слиянии. Ретроактивная обработка при изменении MV отсутствует.
-- MV срабатывает только на новый INSERT-блок:
-- Если INSERT содержит 1000 строк за день 2024-01-05,
-- MV агрегирует только эти 1000 строк;
-- Исторические данные NOT пересчитываются автоматически.
-- Для бэкфилла: INSERT INTO target SELECT ... FROM source;Наиболее распространённый паттерн pre-aggregation в ClickHouse: сырая таблица (MergeTree) → инкрементальное MV (с -State агрегатами) → целевая таблица (AggregatingMergeTree). AggregatingMergeTree при слиянии вызывает -Merge для каждого состояния с одинаковым ключом, накапливая данные из всех вставок. Запросы используют sumMerge(), uniqMerge() для финального результата.
-- Канонический паттерн:
CREATE MATERIALIZED VIEW visits_mv TO visits_agg AS
SELECT
toDate(ts) AS date,
page_id,
uniqState(user_id) AS uv,
sumState(duration) AS total_duration
FROM raw_visits
GROUP BY date, page_id;
-- Запрос:
SELECT date, uniqMerge(uv), sumMerge(total_duration) FROM visits_agg GROUP BY date;Горизонтальный раздел данных в распределённом кластере ClickHouse: каждый шард содержит подмножество строк таблицы, определяемое выражением шардирования (SHARDING KEY). Запросы к распределённой таблице (Distributed engine) отправляются на все шарды параллельно, а результаты объединяются на координаторе. Правильный выбор ключа шардирования критичен для равномерного распределения нагрузки.
-- Distributed таблица для 2-шардового кластера:
CREATE TABLE events_dist ON CLUSTER '{cluster}'
AS events_local
ENGINE = Distributed('{cluster}', currentDatabase(), events_local, rand());
-- rand() как ключ шардирования для равномерного распределенияКопия данных шарда на отдельном узле для обеспечения высокой доступности и масштабирования чтения. ClickHouse поддерживает синхронную (через ReplicatedMergeTree + ClickHouse Keeper) и асинхронную репликацию. Каждая реплика обрабатывает локальные чтения независимо, что линейно масштабирует пропускную способность SELECT.
-- ReplicatedMergeTree с именем реплики из макроса:
CREATE TABLE events ON CLUSTER '{cluster}' (
event_time DateTime,
user_id UInt64
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
ORDER BY (user_id, event_time);Семейство реплицируемых движков MergeTree, использующих ClickHouse Keeper (или ZooKeeper) для координации слияний и репликации. Каждая таблица ReplicatedMergeTree имеет уникальный путь в Keeper (znode), через который реплики обмениваются информацией о партах и очередью слияний. INSERT на любую реплику автоматически распространяется на остальные.
-- Создание реплицированной таблицы:
CREATE TABLE hits (
event_date Date,
user_id UInt64,
url String
) ENGINE = ReplicatedMergeTree(
'/clickhouse/tables/{shard}/hits',
'{replica}'
)
ORDER BY (user_id, event_date);Виртуальный движок таблицы ClickHouse, не хранящий данных, но маршрутизирующий запросы к локальным таблицам на всех шардах кластера. INSERT в Distributed таблицу отправляет данные на шарды согласно ключу шардирования; SELECT агрегирует результаты со всех шардов. Distributed поддерживает параметры: кластер, база данных, локальная таблица, ключ шардирования.
-- Запрос через Distributed (read path):
SELECT user_id, count() AS visits
FROM events_dist -- Distributed таблица
WHERE event_date = today()
GROUP BY user_id
ORDER BY visits DESC
LIMIT 100;Встроенная в ClickHouse реализация координирующего сервиса, совместимого с API ZooKeeper. Keeper обеспечивает консенсус для ReplicatedMergeTree через алгоритм Raft: хранит лог операций, координирует выборы лидера и обеспечивает атомарность обновлений метаданных репликации. С версии 22.4+ является рекомендуемой заменой внешнему ZooKeeper.
-- Конфигурация Keeper в config.xml:
<keeper_server>
<tcp_port>9181</tcp_port>
<server_id>1</server_id>
<log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
<raft_configuration>
<server>
<id>1</id>
<hostname>keeper1</hostname>
<port>9234</port>
</server>
</raft_configuration>
</keeper_server>Режим вставки в ReplicatedMergeTree, при котором сервер подтверждает INSERT только после записи данных на заданное число реплик (настройка insert_quorum). Гарантирует, что данные не будут потеряны при отказе одной реплики сразу после вставки. В сочетании с select_sequential_consistency обеспечивает строгую последовательную консистентность для критических данных.
-- Вставка с подтверждением от 2 реплик:
SET insert_quorum = 2;
SET insert_quorum_parallel = 0;
INSERT INTO payments VALUES (101, 500.00, now());
-- Сервер ответит только после записи на 2 репликиФункция ClickHouse (enable_parallel_replicas = 1), позволяющая одному запросу параллельно читать данные с нескольких реплик одного шарда. Каждая реплика читает непересекающиеся диапазоны гранул, что линейно масштабирует throughput для тяжёлых полных сканирований. Требует parallel_replicas_for_non_replicated_merge_tree для не-реплицированных таблиц.
-- Включение параллельных реплик:
SET enable_parallel_replicas = 1;
SET max_parallel_replicas = 3;
SET parallel_replicas_for_non_replicated_merge_tree = 1;
SELECT count() FROM large_events_table
WHERE event_date >= '2024-01-01';
-- Запрос будет распределён по 3 репликамДвижок хранения ClickHouse Cloud, хранящий данные в объектном хранилище (S3) вместо локальных дисков и разделяющий их между всеми вычислительными узлами. В отличие от ReplicatedMergeTree, SharedMergeTree не копирует данные между репликами — все узлы читают из одного источника. Обеспечивает мгновенное масштабирование compute без перемещения данных.
-- SharedMergeTree доступен только в ClickHouse Cloud:
CREATE TABLE events (
event_time DateTime,
user_id UInt64
) ENGINE = SharedMergeTree('/clickhouse/tables/events', '{replica}')
ORDER BY (user_id, event_time);
-- Данные хранятся в S3, а не на локальном дискеПараметр конфигурации Distributed таблицы, управляющий поведением вставки при нескольких репликах шарда. При internal_replication = true данные отправляются только на одну реплику, которая самостоятельно реплицирует их на остальные через механизм ReplicatedMergeTree. При false — Distributed сам отправляет данные на все реплики, что может привести к дублированию.
-- Конфигурация кластера с internal_replication:
<remote_servers>
<production_cluster>
<shard>
<internal_replication>true</internal_replication>
<replica><host>ch1</host><port>9000</port></replica>
<replica><host>ch2</host><port>9000</port></replica>
</shard>
</production_cluster>
</remote_servers>Движок базы данных ClickHouse, автоматически реплицирующий все DDL-операции (CREATE, ALTER, DROP) на все реплики через ClickHouse Keeper. Позволяет создавать реплицированные таблицы без явного указания пути в Keeper — движок генерирует его автоматически. Упрощает управление схемой в многоузловых кластерах и является обязательным для Parallel Replicas.
-- Создание реплицированной базы данных:
CREATE DATABASE analytics ON CLUSTER '{cluster}'
ENGINE = Replicated('/clickhouse/databases/analytics', '{shard}', '{replica}');
-- DDL выполняется на всех репликах автоматическиМеханизм автоматического удаления или перемещения данных по истечении заданного времени. ClickHouse поддерживает TTL на уровне строк (удаление) и столбцов (замена на значение по умолчанию), а также TTL движения для перемещения данных между уровнями хранения. TTL вычисляется на основе выражения от столбца типа Date/DateTime и применяется при фоновых слияниях.
-- TTL строк + TTL движения на другой диск:
CREATE TABLE logs (
log_time DateTime,
level String,
message String
) ENGINE = MergeTree()
ORDER BY log_time
TTL log_time + INTERVAL 30 DAY
TO DISK 'cold_disk',
log_time + INTERVAL 90 DAY DELETE;Архитектура хранения ClickHouse с несколькими уровнями (горячий SSD → тёплый HDD → холодный S3), автоматически перемещающая данные между уровнями по политике хранения (storage policy). Горячие данные хранятся на быстрых дисках для минимальной латентности; старые данные автоматически переносятся на более медленные и дешёвые носители. Настраивается через storage_configuration в config.xml.
-- Запрос текущего хранения партов:
SELECT
name,
disk_name,
bytes_on_disk
FROM system.parts
WHERE table = 'logs' AND active = 1
ORDER BY modification_time;Именованный набор правил ClickHouse, определяющий порядок использования дисков и томов при записи и перемещении данных. Политика хранения назначается таблице при создании через SETTINGS storage_policy = 'tiered'. Новые данные записываются на первый том (горячий); TTL движения или заполнение тома автоматически переносят парты на следующий уровень.
-- Конфигурация политики хранения в storage.xml:
<storage_configuration>
<volumes>
<hot><disk>ssd_disk</disk></hot>
<cold><disk>s3_disk</disk></cold>
</volumes>
<policies>
<tiered_policy>
<volumes><volume>hot</volume><volume>cold</volume></volumes>
<move_factor>0.1</move_factor>
</tiered_policy>
</policies>
</storage_configuration>Операция удаления строк в ClickHouse (DELETE FROM ... WHERE ...), реализованная через создание маски удалённых строк в парте без перезаписи данных. Значительно быстрее тяжёлых мутаций (ALTER TABLE DELETE): не перезаписывает столбцы, только добавляет битовую маску. Удалённые строки физически удаляются при следующем слиянии парта.
-- Лёгкое удаление устаревших записей:
DELETE FROM events
WHERE event_time < '2023-01-01' AND event_type = 'debug';
-- Проверка строк, помеченных на удаление:
SELECT count() FROM events
WHERE _row_exists = 0; -- системный псевдостолбецМеханизм точечного обновления данных в ClickHouse 25.1+, создающий отдельный маленький 'patch' парт с обновлёнными значениями без перезаписи всего оригинального парта. При чтении patch-парты объединяются с базовыми на лету; при слиянии применяются постоянно. Позволяет выполнять частые UPDATE отдельных строк без накладных расходов полных мутаций.
-- Lightweight UPDATE (Patch Parts, CH 25.1+):
UPDATE events
SET status = 'processed'
WHERE event_id IN (1001, 1002, 1003);
-- Статус patch-партов:
SELECT name, part_type FROM system.parts
WHERE table = 'events' AND part_type = 'Patch';Режим репликации для объектного хранилища (S3/GCS/Azure), при котором реплики не копируют физические файлы друг другу, а только хранят метаданные о расположении данных. Включается через allow_remote_fs_zero_copy_replication = 1. Значительно снижает трафик и стоимость объектного хранилища в многореплицированных кластерах, но требует общего доступа к S3 бакету.
-- Включение zero-copy репликации в конфигурации:
<merge_tree>
<allow_remote_fs_zero_copy_replication>1</allow_remote_fs_zero_copy_replication>
</merge_tree>
-- Реплики хранят только ссылки на объекты S3,
-- не копируя файлы между узламиКеш ClickHouse (query_result_cache), хранящий полные результаты SELECT запросов в памяти. Повторный идентичный запрос возвращает результат из кеша без повторного чтения данных. Управляется настройками use_query_cache = 1 и query_cache_ttl. Эффективен для дашбордов с частыми повторяющимися запросами и относительно стабильными данными.
-- Запрос с использованием кеша результатов:
SELECT
toDate(event_time) AS date,
count() AS events_count
FROM events
GROUP BY date
SETTINGS
use_query_cache = 1,
query_cache_ttl = 300; -- кеш на 5 минутСистема управления вычислительными ресурсами ClickHouse через иерархию scheduler-узлов: FIFO, Priority, Weight Fair (WF). Позволяет разделить нагрузку между tenant-ами или типами запросов, гарантируя минимальные квоты и ограничивая максимальное потребление CPU/IO. Настраивается через SQL DDL (CREATE WORKLOAD) начиная с ClickHouse 24.1.
-- Создание workload с приоритетами:
CREATE WORKLOAD production WEIGHT 10;
CREATE WORKLOAD analytics WEIGHT 5 PARENT production;
CREATE WORKLOAD batch WEIGHT 1 PARENT production;
-- Привязка запроса к workload:
SET workload = 'analytics';Встроенный кеш ClickHouse для файлов меток (.mrk2), хранящих соответствие гранул первичного индекса физическим смещениям в файлах данных. Mark cache позволяет избегать повторного чтения .mrk2 файлов с диска при каждом запросе. Размер контролируется настройкой mark_cache_size (по умолчанию 5 ГБ); при недостаточном размере производительность резко деградирует.
-- Просмотр использования кеша меток:
SELECT
event,
value
FROM system.events
WHERE event IN ('MarkCacheHits', 'MarkCacheMisses');
-- Целевое соотношение: Hits / (Hits + Misses) > 0.95Официальный адаптер dbt для работы с ClickHouse, позволяющий строить трансформационные пайплайны на SQL с управлением зависимостями и тестированием. Поддерживает инкрементальные модели (materialized='incremental') с стратегиями insert_overwrite и delete+insert, а также модели с материализованными представлениями. Версия 1.8+ поддерживает ReplacingMergeTree для upsert-паттернов.
-- dbt модель с инкрементальной стратегией:
-- models/events_daily.sql
{{ config(
materialized='incremental',
engine='MergeTree()',
order_by='(date, user_id)',
incremental_strategy='insert_overwrite'
) }}
SELECT toDate(event_time) AS date, user_id, count() AS cnt
FROM {{ source('raw', 'events') }}
GROUP BY date, user_idApache Iceberg — открытый стандарт табличного формата для data lakehouse с поддержкой ACID, версионирования схемы и time travel. ClickHouse поддерживает чтение Iceberg таблиц через функцию iceberg() и движок IcebergMergeTree (25.1+), позволяющий хранить данные в Iceberg-формате при записи. Bidirectional Iceberg (чтение + запись) доступен с 25.8+.
-- Чтение Iceberg таблицы через table function:
SELECT count() FROM iceberg('s3://my-bucket/warehouse/events')
WHERE event_date = '2024-01-01';
-- Создание нативной IcebergMergeTree таблицы (25.1+):
CREATE TABLE events
ENGINE = IcebergMergeTree('s3://bucket/warehouse/')
ORDER BY event_time;Нативный тип данных JSON в ClickHouse (стал GA в версии 25.3), хранящий полуструктурированные данные с автоматическим обнаружением вложенных путей и динамическим набором столбцов. JSON тип создаёт отдельный столбец для каждого обнаруженного пути, используя тип Dynamic для хранения. Поддерживает вложенные объекты, массивы и гетерогенные типы значений по одному пути.
-- Таблица с нативным JSON типом (25.3+):
CREATE TABLE events (
id UInt64,
data JSON
) ENGINE = MergeTree() ORDER BY id;
INSERT INTO events FORMAT JSONEachRow
{"id": 1, "data": {"user": {"id": 42, "country": "RU"}, "action": "click"}}
-- Запрос: SELECT data.user.country FROM eventsСпециализированный движок таблицы ClickHouse для непрерывного чтения новых файлов из S3 бакета, аналогичный Kafka engine для очередей сообщений. S3Queue отслеживает уже обработанные файлы и автоматически читает только новые. Используется совместно с материализованными представлениями для потоковой загрузки данных из S3 (парсинг логов, CDC файлы).
-- Потоковая загрузка CSV из S3:
CREATE TABLE s3_queue_source (
event_time DateTime,
user_id UInt64,
event_type String
) ENGINE = S3Queue(
's3://my-bucket/events/*.csv',
'CSVWithNames'
)
SETTINGS mode = 'ordered', s3queue_tracked_files_limit = 10000;Движок таблицы ClickHouse для чтения сообщений из топиков Apache Kafka. Kafka engine реализует polling consumer: SELECT читает пакет сообщений из Kafka и смещает оффсет. На практике Kafka engine используется только как источник для материализованного представления — прямые SELECT нежелательны из-за невозможности повторного чтения одних и тех же сообщений.
-- Kafka engine + MV для потоковой загрузки:
CREATE TABLE kafka_events ENGINE = Kafka()
SETTINGS kafka_broker_list = 'kafka:9092',
kafka_topic_list = 'events',
kafka_group_name = 'clickhouse_consumer',
kafka_format = 'JSONEachRow';
-- MV направляет данные в основную таблицу:
CREATE MATERIALIZED VIEW kafka_mv TO events AS SELECT * FROM kafka_events;Автономная утилита командной строки ClickHouse, выполняющая SQL запросы к локальным файлам (CSV, Parquet, JSON, Avro и др.) без необходимости запускать сервер. Поддерживает большинство функций ClickHouse включая JOIN, GROUP BY, оконные функции, FORMAT. Незаменим для ETL-скриптов, анализа дампов и профилирования данных на ноутбуке.
-- Анализ локального Parquet файла:
clickhouse-local --query "
SELECT
toDate(event_time) AS date,
count() AS cnt
FROM file('events.parquet')
GROUP BY date
ORDER BY date
FORMAT PrettyCompact
"Указание формата сериализации/десериализации данных в ClickHouse при INSERT и SELECT. ClickHouse поддерживает более 70 форматов: TabSeparated, CSV, JSONEachRow, Parquet, ORC, Avro, Arrow, ProtoBuf, CapnProto и другие. FORMAT также определяет HTTP-формат ответа API. Правильный выбор формата критичен для производительности массовых вставок.
-- Вставка в формате JSONEachRow:
INSERT INTO events FORMAT JSONEachRow
{"event_time": "2024-01-01 12:00:00", "user_id": 42}
{"event_time": "2024-01-01 12:01:00", "user_id": 43}
-- Экспорт в Parquet:
SELECT * FROM events WHERE event_date = today()
FORMAT Parquet INTO OUTFILE 'events.parquet';Режим вставки ClickHouse (async_insert = 1), при котором сервер буферизует небольшие INSERT-ы в памяти и записывает их пакетами по достижении порога async_insert_max_data_size или таймаута async_insert_busy_timeout_ms. Позволяет приложениям выполнять частые мелкие INSERT-ы без ухудшения производительности от большого числа мелких партов.
-- Включение асинхронных вставок на уровне подключения:
SET async_insert = 1;
SET wait_for_async_insert = 1; -- ждать подтверждения записи
SET async_insert_max_data_size = '10M';
SET async_insert_busy_timeout_ms = 200;
-- Каждый INSERT буферизуется, запись — батчами