Перейти к содержимому
Последнее обновление

Управление параллельными обновлениями в модели 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_updatefalseЗначение 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 во всех пакетах и записывает в таблицу запись с наибольшим значением.

Примечания по использованию​

  1. Столбец Sequence необходимо указывать явно: в заданиях загрузки, таких как Stream Load и Broker Load, а также в операторах INSERT, обновляющих строки, необходимо явно указывать столбец Sequence, если его значение по умолчанию не равно CURRENT_TIMESTAMP. В противном случае вы получите следующую ошибку:

    Table test_tbl has sequence column, need to specify the sequence column
  2. Отключение проверки обязательного указания столбца Sequence: в таких сценариях, как репликация таблиц или внутренняя миграция данных, можно отключить проверку обязательного указания столбца Sequence в операторах INSERT с помощью переменной сеанса:

    set require_sequence_in_insert = false;
  3. Совместимость с частичным обновлением столбцов: начиная с версии 2.0 реализация Merge-on-Write таблиц Unique Key в Doris поддерживает частичное обновление столбцов. При такой загрузке каждый раз можно обновлять только часть столбцов, поэтому столбец Sequence указывать необязательно:

    • Если задание загрузки включает столбец Sequence, поведение не меняется.
    • Если задание загрузки не включает столбец Sequence, Doris использует значение этого столбца из соответствующей существующей записи в качестве значения столбца Sequence обновлённой строки.
    • Если в существующих данных нет записи с таким же Key, Doris автоматически подставляет null или значение по умолчанию.
  4. Гарантия корректности при параллельных загрузках: при параллельных загрузках Doris использует MVCC для обеспечения корректности данных. Если два пакета загрузки обновляют разные столбцы одного и того же Key, то после успешного завершения задания с меньшим номером версии задание с большим номером версии использует строки с тем же Key, записанные заданием с меньшим номером версии, чтобы заполнить недостающие значения.