Обновления из нескольких потоков для модели Unique
Сценарии использования и основные проблемы
В хранилищах данных с широкими таблицами разные столбцы одной таблицы часто поступают из разных источников данных или разных конвейеров обработки. Типичные примеры:
- Поток реального времени: непрерывно обновляет часть полей таблицы (например, столбцы, связанные с поведением пользователей).
- Офлайн-поток (или поток по требованию): обновляет другие поля таблицы в определённые моменты времени (например, столбцы, связанные с профилями пользователей или тегами).
В таком сценарии несколько потоков данных одновременно записывают данные в одну таблицу с первичным ключом, что приводит к двум основным проблемам:
| Проблема | Описание |
|---|---|
| Перезапись при одновременном обновлении | Разные потоки данных поступают в разное время. Простая агрегация REPLACE не гарантирует, что данные каждого потока будут обновляться в порядке версий. |
| Высокие затраты на объединение данных | Практически невозможно дождаться, пока данные всех столбцов будут собраны перед записью. Ожидание может оказаться слишком долгим и неприемлемым для бизнеса. |
Модель Unique в Doris изначально поддерживает контроль версий на основе столбца Sequence, но позволяет определить только один глобальный столбец Sequence. Это не даёт возможности независимо управлять версиями обновлений разных столбцов из разных потоков данных. Для решения этой задачи в Doris предусмотрена функция Sequence Mapping.
Решение: Sequence Mapping
Sequence Mapping — это механизм одновременного обновления из нескольких потоков, поддерживаемый моделью Unique в Doris. Он позволяет назначить разные столбцы Sequence разным столбцам данных и тем самым обеспечить независимый контроль версий для каждого потока данных без взаимного влияния.
Принцип работы
Предположим, таблица модели Unique содержит следующие столбцы:
| Столбец | A | B | C | D | E | s1 | s2 |
|---|---|---|---|---|---|---|---|
| Роль | Key | Key | Value | Value | Value | Sequence | Sequence |
- Поток данных 1 записывает данные в столбцы
A, B, C, Dи используетs1в качестве столбца контроля версий. - Поток данных 2 записывает данные в столбцы
A, B, Eи используетs2в качестве столбца контроля версий.
При таком сопоставлении:
s1управляет версией столбцовC, D: только еслиs1новых данных больше, чемs1существующих данных, обновляютсяC, D, s1.s2управляет версией столбцаE: только еслиs2новых данных больше, чемs2существующих данных, обновляютсяE, s2.- Эти два потока не влияют друг на друга, а запросы могут читать актуальные данные всех столбцов.
Пример использования
1. Создание таблицы с поддержкой Sequence Mapping
Используйте свойство sequence_mapping.<sequence_col> в разделе PROPERTIES, чтобы задать сопоставление между столбцом Sequence и столбцами Value, которыми он управляет.
В следующем примере создаётся таблица модели Unique, в которой версиями столбцов c, d управляет s1, а версиями столбца e управляет s2:
CREATE TABLE `upsert_test` (
`a` bigint(20) NULL COMMENT "",
`b` int(11) NULL COMMENT "",
`c` int(11) NULL COMMENT "",
`d` int(11) NULL COMMENT "",
`e` int(11) NULL COMMENT "",
`s1` int(11) NULL COMMENT "",
`s2` int(11) NULL COMMENT ""
) ENGINE=OLAP
UNIQUE KEY(`a`, `b`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`a`, `b`) BUCKETS 1
PROPERTIES (
"enable_unique_key_merge_on_write" = "false",
"light_schema_change" = "true",
"replication_num" = "1",
"sequence_mapping.s1" = "c,d",
"sequence_mapping.s2" = "e"
);
Поддерживаемые типы столбцов Sequence: целочисленные типы,
DATEиDATETIME. После создания столбца его тип изменить нельзя.
После создания схема таблицы выглядит следующим образом:
MySQL > desc upsert_test;
+-------+--------+------+-------+---------+---------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------+------+-------+---------+---------+
| a | bigint | Yes | true | NULL | |
| b | int | Yes | true | NULL | |
| c | int | Yes | false | NULL | REPLACE |
| d | int | Yes | false | NULL | REPLACE |
| e | int | Yes | false | NULL | REPLACE |
| s1 | int | Yes | false | NULL | REPLACE |
| s2 | int | Yes | false | NULL | REPLACE |
+-------+--------+------+-------+---------+---------+
2. Запись данных и выполнение запросов
Следующие пять операций вставки показывают, как разные значения Sequence влияют на обновление каждого столбца.
Вставка 1: записываются только c, d, s1, а e, s2 не записываются.
MySQL > insert into upsert_test(a, b, c, d, s1) values (1,1,2,2,2);
Query OK, 1 row affected (0.080 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 2 | 2 | NULL | 2 | NULL |
+------+------+------+------+------+------+------+
Вставка 2: s1=1 меньше существующего значения s1=2, поэтому c, d, s1 не обновляются.
MySQL > insert into upsert_test(a, b, c, d, s1) values (1,1,1,1,1);
Query OK, 1 row affected (0.048 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 2 | 2 | NULL | 2 | NULL |
+------+------+------+------+------+------+------+
Вставка 3: записываются e, s2. Поскольку предыдущее значение s2 было NULL (считается наименьшим значением), e, s2 обновляются.
MySQL > insert into upsert_test(a, b, e, s2) values (1,1,2,2);
Query OK, 1 row affected (0.043 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 2 | 2 | 2 | 2 | 2 |
+------+------+------+------+------+------+------+
Вставка 4: s1=3 больше существующего значения s1=2, поэтому c, d, s1 обновляются, а e, s2 остаются без изменений.
MySQL > insert into upsert_test(a, b, c, d, s1) values (1,1,3,3,3);
Query OK, 1 row affected (0.049 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 3 | 3 | 2 | 3 | 2 |
+------+------+------+------+------+------+------+
Вставка 5: записываются обе группы столбцов. И s1=4, и s2=4 больше существующих значений, поэтому обновляются все столбцы.
MySQL > insert into upsert_test(a, b, c, d, s1, e, s2) values (1,1,5,5,4,5,4);
Query OK, 1 row affected (0.050 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | e | s1 | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 5 | 5 | 5 | 4 | 4 |
+------+------+------+------+------+------+------+
Сводка результатов обновления:
| Операция | Условие срабатывания | Обновлённые столбцы |
|---|---|---|
| Вставка 1 | Первая запись c, d, s1 | c, d, s1 |
| Вставка 2 | Новое значение s1 < существующее значение s1 | Нет |
| Вставка 3 | Первая запись e, s2 | e, s2 |
| Вставка 4 | Новое значение s1 > существующее значение s1 | c, d, s1 |
| Вставка 5 | Оба новых значения s1 и s2 больше существующих значений | c, d, s1, e, s2 |
3. Добавление и удаление столбцов
Таблица с Sequence Mapping поддерживает динамическое добавление и удаление столбцов и сопоставлений с помощью ALTER TABLE.
Создание исходной таблицы: таблица содержит только одно сопоставление s1 -> c,d.
CREATE TABLE `upsert_test` (
`a` bigint(20) NULL COMMENT "",
`b` int(11) NULL COMMENT "",
`c` int(11) NULL COMMENT "",
`d` int(11) NULL COMMENT "",
`s1` int(11) NULL COMMENT ""
) ENGINE=OLAP
UNIQUE KEY(`a`, `b`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`a`, `b`) BUCKETS 1
PROPERTIES (
"enable_unique_key_merge_on_write" = "false",
"light_schema_change" = "true",
"replication_num" = "1",
"sequence_mapping.s1" = "c,d"
);
Запись исходных данных:
MySQL > insert into upsert_test(a, b, c, d, s1) values (1,1,1,1,1),(1,1,3,3,3),(1,1,2,2,2);
Query OK, 3 rows affected (0.101 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+
| a | b | c | d | s1 |
+------+------+------+------+------+
| 1 | 1 | 3 | 3 | 3 |
+------+------+------+------+------+
Добавление столбцов и сопоставления: используйте ALTER TABLE, чтобы добавить столбцы e, s2 и одновременно задать сопоставление s2 -> e.
MySQL > alter table upsert_test add column (e int(11) NULL, s2 bigint) PROPERTIES('sequence_mapping.s2' = 'e');
Query OK, 0 rows affected (0.011 sec)
MySQL > desc upsert_test;
+-------+--------+------+-------+---------+---------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------+------+-------+---------+---------+
| a | bigint | Yes | true | NULL | |
| b | int | Yes | true | NULL | |
| c | int | Yes | false | NULL | REPLACE |
| d | int | Yes | false | NULL | REPLACE |
| s1 | int | Yes | false | NULL | REPLACE |
| e | int | Yes | false | NULL | REPLACE |
| s2 | bigint | Yes | false | NULL | REPLACE |
+-------+--------+------+-------+---------+---------+
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | s1 | e | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 3 | 3 | 3 | NULL | NULL |
+------+------+------+------+------+------+------+
Запись после вступления нового сопоставления в силу:
MySQL > insert into upsert_test(a, b, e, s2) values (1,1,2,2);
Query OK, 1 row affected (0.052 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | s1 | e | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 3 | 3 | 3 | 2 | 2 |
+------+------+------+------+------+------+------+
MySQL > insert into upsert_test(a, b, c, d, s1, e, s2) values (1,1,5,5,4,5,4);
Query OK, 1 row affected (0.050 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+------+
| a | b | c | d | s1 | e | s2 |
+------+------+------+------+------+------+------+
| 1 | 1 | 5 | 5 | 4 | 5 | 4 |
+------+------+------+------+------+------+------+
Удаление столбцов: удалите столбец Value e, после чего соответствующее сопоставление перестанет действовать. Затем удалите столбец Sequence s2.
MySQL > alter table upsert_test drop column e;
Query OK, 0 rows affected (0.006 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+------+
| a | b | c | d | s1 | s2 |
+------+------+------+------+------+------+
| 1 | 1 | 5 | 5 | 4 | 4 |
+------+------+------+------+------+------+
MySQL > alter table upsert_test drop column s2;
Query OK, 0 rows affected (0.005 sec)
MySQL > select * from upsert_test;
+------+------+------+------+------+
| a | b | c | d | s1 |
+------+------+------+------+------+
| 1 | 1 | 5 | 5 | 4 |
+------+------+------+------+------+
Ограничения использования
При использовании Sequence Mapping учитывайте следующие ограничения:
| Категория | Ограничение |
|---|---|
| Настройки при создании таблицы | Параметр light_schema_change должен быть включён. Если свойство sequence_mapping не задано при создании таблицы, включить его позднее нельзя. |
| Типы столбцов | Столбцы Sequence поддерживают только целочисленные типы и типы даты и времени (DATE, DATETIME); после создания столбца его тип изменить нельзя. |
| Роли столбцов | Ни столбцы Sequence, ни сопоставленные им столбцы не могут быть столбцами Key. Все столбцы, кроме Key, должны быть сопоставлены со столбцом Sequence. |
| Связи сопоставления | Наборы столбцов, сопоставленных разным столбцам Sequence, не могут пересекаться (например, d нельзя сопоставить одновременно с s1 и s2). После создания сопоставления его нельзя изменить (например, столбец, уже сопоставленный с s1, нельзя переназначить столбцу s2). |
| Ограничения DDL | Переименование столбцов не поддерживается. Создание Rollup не поддерживается. |
| Режим хранения | В настоящее время поддерживаются только таблицы MOR (Merge-on-Read). Одновременное использование с глобальным столбцом Sequence не поддерживается. Пакетные операции удаления не поддерживаются. |
| Семантика загрузки | Поля, не указанные при загрузке, автоматически заполняются значениями по умолчанию или NULL. При сравнении столбцов Sequence значение NULL считается наименьшим значением. |