Управление конкурентными обновлениями в модели 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 и TIMESTAMP_NS. После создания таблицы тип столбца изменить нельзя.
Пример использования
Следующий пример показывает полный процесс с использованием Stream Load.
1. Создание таблицы с поддержкой столбца Sequence
Создайте таблицу test_table модели Unique и сопоставьте столбец 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 из соответствующей записи в исторических данных в качестве значения столбца Sequence для обновлённой строки.
- Если в исторических данных нет записи с таким же Key, Doris автоматически подставляет
nullили значение по умолчанию.
-
Гарантия корректности при параллельных загрузках: при параллельных загрузках Doris использует MVCC для обеспечения корректности данных. Если два пакета загрузки обновляют разные столбцы одного и того же Key, то после успешного завершения задания загрузки с меньшим номером версии задание с большим номером версии использует строки, записанные заданием с меньшим номером версии для того же Key, чтобы заполнить недостающие значения.