CollapsingMergeTree
Описание
Двигатель CollapsingMergeTree
унаследован от MergeTree и добавляет логику для сжатия строк в процессе слияния.
Двигатель таблицы CollapsingMergeTree
асинхронно удаляет (сжимает) пары строк, если все поля в ключе сортировки (ORDER BY
) эквивалентны, кроме специального поля Sign
, которое может иметь значения 1
или -1
.
Строки без пары строк с противоположными значениями Sign
сохраняются.
Для получения дополнительной информации смотрите раздел Collapsing документа.
Этот движок может значительно уменьшить объем хранения, что, в свою очередь, увеличивает эффективность запросов SELECT
.
Параметры
Все параметры этого движка таблицы, за исключением параметра Sign
, имеют такое же значение, как и в MergeTree
.
Sign
— Название колонки с типом строки, где1
— это "строка состояния", а-1
— "строка отмены". Тип: Int8.
Создание таблицы
Устаревший метод создания таблицы
Метод ниже не рекомендуется использовать в новых проектах. Советуем, если возможно, обновить старые проекты для использования нового метода.
Sign
— Название колонки с типом строки, где 1
— это "строка состояния", а -1
— "строка отмены". Int8.
- Для описания параметров запроса смотрите описание запроса.
- При создании таблицы
CollapsingMergeTree
необходимы те же условия запроса, как и при создании таблицыMergeTree
.
Сжатие
Данные
Рассмотрим ситуацию, когда вам нужно сохранять постоянно изменяющиеся данные для некоторого объекта.
Может показаться логичным иметь одну строку на объект и обновлять ее всякий раз, когда что-то изменяется, однако операции обновления дороги и медлительны для СУБД, так как они требуют переписывания данных в хранилище.
Если нам необходимо быстро записывать данные, выполнять большое количество обновлений — это недопустимый подход, но мы всегда можем записывать изменения объекта последовательно.
Для этого мы используем специальную колонку Sign
.
- Если
Sign
=1
, это означает, что строка является "строкой состояния": строка, содержащая поля, представляющие текущий действительный статус. - Если
Sign
=-1
, это означает, что строка является "строкой отмены": строка, используемая для отмены состояния объекта с теми же атрибутами.
Например, мы хотим вычислить, сколько страниц пользователи проверили на некотором веб-сайте и как долго они их посещали. В какой-то момент мы записываем следующую строку со статусом активности пользователя:
Позже мы фиксируем изменение активности пользователя и записываем это в две строки:
Первая строка отменяет предыдущее состояние объекта (в данном случае представляющего пользователя).
Она должна скопировать все поля ключа сортировки для "отмененной" строки, кроме Sign
.
Вторая строка выше содержит текущее состояние.
Так как нам нужно только последнее состояние активности пользователя, оригинальная "строка состояния" и "строка отмены", которые мы вставили, могут быть удалены, как показано ниже, сжимая недействительное (старое) состояние объекта:
CollapsingMergeTree
выполняет именно такое сжатие во время слияния частей данных.
Причина, по которой для каждого изменения требуется две строки, обсуждается в разделе Алгоритм.
Особенности такого подхода
- Программа, которая записывает данные, должна помнить состояние объекта, чтобы иметь возможность его отменить. "Строка отмены" должна содержать копии полей ключа сортировки "строки состояния" и противоположный
Sign
. Это увеличивает начальный размер хранения, но позволяет нам быстро записывать данные. - Длинные растущие массивы в колонках уменьшают эффективность движка из-за увеличенной нагрузки на запись. Чем проще данные, тем выше эффективность.
- Результаты
SELECT
сильно зависят от согласованности истории изменений объекта. Будьте внимательны при подготовке данных для вставки. Вы можете получить непредсказуемые результаты с несогласованными данными. Например, отрицательные значения для неотрицательных метрик, таких как глубина сессии.
Алгоритм
Когда ClickHouse сливает данные частей, каждый набор последовательных строк с одинаковым ключом сортировки (ORDER BY
) сокращается до не более чем двух строк: "строки состояния" с Sign
= 1
и "строки отмены" с Sign
= -1
.
Иными словами, записи в ClickHouse сжимаются.
Для каждой результирующей части данных ClickHouse сохраняет:
1. | Первая "строка отмены" и последняя "строка состояния", если количество "строк состояния" и "строк отмены" совпадает, а последняя строка — "строка состояния". |
2. | Последняя "строка состояния", если "строк состояния" больше, чем "строк отмены". |
3. | Первая "строка отмены", если "строк отмены" больше, чем "строк состояния". |
4. | Ни одной строки в остальных случаях. |
Дополнительно, когда "строк состояния" как минимум на две больше, чем "строк отмены", или "строк отмены" как минимум на две больше, чем "строк состояния", слияние продолжается. Тем не менее, ClickHouse рассматривает эту ситуацию как логическую ошибку и фиксирует это в журнале сервера. Эта ошибка может возникнуть, если одни и те же данные вставляются более одного раза. Таким образом, сжатие не должно изменять результаты вычисления статистики. Изменения постепенно сжимаются, так что в конечном итоге остается только последнее состояние почти каждого объекта.
Столбец Sign
необходим, потому что алгоритм слияния не гарантирует, что все строки с одинаковым ключом сортировки будут находиться в одной результирующей части данных и даже на одном физическом сервере.
ClickHouse обрабатывает запросы SELECT
с помощью нескольких потоков, и он не может предсказать порядок строк в результате.
Агрегация требуется, если необходимо получить полностью "сжатые" данные из таблицы CollapsingMergeTree
.
Чтобы завершить сжатие, выполните запрос с клаузой GROUP BY
и агрегирующими функциями, учитывающими знак.
Например, для подсчета количества используйте sum(Sign)
вместо count()
.
Для вычисления суммы чего-либо используйте sum(Sign * x)
вместе с HAVING sum(Sign) > 0
вместо sum(x)
, как в примере ниже.
Агрегаты count
, sum
и avg
могут быть вычислены таким образом.
Агрегат uniq
может быть вычислен, если у объекта есть хотя бы одно не сжатое состояние.
Агрегаты min
и max
не могут быть вычислены, так как CollapsingMergeTree
не сохраняет историю сжатых состояний.
Если вам нужно извлечь данные без агрегации (например, проверить наличие строк, у которых последние значения соответствуют определенным условиям), вы можете использовать модификатор FINAL
для клаузи FROM
. Он объединит данные перед тем, как вернуть результат.
Для CollapsingMergeTree возвращается только последняя строка состояния для каждого ключа.
Примеры
Пример использования
Дан следующий пример данных:
Создадим таблицу UAct
с помощью CollapsingMergeTree
:
Затем мы вставим несколько данных:
Мы используем два запроса INSERT
, чтобы создать две разные части данных.
Если мы вставим данные одиночным запросом, ClickHouse создаст только одну часть данных и никогда не выполнит слияние.
Мы можем выбрать данные с помощью:
Давайте посмотрим на возвращенные данные и проверим, произошло ли сжатие...
С помощью двух запросов INSERT
мы создали две части данных.
Запрос SELECT
был выполнен в два потока, и мы получили случайный порядок строк.
Однако сжатие не произошло, потому что никакого слияния частей данных еще не было,
и ClickHouse выполняет слияние частей данных в фоновом режиме в неизвестный момент времени, который мы не можем предсказать.
Следовательно, нам нужно агрегирование, которое мы выполняем с помощью агрегатной функции sum
и клаузой HAVING
:
Если нам не нужно агрегирование и мы хотим принудительно выполнить сжатие, мы также можем использовать модификатор FINAL
для клаузи FROM
.
Такой способ выбора данных менее эффективен и не рекомендуется для использования с большими объемами сканируемых данных (миллионы строк).
Пример другого подхода
Идея этого подхода заключается в том, что слияния учитывают только ключевые поля.
В "строке отмены" мы можем, следовательно, указать отрицательные значения, которые уравновесят предыдущую версию строки при суммировании, не используя колонку Sign
.
Для этого примера мы воспользуемся следующим примером данных:
Для этого подхода необходимо изменить типы данных PageViews
и Duration
, чтобы хранить отрицательные значения.
Поэтому мы изменим значения этих колонок с UInt8
на Int16
, когда создаем нашу таблицу UAct
, используя CollapsingMergeTree
:
Давайте протестируем подход, вставляя данные в нашу таблицу.
Для примеров или небольших таблиц это, тем не менее, приемлемо: