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
делает это во время слияния частей данных.
Чтобы узнать, почему нам нужны две строки для каждого изменения, смотрите Алгоритм.
Примечания по использованию
- Программа, которая записывает данные, должна помнить состояние объекта, чтобы иметь возможность его отменить. Строка "отмена" должна содержать копии полей первичного ключа и версию строки "состояния" с противоположным
Sign
. Это увеличивает первоначальный объём хранилища, но позволяет быстро записывать данные. - Длинные растущие массивы в колонках снижают эффективность движка из-за нагрузки за счёт записи. Чем проще данные, тем лучше эффективность.
- Результаты
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
.
Этот способ выбора данных является очень неэффективным. Не используйте его для больших таблиц.