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

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

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

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