deшка
Урок 10.9

ReplacingMergeTree: версии и корректный результат

Как хранить меняющееся состояние, читать его до merge и не путать дедупликацию с уникальностью.

ReplacingMergeTree хранит поступающие версии, а при слиянии частей оставляет одну строку для одинакового ORDER BY. Он подходит для аналитической копии последнего состояния сущности. Это не ограничение уникальности и не немедленный UPDATE по ключу.

Идентичность и версия

Рассмотрим состояние сущности: стабильный id, возрастающая версия и изменяемые поля.

CREATE TABLE entity_state
(
    id UInt64,
    version UInt64,
    state String,
    value UInt64
)
ENGINE = ReplacingMergeTree(version)
ORDER BY id;

Строки (7, 1, 'new', 100) и (7, 2, 'ready', 120) описывают одну сущность. При схлопывании выигрывает версия 2. Версия задаёт порядок состояний; время поступления может отличаться от порядка изменений. Если две строки имеют одинаковую максимальную версию, нельзя использовать эту ситуацию как надёжное правило выбора актуального бизнес-состояния.

ORDER BY должен оставаться одинаковым между версиями. Добавление изменяемого state в ключ сделает new и ready разными ключами, и строки не схлопнутся. Версию также не включают в ключ, когда требуется замена по id.

Без аргумента version движок выбирает строку по порядку частей и вставок. Для изменений, доставляемых с задержками, задают явную версию источника.

Что видно до слияния

Обычный SELECT может вернуть обе версии: merge работает в фоне и не обязан немедленно обработать все части. Запрос с FINAL выполняет необходимое схлопывание во время чтения:

SELECT id, state, value
FROM entity_state FINAL;

FINAL учитывает семантику движка, но не переписывает данные на диске. Он добавляет работу к запросу; стоимость зависит от версии ClickHouse, ключа, фильтра и объёма выбранных частей. OPTIMIZE FINAL — отдельная тяжёлая операция с частями, а не обязательный шаг после каждой вставки.

Альтернатива для явно версионных данных — выбрать значения по максимальной версии:

SELECT id, argMax(tuple(state, value), version) AS latest
FROM entity_state
GROUP BY id;

Tuple связывает атрибуты одной выбранной строки. Несколько отдельных argMax при равных версиях или NULL требуют особенно внимательной семантики. Уникальный порядок версий лучше определить в источнике.

Партиции и удаления

Parts разных партиций не сливаются. Все версии одной сущности желательно направлять в одну стабильную партицию. Если партиция вычисляется из изменяемого поля, версии могут разойтись по разным группам. Поведение FINAL между партициями дополнительно зависит от настроек; это не замена правильной модели.

Вариант ReplacingMergeTree(version, is_deleted) позволяет передавать признак удаления в последней версии. Удаляющую версию нельзя бездумно физически выбросить: поздно пришедшая старая строка способна снова появиться. Очистка требует гарантии, что старые версии больше не поступят, и согласованного состояния реплик.

Поиск и устранение повторов

Сначала определяют, что считается дублем: полностью одинаковые строки или несколько версий одного бизнес-ключа. Для поиска полных повторов в entity_state можно сгруппировать все её поля:

SELECT id, version, state, value, count() AS copies
FROM entity_state
GROUP BY id, version, state, value
HAVING copies > 1;

Разные версии одного id описывают состояния сущности; для выбора последнего нужна версия. Удаление всех строк по повторяющемуся id уничтожит и экземпляр, который требовалось сохранить.

При повторной доставке нужны устойчивый идентификатор и версия источника. Для пересчёта можно сохранять вход в промежуточной таблице, выбирать однозначное состояние и затем публиковать результат. Границу обработанного входа согласуют с поступлением новых данных; частоту пересчёта выбирают по допустимой задержке и стоимости.

Последнее состояние и агрегаты

Для журнала независимых событий потеря старых строк может быть нежелательна; тогда обычный MergeTree сохраняет историю. Replacing выбирают, когда действительно нужна одна актуальная сущность.

Инкрементальная materialized view обрабатывает каждую вставку. Она может уже учесть и старую, и новую версию. Последующее схлопывание источника не отменяет прежний вклад в витрину. Для корректной агрегации нужен подготовленный вход без повторного вклада, модель изменений со знаком или согласованный пересчёт.

Войти, чтобы отмечатьдальше →
10.10 →SummingMergeTree и AggregatingMergeTree