ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree, CollapsingMergeTree, VersionedCollapsingMergeTree, GraphiteMergeTree
ReplacingMergeTree, SummingMergeTree, AggregatingMergeTree и другие специализированные движки
Семейство MergeTree включает специализированные движки для разных сценариев:
| Движок | Назначение | Когда использовать |
|---|---|---|
| MergeTree | Базовый | Общие случаи |
| ReplacingMergeTree | Удаление дубликатов | Дедупликация, версии записей |
| SummingMergeTree | Суммирование агрегатов | Предварительная агрегация числовых данных |
| AggregatingMergeTree | Сложные агрегаты | Инкрементальные вычисления с состояниями |
| CollapsingMergeTree | Схлопывание пар строк | Эмуляция UPDATE/DELETE |
| VersionedCollapsingMergeTree | Схлопывание с версиями | Корректная обработка порядка вставки |
| GraphiteMergeTree | Агрегация метрик | Интеграция с Graphite/метриками |
ReplacingMergeTree удаляет дубликаты строк с одинаковым ключом сортировки при слиянии частей (merge).
Важно: Дедупликация происходит только во время merge, который выполняется фоном. В момент вставки дубликаты ещё присутствуют.
CREATE TABLE users
(
user_id UInt64,
name String,
email String,
updated_at DateTime
)
ENGINE = ReplacingMergeTree(updated_at)
PARTITION BY toYYYYMM(updated_at)
ORDER BY (user_id);Параметр version_column (опциональный):
CREATE TABLE events_dedup
(
event_id UInt64,
event_time DateTime,
user_id UInt64
)
ENGINE = ReplacingMergeTree()
ORDER BY (event_id);
-- Вставка дубликатов
INSERT INTO events_dedup VALUES (1, '2026-03-01 10:00:00', 100);
INSERT INTO events_dedup VALUES (1, '2026-03-01 10:00:00', 100);
INSERT INTO events_dedup VALUES (1, '2026-03-01 10:00:00', 100);
-- Сразу после вставки: 3 строки
SELECT count() FROM events_dedup; -- 3
-- После merge (может занять время):
SELECT count() FROM events_dedup; -- 1CREATE TABLE products
(
product_id UInt64,
name String,
price Decimal(10, 2),
stock UInt32,
updated_at DateTime DEFAULT now()
)
ENGINE = ReplacingMergeTree(updated_at)
ORDER BY (product_id);
-- Вставка обновлений
INSERT INTO products (product_id, name, price, stock)
VALUES (1, 'Widget', 10.00, 100);
INSERT INTO products (product_id, name, price, stock)
VALUES (1, 'Widget', 12.00, 80); -- Обновление цены
-- После merge останется строка с max(updated_at)
SELECT * FROM products;
-- product_id=1, name='Widget', price=12.00, stock=80Поскольку merge происходит асинхронно, для чтения актуальных данных используйте:
-- Вариант 1: FINAL модификатор (дорогой!)
SELECT * FROM products FINAL;
-- Вариант 2: Подзапрос с группировкой
SELECT
product_id,
argMax(name, updated_at) AS name,
argMax(price, updated_at) AS price,
argMax(stock, updated_at) AS stock
FROM products
GROUP BY product_id;
-- Вариант 3: Ожидание завершения мутаций
OPTIMIZE TABLE products FINAL;
SELECT * FROM products; -- Теперь без дубликатовВажно: FINAL заставляет ClickHouse применять дедупликацию при чтении, но это значительно замедляет запросы.
✅ Хорошие сценарии:
❌ Плохие сценарии:
SummingMergeTree суммирует значения числовых колонок для строк с одинаковым ключом сортировки при слиянии.
Используется для предварительной агрегации данных.
CREATE TABLE sales_summary
(
date Date,
product_id UInt32,
category_id UInt16,
quantity UInt32, -- Будет суммироваться
revenue Decimal(12, 2) -- Будет суммироваться
)
ENGINE = SummingMergeTree()
ORDER BY (date, product_id, category_id);Правила суммирования:
CREATE TABLE sales_explicit
(
date Date,
product_id UInt32,
category_id UInt16,
quantity UInt32,
revenue Decimal(12, 2),
discount Decimal(5, 2),
notes String -- Не суммируется
)
ENGINE = SummingMergeTree((quantity, revenue, discount))
ORDER BY (date, product_id, category_id);CREATE TABLE sales_raw
(
event_time DateTime,
product_id UInt32,
quantity UInt32,
revenue Decimal(10, 2)
)
ENGINE = MergeTree()
ORDER BY (event_time, product_id);
CREATE TABLE sales_daily
(
date Date,
product_id UInt32,
quantity UInt32,
revenue Decimal(10, 2)
)
ENGINE = SummingMergeTree()
ORDER BY (date, product_id);
-- Вставка сырых данных
INSERT INTO sales_raw VALUES
('2026-03-01 10:00:00', 1, 5, 100.00),
('2026-03-01 11:00:00', 1, 3, 60.00),
('2026-03-01 12:00:00', 1, 2, 40.00);
-- Агрегация в daily-таблицу
INSERT INTO sales_daily
SELECT
toDate(event_time) AS date,
product_id,
sum(quantity) AS quantity,
sum(revenue) AS revenue
FROM sales_raw
GROUP BY date, product_id;
-- После merge: одна строка с суммой
SELECT * FROM sales_daily;
-- date=2026-03-01, product_id=1, quantity=10, revenue=200.00-- С FINAL для гарантированной агрегации
SELECT
date,
sum(quantity) AS total_quantity,
sum(revenue) AS total_revenue
FROM sales_daily
GROUP BY date;
-- Или с FINAL (агрегация уже применена)
SELECT
date,
sum(quantity),
sum(revenue)
FROM sales_daily FINAL
GROUP BY date;✅ Хорошие сценарии:
❌ Плохие сценарии:
AggregatingMergeTree хранит состояния агрегатных функций для инкрементальной агрегации.
Позволяет строить сложные преагрегации с uniq, quantile, groupArray и другими функциями.
CREATE TABLE stats
(
date Date,
category String,
uniq_users AggregateFunction(uniq, UInt64),
sum_time SimpleAggregateFunction(sum, UInt32),
max_value SimpleAggregateFunction(max, Float64)
)
ENGINE = AggregatingMergeTree()
ORDER BY (date, category);AggregateFunction:
*State при вставке и *Merge при чтенииSimpleAggregateFunction:
*Merge при чтенииCREATE TABLE daily_users
(
date Date,
platform LowCardinality(String),
uniq_users AggregateFunction(uniq, UInt64),
page_views SimpleAggregateFunction(sum, UInt64)
)
ENGINE = AggregatingMergeTree()
ORDER BY (date, platform);
-- Вставка с состоянием агрегата
INSERT INTO daily_users
SELECT
toDate(event_time) AS date,
platform,
uniqState(user_id) AS uniq_users,
sum(page_views) AS page_views
FROM events
GROUP BY date, platform;
-- Чтение с применением агрегации
SELECT
date,
platform,
uniqMerge(uniq_users) AS unique_users,
sumMerge(page_views) AS total_views
FROM daily_users
GROUP BY date, platform;CREATE TABLE response_times
(
date Date,
endpoint String,
latency_p50 AggregateFunction(quantileTiming(0.50), UInt16),
latency_p95 AggregateFunction(quantileTiming(0.95), UInt16),
latency_p99 AggregateFunction(quantileTiming(0.99), UInt16)
)
ENGINE = AggregatingMergeTree()
ORDER BY (date, endpoint);
-- Вставка
INSERT INTO response_times
SELECT
toDate(timestamp) AS date,
endpoint,
quantileTimingState(0.50)(response_ms) AS p50,
quantileTimingState(0.95)(response_ms) AS p95,
quantileTimingState(0.99)(response_ms) AS p99
FROM api_logs
GROUP BY date, endpoint;
-- Чтение
SELECT
date,
endpoint,
quantileTimingMerge(0.50)(latency_p50) AS p50_ms,
quantileTimingMerge(0.95)(latency_p95) AS p95_ms,
quantileTimingMerge(0.99)(latency_p99) AS p99_ms
FROM response_times
GROUP BY date, endpoint;-- Сырая таблица
CREATE TABLE events_raw
(
event_time DateTime,
user_id UInt64,
platform String
)
ENGINE = MergeTree()
ORDER BY (event_time, user_id);
-- Таблица агрегатов
CREATE TABLE daily_stats
(
date Date,
platform String,
uniq_users AggregateFunction(uniq, UInt64)
)
ENGINE = AggregatingMergeTree()
ORDER BY (date, platform);
-- Материализованное представление для авто-агрегации
CREATE MATERIALIZED VIEW daily_stats_mv TO daily_stats
AS SELECT
toDate(event_time) AS date,
platform,
uniqState(user_id) AS uniq_users
FROM events_raw
GROUP BY date, platform;
-- Теперь при вставке в events_raw данные автоматически
-- агрегируются в daily_stats
INSERT INTO events_raw VALUES (now(), 1, 'web');
INSERT INTO events_raw VALUES (now(), 2, 'web');
INSERT INTO events_raw VALUES (now(), 3, 'mobile');
-- Агрегаты уже посчитаны
SELECT
platform,
uniqMerge(uniq_users) AS users
FROM daily_stats
GROUP BY platform;✅ Хорошие сценарии:
❌ Плохие сценарии:
CollapsingMergeTree схлопывает пары строк с одинаковым ключом сортировки и противоположными значениями колонки Sign (+1/-1).
Эмулирует UPDATE и DELETE в ClickHouse.
CREATE TABLE orders
(
order_id UInt64,
status String,
amount Decimal(10, 2),
Sign Int8 -- +1 для вставки, -1 для удаления
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY (order_id);-- Вставка заказа
INSERT INTO orders VALUES (1, 'pending', 100.00, +1);
-- "Обновление" статуса: удаляем старую строку, вставляем новую
INSERT INTO orders VALUES (1, 'pending', 100.00, -1); -- Удаление
INSERT INTO orders VALUES (1, 'shipped', 100.00, +1); -- Вставка
-- После merge останется одна строка
SELECT * FROM orders FINAL;
-- order_id=1, status='shipped', amount=100.00, Sign=+1-- Вставка
INSERT INTO orders VALUES (2, 'pending', 50.00, +1);
-- Удаление
INSERT INTO orders VALUES (2, 'pending', 50.00, -1);
-- После merge строка исчезнет
SELECT * FROM orders FINAL; -- ПустоКритично: Для корректного схлопывания строки должны вставляться парами в правильном порядке.
-- Плохо: если -1 вставлен до +1, строки не схлопнутся
INSERT INTO orders VALUES (3, 'pending', 75.00, -1); -- Сначала удаление
INSERT INTO orders VALUES (3, 'pending', 75.00, +1); -- Потом вставка
-- Результат: останутся ОБЕ строки!Решение: VersionedCollapsingMergeTree.
VersionedCollapsingMergeTree решает проблему порядка вставки, используя колонку версии.
CREATE TABLE orders_versioned
(
order_id UInt64,
status String,
amount Decimal(10, 2),
version UInt32, -- Версия строки
Sign Int8
)
ENGINE = VersionedCollapsingMergeTree(Sign, version)
ORDER BY (order_id);-- Версия 1
INSERT INTO orders_versioned VALUES (1, 'pending', 100.00, 1, +1);
-- Версия 2 (обновление статуса)
INSERT INTO orders_versioned VALUES (1, 'pending', 100.00, 1, -1); -- Удаляем v1
INSERT INTO orders_versioned VALUES (1, 'shipped', 100.00, 2, +1); -- Вставляем v2
-- Схлопывание работает корректно независимо от порядка вставки
SELECT * FROM orders_versioned FINAL;
-- order_id=1, status='shipped', version=2, Sign=+1✅ Хорошие сценарии:
❌ Плохие сценарии:
GraphiteMergeTree агрегирует метрики в стиле Graphite по заданным правилам (retention policies).
CREATE TABLE metrics
(
timestamp DateTime,
metric String,
value Float64
)
ENGINE = GraphiteMergeTree('graphite_config')
ORDER BY (timestamp, metric);Требует конфигурации в config.xml:
<graphite_rollup>
<pattern>
<regexp>^traffic\.</regexp>
<retention>
<age>0</age>
<precision>60</precision> <!-- 1 минута -->
</retention>
<retention>
<age>3600</age> <!-- 1 час -->
<precision>300</precision> <!-- 5 минут -->
</retention>
</pattern>
</graphite_rollup>| Характеристика | Replacing | Summing | Aggregating | Collapsing |
|---|---|---|---|---|
| Дедупликация | ✅ Да | ❌ Нет | ❌ Нет | ⚠️ Парами |
| Суммирование | ❌ Нет | ✅ Да | ⚠️ Через функции | ❌ Нет |
| Состояния агрегатов | ❌ Нет | ❌ Нет | ✅ Да | ❌ Нет |
| Эмуляция UPDATE | ⚠️ Версии | ❌ Нет | ❌ Нет | ✅ Да |
| Порядок вставки | Не важен | Не важен | Не важен | Критичен |
| Версионирование | ✅ Опционально | ❌ Нет | ❌ Нет | ✅ Требуется |
ReplacingMergeTree:
SummingMergeTree:
AggregatingMergeTree:
CollapsingMergeTree:
VersionedCollapsingMergeTree:
Изучим SQL-диалект ClickHouse: функции, агрегации, оконные функции и работу с массивами.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.