ClickHouse/docs/ru/operations/table_engines/collapsingmergetree.md
2018-08-21 10:54:37 +03:00

7.0 KiB
Raw Blame History

CollapsingMergeTree

Движок достаточно специфичен для Яндекс.Метрики.

Отличается от MergeTree тем, что позволяет автоматически удалять - "схлопывать" некоторые пары строк при слиянии.

В Яндекс.Метрике есть обычные логи (например, лог хитов) и логи изменений. Логи изменений используются, чтобы инкрементально считать статистику по постоянно меняющимся данным. Например - логи изменений визитов, логи изменений истории посетителей. Визиты в Яндекс.Метрике постоянно меняются - например, увеличивается количество хитов в визите. Изменением какого либо объекта будем называть пару (?старые значения, ?новые значения). Старые значения могут отсутствовать, если объект создался. Новые значения могут отсутствовать, если объект удалился. Если объект изменился, но был раньше и не удалился - присутствует оба значения. В лог изменений, для каждого изменения, пишется от одной до двух записей. Каждая запись содержит все те же атрибуты, что и сам объект, и ещё специальный атрибут, который позволяет отличить старые и новые значения. Видно, что при изменении объектов, в лог изменений лишь дописываются новые записи и не трогаются уже имеющиеся.

Лог изменений позволяет инкрементально считать почти любую статистику. Для этого надо учитывать "новые" строки с положительным знаком, и "старые" строки с отрицательным знаком. То есть, возможно инкрементально считать все статистики, алгебраическая структура которых содержит операцию взятия обратного элемента. Большинство статистик именно такие. Также удаётся посчитать "идемпотентные" статистики, например, количество уникальных посетителей, так как при изменении визитов, уникальные посетители не удаляются.

Это - основная идея, благодаря которой Яндекс.Метрика работает в реальном времени.

CollapsingMergeTree принимает дополнительный параметр - имя столбца типа Int8, содержащего "знак" строки. Пример:

CollapsingMergeTree(EventDate, (CounterID, EventDate, intHash32(UniqID), VisitID), 8192, Sign)

Здесь Sign - столбец, содержащий -1 для "старых" значений и 1 для "новых" значений.

При слиянии, для каждой группы идущих подряд одинаковых значений первичного ключа (столбцов, по которым сортируются данные), остаётся не более одной строки со значением столбца sign_column = -1 ("отрицательной строки") и не более одной строки со значением столбца sign_column = 1 ("положительной строки"). То есть - производится схлопывание записей из лога изменений.

Если количество положительных и отрицательных строк совпадает - то пишет первую отрицательную и последнюю положительную строку. Если положительных на 1 больше, чем отрицательных - то пишет только последнюю положительную строку. Если отрицательных на 1 больше, чем положительных - то пишет только первую отрицательную строку. Иначе - логическая ошибка, и ни одна из таких строк не пишется. (Логическая ошибка может возникать, если случайно один кусок лога был вставлен более одного раза. Поэтому, об ошибке всего лишь пишется в лог сервера, и слияние продолжает работать.)

Как видно, от схлопывания не должны меняться результаты расчётов статистик. Изменения постепенно схлопываются так что в конце-концов, для почти каждого объекта, остаются лишь его последние значения. По сравнению с MergeTree, движок CollapsingMergeTree позволяет в несколько раз уменьшить объём данных.

Существует несколько способов получения полностью "схлопнутых" данных из таблицы типа CollapsingMergeTree:

  1. Написать запрос с GROUP BY и агрегатными функциями, учитывающими знак. Например, чтобы посчитать количество, надо вместо count() написать sum(Sign); чтобы посчитать сумму чего-либо, надо вместо sum(x) написать sum(Sign * x) и т. п., а также добавить HAVING sum(Sign) > 0. Не все величины можно посчитать подобным образом. Например, агрегатные функции min, max не могут быть переписаны.
  2. Если необходимо вынимать данные без агрегации (например, проверить наличие строк, самые новые значения которых удовлетворяют некоторым условиям), можно использовать модификатор FINAL для секции FROM. Это вариант существенно менее эффективен.