Управление параллельными обновлениями в модели Unique Key
В модели Unique Key Doris предоставляет комплексные механизмы управления параллельными обновлениями. Они предназначены прежде всего для следующих трёх типичных сценариев:
| Пользовательский сценарий | Проблема | Решение Doris |
|---|---|---|
| Многократная загрузка данных с одинаковым первичным ключом | Необходимо определить, какая запись в итоге сохранится при параллельных загрузках | MVCC (многоверсионное управление конкурентным доступом): номер версии определяет порядок перезаписи |
| Поступление данных не по порядку при многопоточной синхронизации | Более старые данные поступают позже и ошибочно перезаписывают более новые | Столбец Sequence: пользователь задаёт порядок замены, и приоритет получает строка с большим значением Sequence |
| Параллельные операции UPDATE над одной таблицей | Обновления на уровне строк могут приводить к появлению «грязных» данных, поэтому по умолчанию они выполняются последовательно | Блокировка на уровне таблицы обеспечивает уровень изоляции Serializable; ограничение параллельного выполнения можно снять |
В следующих разделах описаны принципы работы и использование этих механизмов.
MVCC: многоверсионное управление конкурентным доступом
Doris использует многоверсионное управление конкурентным доступом (MVCC) для управления параллельными обновлениями:
- Каждой операции записи данных назначается транзакция записи, которая гарантирует атомарность операции: запись либо полностью выполняется, либо полностью завершается с ошибкой.
- При фиксации транзакции записи система назначает ей номер версии.
- Если при многократной загрузке данных в таблицу Unique Key встречаются одинаковые первичные ключи, Doris определяет порядок перезаписи по номеру версии: данные с большим номером версии перезаписывают данные с меньшим номером версии.
Управление параллельным выполнением UPDATE
Поведение по умолчанию
По умолчанию Doris не разрешает параллельно выполнять несколько операций UPDATE над одной таблицей.
Причина в том, что сейчас Doris поддерживает обновления на уровне строк: даже если указать только SET v2 = 1, все остальные столбцы Value также перезаписываются, хотя их значения не меняются. Если две операции UPDATE одновременно обновляют одну строку, поведение не определено и существует риск появления «грязных» данных.
Поэтому оператор UPDATE по умолчанию обеспечивает уровень изоляции Serializable с помощью блокировки на уровне таблицы, и несколько операций UPDATE могут выполняться только последовательно.
Снятие ограничения параллельного выполнения
На практике, если вы можете гарантировать, что параллельные обновления не будут одновременно затрагивать одну строку, ограничение параллельного выполнения можно снять, изменив параметр конфигурации FE:
| Параметр конфигурации | Значение по умолчанию | Описание |
|---|---|---|
enable_concurrent_update | false | Значение true разрешает параллельное выполнение UPDATE, но транзакционные гарантии больше не предоставляются |
Столбец Sequence
Зачем нужен столбец Sequence
Модель Unique предназначена прежде всего для сценариев, требующих уникальных первичных ключей, и гарантирует их уникальность. Однако в следующих ситуациях полагаться только на номер версии загрузки недостаточно:
- При синхронизации данных с Doris в нескольких параллельных потоках данные из разных потоков могут поступать не по порядку.
- Более старые данные могут поступить позже и ошибочно перезаписать более новые.
- Когда данные с одинаковым первичным ключом загружаются в одном пакете или в разных пакетах, порядок замены не гарантируется, поэтому итоговый результат недетерминирован.
Для решения этой проблемы Doris позволяет указать столбец Sequence при загрузке. Для строк с одинаковыми значениями столбцов Key замена выполняется на основе значения столбца Sequence: большее значение заменяет меньшее, а в обратном случае замена не происходит. Таким образом, вы сами определяете порядок замены.
Для реализации этого механизма Doris добавляет скрытый столбец __DORIS_SEQUENCE_COL__. Тип столбца пользователь задаёт при создании таблицы, а значение определяется во время загрузки. Затем Doris использует это значение, чтобы выбрать строку среди строк с одинаковыми значениями столбцов Key.
В настоящее время столбец Sequence поддерживается только в модели Unique.
Включение столбца Sequence
Включить его можно следующими способами:
| Сценарий | Действие |
|---|---|
| Включение в новой таблице | Задайте function_column.sequence_col или function_column.sequence_type в разделе PROPERTIES оператора CREATE TABLE |
| Включение в существующей таблице | Выполните ALTER TABLE example_db.my_table ENABLE FEATURE "SEQUENCE_LOAD" WITH PROPERTIES ("function_column.sequence_type" = "Date") |
| Проверка включения | Выполните SET show_hidden_columns=true, затем desc tablename. Если результат содержит __DORIS_SEQUENCE_COL__, столбец включён |
Эти два свойства различаются следующим образом:
| Свойство | Назначение | Должна ли схема содержать соответствующий столбец |
|---|---|---|
function_column.sequence_col | Сопоставляет столбец Sequence с существующим столбцом таблицы | Да |
function_column.sequence_type | Задаёт только тип столбца Sequence, значения которого хранятся в скрытом столбце | Нет |
Поддерживаемые типы столбца: целочисленные типы, DATE и DATETIME. После создания таблицы изменить тип столбца нельзя.
Пример использования
В следующем примере показан полный процесс с использованием Stream Load.
1. Создание таблицы с поддержкой столбца Sequence
Создайте таблицу модели Unique test_table и сопоставьте столбец Sequence со столбцом modify_date этой таблицы:
CREATE TABLE test.test_table
(
user_id bigint,
date date,
group_id bigint,
modify_date date,
keyword VARCHAR(128)
)
UNIQUE KEY(user_id, date, group_id)
DISTRIBUTED BY HASH (user_id) BUCKETS 32
PROPERTIES(
"function_column.sequence_col" = 'modify_date',
"replication_num" = "1",
"in_memory" = "false"
);
Полученная схема таблицы выглядит следующим образом:
MySQL> desc test_table;
+-------------+--------------+------+-------+---------+---------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-------+---------+---------+
| user_id | BIGINT | No | true | NULL | |
| date | DATE | No | true | NULL | |
| group_id | BIGINT | No | true | NULL | |
| modify_date | DATE | No | false | NULL | REPLACE |
| keyword | VARCHAR(128) | No | false | NULL | REPLACE |
+-------------+--------------+------+-------+---------+---------+
Если вы не хотите, чтобы этот столбец отображался в схеме таблицы, вместо этого укажите его тип с помощью function_column.sequence_type:
PROPERTIES (
"function_column.sequence_type" = 'Date'
);
2. Загрузка данных и проверка работы Sequence
При сопоставлении с существующим столбцом (function_column.sequence_col) команде загрузки не нужны дополнительные параметры. Загрузите следующие данные с помощью Stream Load:
1 2020-02-22 1 2020-02-21 a
1 2020-02-22 1 2020-02-22 b
1 2020-02-22 1 2020-03-05 c
1 2020-02-22 1 2020-02-26 d
1 2020-02-22 1 2020-02-23 e
1 2020-02-22 1 2020-02-24 b
Команда Stream Load:
curl --location-trusted -u root: -T testData http://host:port/api/test/test_table/_stream_load
Результат запроса:
MySQL> select * from test_table;
+---------+------------+----------+-------------+---------+
| user_id | date | group_id | modify_date | keyword |
+---------+------------+----------+-------------+---------+
| 1 | 2020-02-22 | 1 | 2020-03-05 | c |
+---------+------------+----------+-------------+---------+
Поскольку 2020-03-05 — наибольшее значение в столбце Sequence (modify_date), в столбце keyword в итоге сохраняется c.
3. Указание столбца Sequence при разных способах загрузки
Если при создании таблицы используется function_column.sequence_type, то есть столбец Sequence не сопоставлен напрямую со столбцом таблицы, при загрузке необходимо явно указать сопоставление столбца Sequence со столбцом данных.
Stream Load
В заголовке используйте function_column.sequence_col, чтобы задать сопоставление source_sequence со скрытым столбцом:
curl --location-trusted -u root \
-H "columns: k1,k2,source_sequence,v1,v2" \
-H "function_column.sequence_col: source_sequence" \
-T testData http://host:port/api/testDb/testTbl/_stream_load
Broker Load
Укажите поле source_sequence для сопоставления со скрытым столбцом в предложении ORDER BY:
LOAD LABEL db1.label1
(
DATA INFILE("hdfs://host:port/user/data/*/test.txt")
INTO TABLE `tbl1`
COLUMNS TERMINATED BY ","
(k1,k2,source_sequence,v1,v2)
ORDER BY source_sequence
)
WITH BROKER 'broker'
(
"username"="user",
"password"="pass"
)
PROPERTIES
(
"timeout" = "3600"
);
Routine Load
Сопоставление задаётся так же, как описано выше. Пример:
CREATE ROUTINE LOAD example_db.test1 ON example_tbl
[WITH MERGE|APPEND|DELETE]
COLUMNS(k1, k2, source_sequence, v1, v2),
WHERE k1 100 and k2 like "%doris%"
[ORDER BY source_sequence]
PROPERTIES
(
"desired_concurrent_number"="3",
"max_batch_interval" = "20",
"max_batch_rows" = "300000",
"max_batch_size" = "209715200",
"strict_mode" = "false"
)
FROM KAFKA
(
"kafka_broker_list" = "broker1:9092,broker2:9092,broker3:9092",
"kafka_topic" = "my_topic",
"kafka_partitions" = "0,1,2,3",
"kafka_offsets" = "101,0,0,200"
);
4. Проверка гарантии порядка замены
Продолжая предыдущий пример, загрузите следующие данные, в которых все значения Sequence меньше текущего максимального значения 2020-03-05:
1 2020-02-22 1 2020-02-22 a
1 2020-02-22 1 2020-02-23 b
Результат запроса:
MySQL [test]> select * from test_table;
+---------+------------+----------+-------------+---------+
| user_id | date | group_id | modify_date | keyword |
+---------+------------+----------+-------------+---------+
| 1 | 2020-02-22 | 1 | 2020-03-05 | c |
+---------+------------+----------+-------------+---------+
Поскольку все значения Sequence в этой загрузке меньше текущего максимального значения 2020-03-05, результат остаётся прежним.
Теперь загрузите данные с большим значением Sequence:
1 2020-02-22 1 2020-02-22 a
1 2020-02-22 1 2020-03-23 w
Результат запроса:
MySQL [test]> select * from test_table;
+---------+------------+----------+-------------+---------+
| user_id | date | group_id | modify_date | keyword |
+---------+------------+----------+-------------+---------+
| 1 | 2020-02-22 | 1 | 2020-03-23 | w |
+---------+------------+----------+-------------+---------+
Поскольку значение Sequence 2020-03-23 в новых загруженных данных больше текущего максимального значения 2020-03-05, исходные данные заменяются.
Вывод: при загрузке Doris сравнивает значения столбца Sequence во всех пакетах и записывает в таблицу запись с наибольшим значением.
Примечания по использованию
-
Столбец Sequence необходимо указывать явно: в заданиях загрузки, таких как Stream Load и Broker Load, а также в операторах
INSERT, обновляющих строки, необходимо явно указывать столбец Sequence, если его значение по умолчанию не равноCURRENT_TIMESTAMP. В противном случае вы получите следующую ошибку:Table test_tbl has sequence column, need to specify the sequence column -
Отключение проверки обязательного указания столбца Sequence: в таких сценариях, как репликация таблиц или внутренняя миграция данных, можно отключить проверку обязательного указания столбца Sequence в операторах
INSERTс помощью переменной сеанса:set require_sequence_in_insert = false; -
Совместимость с частичным обновлением столбцов: начиная с версии 2.0 реализация Merge-on-Write таблиц Unique Key в Doris поддерживает частичное обновление столбцов. При такой загрузке каждый раз можно обновлять только часть столбцов, поэтому столбец Sequence указывать необязательно:
- Если задание загрузки включает столбец Sequence, поведение не меняется.
- Если задание загрузки не включает столбец Sequence, Doris использует значение этого столбца из соответствующей существующей записи в качестве значения столбца Sequence обновлённой строки.
- Если в существующих данных нет записи с таким же Key, Doris автоматически подставляет
nullили значение по умолчанию.
-
Гарантия корректности при параллельных загрузках: при параллельных загрузках Doris использует MVCC для обеспечения корректности данных. Если два пакета загрузки обновляют разные столбцы одного и того же Key, то после успешного завершения задания с меньшим номером версии задание с большим номером версии использует строки с тем же Key, записанные заданием с меньшим номером версии, чтобы заполнить недостающие значения.