Learning Platform
Глоссарий Troubleshooting
Урок 04.05 · 20 мин
Продвинутый
CollapsingMergeTreeVersionedCollapsingMergeTreeSignCancel Row

CollapsingMergeTree и VersionedCollapsingMergeTree

CollapsingMergeTree и VersionedCollapsingMergeTree реализуют sign-based мутации: вместо обновления строки in-place вы вставляете “отменяющую” строку (-1) и новую строку (+1). При фоновом merge пары cancel/state коллапсируются, оставляя только актуальные данные.


CollapsingMergeTree: базовый механизм

CollapsingMergeTree добавляет к таблице специальный sign-столбец типа Int8. Каждая строка помечается:

  • +1 (state) — актуальная строка
  • -1 (cancel) — отмена предыдущей строки с тем же ORDER BY ключом
CREATE TABLE orders (
    order_id UInt32,
    status String,
    amount Decimal(10, 2),
    Sign Int8
) ENGINE = CollapsingMergeTree(Sign)
ORDER BY order_id;

Жизненный цикл: state -> cancel -> new state

Допустим, заказ #100 переходит из статуса “new” в “paid”:

-- 1. Вставляем исходное состояние (state, +1)
INSERT INTO orders VALUES (100, 'new', 1500.00, 1);

-- 2. Отменяем старое состояние (cancel, -1) -- ТОЧНАЯ копия с Sign=-1
INSERT INTO orders VALUES (100, 'new', 1500.00, -1);

-- 3. Вставляем новое состояние (state, +1)
INSERT INTO orders VALUES (100, 'paid', 1500.00, 1);

До merge в таблице 3 строки. После merge:

  • Пара (state +1, cancel -1) с одинаковым ORDER BY ключом и значениями коллапсируется
  • Остаётся одна строка: (100, 'paid', 1500.00, 1)
Жизненный цикл коллапсирования
state (+1): order #100, newINSERT state: вставка актуальной строки с Sign=+1. Это исходное состояние записи.
cancel
cancel (-1): order #100, newINSERT cancel: вставка отменяющей строки с Sign=-1. Значения ВСЕХ столбцов должны совпадать с отменяемой строкой (кроме Sign). Если значения не совпадут -- коллапсирование не произойдёт.
new state
state (+1): order #100, paidINSERT new state: вставка нового состояния с Sign=+1 и обновлёнными значениями.
merge
order #100, paid, Sign=+1После merge: пара (+1, -1) с одинаковым ORDER BY ключом и одинаковыми значениями коллапсируется. Остаётся только последний state (+1).

Критическое ограничение: строгий порядок вставки

CollapsingMergeTree требует, чтобы cancel строка (-1) была вставлена после соответствующей state строки (+1). Если порядок нарушен (cancel приходит раньше state), коллапсирование не сработает — обе строки останутся.

Это ограничение делает CollapsingMergeTree пригодным только для однопоточной последовательной вставки, где порядок событий гарантирован.


Паттерн запроса: HAVING sum(Sign) > 0

До merge в таблице могут быть несколлапсированные строки. Правильный запрос всегда агрегирует по Sign:

-- ПРАВИЛЬНО: фильтрация по Sign
SELECT
    order_id,
    anyIf(status, Sign = 1) AS current_status,
    sum(amount * Sign) AS net_amount
FROM orders
GROUP BY order_id
HAVING sum(Sign) > 0;

-- НЕПРАВИЛЬНО: чтение без агрегации Sign
-- SELECT * FROM orders -- покажет cancel и state строки

HAVING sum(Sign) > 0 отсеивает полностью отменённые записи (где cancel и state уже вставлены, но merge ещё не произошёл).


VersionedCollapsingMergeTree: out-of-order вставка

VersionedCollapsingMergeTree расширяет CollapsingMergeTree, добавляя столбец Version. Это снимает ограничение на порядок вставки:

CREATE TABLE orders_v (
    order_id UInt32,
    status String,
    amount Decimal(10, 2),
    Sign Int8,
    Version UInt32
) ENGINE = VersionedCollapsingMergeTree(Sign, Version)
ORDER BY order_id;

При merge движок удаляет пары строк с одинаковым ORDER BY ключом, одинаковым Version, но разным Sign. Порядок вставки не имеет значения — Version столбец определяет, какие строки являются парой.

-- Порядок вставки НЕ важен
INSERT INTO orders_v VALUES (100, 'new', 1500.00, 1, 1);    -- state v1
INSERT INTO orders_v VALUES (100, 'paid', 1500.00, 1, 2);   -- state v2
INSERT INTO orders_v VALUES (100, 'new', 1500.00, -1, 1);   -- cancel v1 (может прийти позже!)

После merge: пара (state v1, cancel v1) коллапсируется. Остаётся state v2.


Сравнение: Collapsing vs Versioned

АспектCollapsingMergeTreeVersionedCollapsingMergeTree
Столбец SignInt8 (+1/-1)Int8 (+1/-1)
Столбец VersionНетUInt* (обязательный)
Порядок INSERTСтрого последовательныйПроизвольный
Cancel/state matchingПо ORDER BY ключу + все значенияПо ORDER BY ключу + Version
Однопоточная вставкаПодходитПодходит
Многопоточная/distributed вставкаНе подходитПредпочтительный выбор

Когда использовать какой:

  • CollapsingMergeTree — однопоточная вставка, один источник данных, порядок событий гарантирован. Проще (нет Version столбца).
  • VersionedCollapsingMergeTree — многопоточная вставка, distributed INSERT, несколько источников данных. Всегда предпочтительнее в production-системах с несколькими клиентами.
WARNING

Оба движка сложнее ReplacingMergeTree. Используйте только когда нужна точная отмена отдельных строк (не полная замена по ключу). Если задача — просто “оставить последнюю версию строки”, ReplacingMergeTree проще и надёжнее.


Когда Collapsing, а не Replacing

ReplacingMergeTree заменяет всю строку по ORDER BY ключу — старые данные теряются. Collapsing-движки позволяют:

  • Аддитивную агрегацию с корректной отменой: sum(amount * Sign) корректно вычитает отменённые строки. ReplacingMergeTree не поддерживает такую семантику.
  • Групповую отмену: можно отменить несколько строк одного заказа (позиции, платежи) с разными ORDER BY ключами.
  • Точную коррекцию: ошибочно вставленный event можно отменить без потери других events с тем же ключом.

Ключевые выводы

  1. CollapsingMergeTree использует Sign столбец (Int8: +1 state, -1 cancel). Пары с одинаковым ORDER BY ключом коллапсируются при merge.
  2. Порядок INSERT критичен для CollapsingMergeTree: cancel (-1) должен быть вставлен строго после state (+1). Нарушение порядка ломает коллапсирование.
  3. VersionedCollapsingMergeTree добавляет Version столбец и снимает ограничение на порядок вставки. Предпочтительнее для distributed и многопоточных систем.
  4. Паттерн запроса: всегда используйте HAVING sum(Sign) > 0 или агрегируйте с Sign для корректных результатов до merge.
  5. Collapsing vs Replacing: используйте Collapsing, когда нужна аддитивная агрегация с корректной отменой (sum(amount * Sign)), а не простая замена последней версии строки.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 3. В CollapsingMergeTree вставлена строка (+1) для заказа #100, затем cancel (-1), затем новая строка (+1) с обновлённым статусом. Сколько строк останется после merge?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 7