Обновление данных из нескольких потоков в модели 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иTIMESTAMP_NS. После создания столбца изменить его тип нельзя.
После создания схема таблицы выглядит следующим образом:
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, TIMESTAMP_NS); после создания изменить тип нельзя. |
| Роли столбцов | Ни столбцы Sequence, ни сопоставленные столбцы не могут быть столбцами Key. Все столбцы, не относящиеся к Key, должны быть сопоставлены со столбцом Sequence. |
| Правила сопоставления | Наборы столбцов, сопоставленных разным столбцам Sequence, не могут пересекаться (например, d нельзя сопоставить одновременно с s1 и s2). После создания сопоставления его нельзя изменить (например, столбец, уже сопоставленный с s1, нельзя переназначить на s2). |
| Ограничения DDL | Переименование столбцов не поддерживается. Создание Rollup не поддерживается. |
| Режим хранения | В настоящее время поддерживаются только таблицы MOR (Merge-on-Read). Совместное использование с глобальным столбцом Sequence не поддерживается. Пакетные операции удаления не поддерживаются. |
| Семантика загрузки | Поля, отсутствующие в загружаемых данных, автоматически заполняются значениями по умолчанию или NULL. При сравнении столбцов Sequence NULL считается наименьшим значением. |