mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-24 00:22:29 +00:00
230 lines
17 KiB
Markdown
230 lines
17 KiB
Markdown
# VersionedCollapsingMergeTree
|
||
|
||
Движок:
|
||
|
||
- Позволяет быстро записывать постоянно изменяющиеся состояния объектов.
|
||
- Удаляет старые состояния объектов в фоновом режиме. Это значительно сокращает объем хранения.
|
||
|
||
Подробнее читайте в разделе [Collapsing](#table_engines_versionedcollapsingmergetree).
|
||
|
||
Движок наследует функциональность от [MergeTree](mergetree.md#table_engines-mergetree) и добавляет в алгоритм слияния кусков данных логику сворачивания (удаления) строк. `VersionedCollapsingMergeTree` предназначен для тех же задач, что и [CollapsingMergeTree](collapsingmergetree.md), но использует другой алгоритм свёртывания, который позволяет вставлять данные в любом порядке в несколько потоков. В частности, столбец `Version` помогает свернуть строки правильно, даже если они вставлены в неправильном порядке. `CollapsingMergeTree` требует строго последовательную вставку данных.
|
||
|
||
## Создание таблицы
|
||
|
||
```sql
|
||
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
|
||
(
|
||
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
|
||
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
|
||
...
|
||
) ENGINE = VersionedCollapsingMergeTree(sign, version)
|
||
[PARTITION BY expr]
|
||
[ORDER BY expr]
|
||
[SAMPLE BY expr]
|
||
[SETTINGS name=value, ...]
|
||
```
|
||
|
||
Подробности про `CREATE TABLE` смотрите в [описании запроса](../../query_language/create.md).
|
||
|
||
**Параметры движка**
|
||
|
||
```
|
||
VersionedCollapsingMergeTree(sign, version)
|
||
```
|
||
|
||
- `sign` — Имя столбца с типом строки: `1` — строка состояния, `-1` — строка отмены состояния.
|
||
|
||
Тип данных столбца должен быть `Int8`.
|
||
|
||
- `version` — имя столбца с версией состояния объекта.
|
||
|
||
Тип данных столбца должен быть `UInt*`.
|
||
|
||
**Секции запроса**
|
||
|
||
При создании таблицы `VersionedСollapsingMergeTree` используются те же [секции](mergetree.md) запроса, что и при создании таблицы `MergeTree` .
|
||
|
||
<details markdown="1"><summary>Устаревший способ создания таблицы</summary>
|
||
|
||
!!! warning "Внимание"
|
||
Не используйте этот метод в новых проектах. По возможности переключите старые проекты на метод, описанный выше.
|
||
|
||
```sql
|
||
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
|
||
(
|
||
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
|
||
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
|
||
...
|
||
) ENGINE [=] VersionedCollapsingMergeTree(date-column [, sampling_expression], (primary, key), index_granularity, sign, version)
|
||
```
|
||
|
||
Все параметры, за исключением `sign` и `version` имеют то же значение, что и в `MergeTree`.
|
||
|
||
- `sign` — Имя столбца с типом строки: `1` — строка состояния, `-1` — строка отмены состояния.
|
||
|
||
Тип данных столбца — `Int8`.
|
||
|
||
- `version` — имя столбца с версией состояния объекта.
|
||
|
||
Тип данных столбца должен быть `UInt*`.
|
||
|
||
</details>
|
||
|
||
## Сворачивание (удаление) строк {#table_engines_versionedcollapsingmergetree}
|
||
|
||
### Данные
|
||
|
||
Рассмотрим ситуацию, когда необходимо сохранять постоянно изменяющиеся данные для какого-либо объекта. Разумно иметь одну строку для объекта и обновлять эту строку при каждом изменении. Однако операция обновления является дорогостоящей и медленной для СУБД, поскольку требует перезаписи данных в хранилище. Обновление неприемлемо, если требуется быстро записывать данные, но можно записывать изменения в объект последовательно следующим образом.
|
||
|
||
Используйте столбец `Sign` при записи строки. Если `Sign = 1`, то это означает, что строка является состоянием объекта, назовём её строкой состояния. Если `Sign = -1`, то это означает отмену состояния объекта с теми же атрибутами, назовём её строкой отмены состояния. Также используйте столбец `Version`, который должен идентифицировать каждое состояние объекта отдельным номером.
|
||
|
||
Например, мы хотим рассчитать, сколько страниц пользователи посетили на каком-либо сайте и как долго они там находились. В какой-то момент времени мы записываем следующую строку состояния пользовательской активности:
|
||
|
||
```
|
||
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
|
||
│ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 |
|
||
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
|
||
```
|
||
|
||
Через некоторое время мы регистрируем изменение активности пользователя и записываем его следующими двумя строками.
|
||
|
||
```
|
||
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
|
||
│ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 |
|
||
│ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 |
|
||
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
|
||
```
|
||
|
||
Первая строка отменяет предыдущее состояние объекта (пользователя). Она должна копировать все поля отменяемого состояния за исключением `Sign`.
|
||
|
||
Вторая строка содержит текущее состояние.
|
||
|
||
Поскольку нам нужно только последнее состояние активности пользователя, строки
|
||
|
||
```
|
||
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
|
||
│ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 |
|
||
│ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 |
|
||
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
|
||
```
|
||
|
||
можно удалить, сворачивая (удаляя) устаревшее состояние объекта. `VersionedCollapsingMergeTree` делает это при слиянии кусков данных.
|
||
|
||
Чтобы узнать, зачем нам нужны две строки для каждого изменения, см. раздел [Алгоритм](#table_engines-versionedcollapsingmergetree-algorithm).
|
||
|
||
**Примечания по использованию**
|
||
|
||
1. Программа, которая записывает данные, должна помнить состояние объекта, чтобы иметь возможность отменить его. Строка отмены состояния должна быть копией предыдущей строки состояния с противоположным значением `Sign`. Это увеличивает начальный размер хранилища, но позволяет быстро записывать данные.
|
||
2. Длинные растущие массивы в столбцах снижают эффективность работы движка за счёт нагрузки на запись. Чем проще данные, тем выше эффективность.
|
||
3. `SELECT` результаты сильно зависят от согласованности истории изменений объекта. Будьте точны при подготовке данных для вставки. Вы можете получить непредсказуемые результаты с несогласованными данными, такими как отрицательные значения для неотрицательных метрик, таких как глубина сеанса.
|
||
|
||
### Алгоритм {#table_engines-versionedcollapsingmergetree-algorithm}
|
||
|
||
Когда ClickHouse объединяет куски данных, он удаляет каждую пару строк, которые имеют один и тот же первичный ключ и версию и разный `Sign`. Порядок строк не имеет значения.
|
||
|
||
Когда ClickHouse вставляет данные, он упорядочивает строки по первичному ключу. Если столбец `Version` не находится в первичном ключе, ClickHouse добавляет его к первичному ключу неявно как последнее поле и использует для сортировки.
|
||
|
||
## Выборка данных
|
||
|
||
ClickHouse не гарантирует, что все строки с одинаковым первичным ключом будут находиться в одном результирующем куске данных или даже на одном физическом сервере. Это справедливо как для записи данных, так и для последующего слияния кусков данных. Кроме того, ClickHouse обрабатывает запросы `SELECT` несколькими потоками, и не может предсказать порядок строк в конечной выборке. Это означает, что если необходимо получить полностью "свернутые" данные из таблицы `VersionedCollapsingMergeTree`, то требуется агрегирование.
|
||
|
||
Для завершения свертывания добавьте в запрос секцию `GROUP BY` и агрегатные функции, которые учитывают знак. Например, для расчета количества используйте `sum(Sign)` вместо`count()`. Чтобы вычислить сумму чего-либо, используйте `sum(Sign * x)` вместо`sum(х)`, а также добавьте `HAVING sum(Sign) > 0` .
|
||
|
||
Таким образом можно вычислять агрегации `count`, `sum` и `avg`. Агрегация `uniq` может вычисляться, если объект имеет хотя бы одно не свернутое состояние. Невозможно вычислить агрегации `min` и `max` поскольку`VersionedCollapsingMergeTree` не сохраняет историю значений для свернутых состояний.
|
||
|
||
Если необходимо выбирать данные без агрегации (например, проверить наличие строк, последние значения которых удовлетворяют некоторым условиям), можно использовать модификатор `FINAL` для секции `FROM`. Такой подход неэффективен и не должен использоваться с большими таблицами.
|
||
|
||
## Пример использования
|
||
|
||
Данные для примера:
|
||
|
||
```
|
||
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
|
||
│ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 |
|
||
│ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 |
|
||
│ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 |
|
||
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
|
||
```
|
||
|
||
Создание таблицы:
|
||
|
||
```sql
|
||
CREATE TABLE UAct
|
||
(
|
||
UserID UInt64,
|
||
PageViews UInt8,
|
||
Duration UInt8,
|
||
Sign Int8,
|
||
Version UInt8
|
||
)
|
||
ENGINE = VersionedCollapsingMergeTree(Sign, Version)
|
||
ORDER BY UserID
|
||
```
|
||
|
||
Вставка данных:
|
||
|
||
```sql
|
||
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1, 1)
|
||
```
|
||
|
||
```sql
|
||
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1, 1),(4324182021466249494, 6, 185, 1, 2)
|
||
```
|
||
|
||
Мы используем два запроса `INSERT` для создания двух различных кусков данных. Если мы вставляем данные с помощью одного запроса, ClickHouse создаёт один кусок данных и не будет выполнять слияние.
|
||
|
||
Получение данных:
|
||
|
||
```
|
||
SELECT * FROM UAct
|
||
```
|
||
|
||
```
|
||
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
|
||
│ 4324182021466249494 │ 5 │ 146 │ 1 │ 1 │
|
||
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
|
||
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
|
||
│ 4324182021466249494 │ 5 │ 146 │ -1 │ 1 │
|
||
│ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │
|
||
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
|
||
```
|
||
|
||
Что мы видим и где сворачивание?
|
||
Мы создали два куска данных, используя два запроса `INSERT`. Запрос `SELECT` был выполнен в два потока, и результатом является случайный порядок строк.
|
||
Свертывание не произошло, поскольку части данных еще не были объединены. ClickHouse объединяет части данных в неизвестный момент времени, который мы не можем предсказать.
|
||
|
||
Поэтому нам нужна агрегация:
|
||
|
||
```sql
|
||
SELECT
|
||
UserID,
|
||
sum(PageViews * Sign) AS PageViews,
|
||
sum(Duration * Sign) AS Duration,
|
||
Version
|
||
FROM UAct
|
||
GROUP BY UserID, Version
|
||
HAVING sum(Sign) > 0
|
||
```
|
||
|
||
```
|
||
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Version─┐
|
||
│ 4324182021466249494 │ 6 │ 185 │ 2 │
|
||
└─────────────────────┴───────────┴──────────┴─────────┘
|
||
```
|
||
|
||
Если нам не нужна агрегация, но мы хотим принудительно выполнить свёртку данных, то можно использовать модификатор `FINAL` для секции `FROM`.
|
||
|
||
```sql
|
||
SELECT * FROM UAct FINAL
|
||
```
|
||
|
||
```
|
||
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┬─Version─┐
|
||
│ 4324182021466249494 │ 6 │ 185 │ 1 │ 2 │
|
||
└─────────────────────┴───────────┴──────────┴──────┴─────────┘
|
||
```
|
||
|
||
Это очень неэффективный способ выбора данных. Не используйте его для больших таблиц.
|
||
|
||
[Оригинальная статья](https://clickhouse.yandex/docs/ru/operations/table_engines/versionedcollapsingmergetree/) <!--hide-->
|