Перейти к основному содержимому
Перейти к основному содержимому

VersionedCollapsingMergeTree

Этот движок:

  • Позволяет быстро записывать состояния объектов, которые постоянно меняются.
  • Удаляет старые состояния объектов в фоновом режиме. Это значительно уменьшает объем хранения.

Смотрите раздел Collapsing для подробностей.

Движок наследуется от MergeTree и добавляет логику для коллапсирования строк к алгоритму объединения частей данных. VersionedCollapsingMergeTree выполняет ту же функцию, что и CollapsingMergeTree, но использует другой алгоритм коллапсирования, который позволяет вставлять данные в любом порядке с использованием нескольких потоков. В частности, колонка Version помогает правильно коллапсировать строки, даже если они вставлены в неправильном порядке. В отличие от этого, CollapsingMergeTree позволяет только строго последовательную вставку.

Создание таблицы

Для описания параметров запроса смотрите описание запроса.

Параметры движка

ПараметрОписаниеТип
signИмя колонки с типом строки: 1 — это строка "состояния", -1 — это строка "отмены".Int8
versionИмя колонки с версией состояния объекта.Int*, UInt*, Date, Date32, DateTime или DateTime64

Классы запросов

При создании таблицы VersionedCollapsingMergeTree требуются те же классы, что и при создании таблицы MergeTree.

Устаревший метод создания таблицы
примечание

Не используйте этот метод в новых проектах. Если возможно, переключите старые проекты на описанный выше метод.

Все параметры, кроме sign и version, имеют то же значение, что и в MergeTree.

  • sign — Имя колонки с типом строки: 1 — это строка "состояния", -1 — это строка "отмены".

    Тип данных колонки — Int8.

  • version — Имя колонки с версией состояния объекта.

    Тип данных колонки должен быть UInt*.

Коллапсирование

Данные

Рассмотрим ситуацию, когда необходимо сохранить постоянно изменяющиеся данные для некоторого объекта. Разумно иметь одну строку для объекта и обновлять строку каждый раз, когда происходят изменения. Однако операция обновления является дорогой и медленной для СУБД, поскольку требует перезаписи данных в хранилище. Обновление неприемлемо, если необходимо быстро записывать данные, но можно записывать изменения для объекта последовательно следующим образом.

Используйте колонку Sign при записи строки. Если Sign = 1, это означает, что строка является состоянием объекта (назовем ее "строкой состояния"). Если Sign = -1, это указывает на отмену состояния объекта с теми же атрибутами (назовем ее "строкой отмены"). Также используйте колонку Version, которая должна идентифицировать каждое состояние объекта отдельным номером.

Например, мы хотим подсчитать, сколько страниц пользователи посетили на каком-то сайте и как долго они там находились. В какой-то момент времени мы записываем следующую строку с состоянием активности пользователя:

Спустя некоторое время мы регистрируем изменение активности пользователя и записываем его двумя строками.

Первая строка отменяет предыдущее состояние объекта (пользователя). Она должна копировать все поля отменяемого состояния, кроме Sign.

Вторая строка содержит текущее состояние.

Поскольку нам нужно только последнее состояние активности пользователя, строки

могут быть удалены, коллапсируя недействительное (старое) состояние объекта. VersionedCollapsingMergeTree делает это при объединении частей данных.

Чтобы понять, почему нам нужны две строки для каждого изменения, смотрите Алгоритм.

Заметки по использованию

  1. Программа, которая записывает данные, должна запомнить состояние объекта, чтобы иметь возможность отменить его. Строка "Отмены" должна содержать копии полей первичного ключа и версии строки "Состояние" и противоположный Sign. Это увеличивает начальный размер хранения, но позволяет быстро записывать данные.
  2. Длинные растущие массивы в колонках снижают эффективность движка из-за нагрузки на запись. Чем проще данные, тем лучше эффективность.
  3. Результаты SELECT сильно зависят от последовательности истории изменений объекта. Будьте аккуратны при подготовке данных для вставки. Вы можете получить непредсказуемые результаты с несогласованными данными, такими как отрицательные значения для неотрицательных метрик, таких как глубина сессии.

Алгоритм

Когда ClickHouse объединяет части данных, он удаляет каждую пару строк, которые имеют одинаковый первичный ключ и версию и разные Sign. Порядок строк не имеет значения.

Когда ClickHouse вставляет данные, он упорядочивает строки по первичному ключу. Если колонка Version не входит в первичный ключ, ClickHouse неявно добавляет её в первичный ключ как последнее поле и использует её для сортировки.

Выбор данных

ClickHouse не гарантирует, что все строки с одинаковым первичным ключом окажутся в одной результирующей части данных или даже на одном физическом сервере. Это верно как для записи данных, так и для последующего объединения частей данных. Кроме того, ClickHouse обрабатывает запросы SELECT с использованием нескольких потоков, и он не может предсказать порядок строк в результате. Это означает, что требуется агрегация, если необходимо получить полностью "коллапсированные" данные из таблицы VersionedCollapsingMergeTree.

Чтобы завершить коллапсирование, напишите запрос с оператором GROUP BY и агрегирующими функциями, учитывающими знак. Например, для подсчета количества используйте sum(Sign) вместо count(). Чтобы подсчитать сумму чего-либо, используйте sum(Sign * x) вместо sum(x), и добавьте HAVING sum(Sign) > 0.

Агрегаты count, sum и avg можно вычислить таким образом. Агрегат uniq можно вычислить, если у объекта есть хотя бы одно неколлапсированное состояние. Агрегаты min и max нельзя вычислить, поскольку VersionedCollapsingMergeTree не сохраняет историю значений коллапсированных состояний.

Если вам нужно извлечь данные с "коллапсированием", но без агрегации (например, чтобы проверить, присутствуют ли строки, последние значения которых соответствуют определенным условиям), вы можете использовать модификатор FINAL для оператора FROM. Этот подход неэффективен и не следует использовать для больших таблиц.

Пример использования

Пример данных:

Создание таблицы:

Вставка данных:

Мы используем два запроса INSERT, чтобы создать две разные части данных. Если мы вставим данные одним запросом, ClickHouse создаст одну часть данных и никогда не выполнит слияние.

Получение данных:

Что мы здесь видим и где коллапсированные части? Мы создали две части данных с помощью двух запросов INSERT. Запрос SELECT был выполнен в двух потоках, и результат — это случайный порядок строк. Коллапсирование не произошло, потому что части данных еще не были объединены. ClickHouse объединяет части данных в неизвестный момент времени, который мы не можем предсказать.

Поэтому нам нужна агрегация:

Если нам не нужна агрегация и мы хотим принудительно коллапсировать, мы можем использовать модификатор FINAL для оператора FROM.

Это очень неэффективный способ выборки данных. Не используйте его для больших таблиц.