Обновление столбцов
При обновлении данных бизнес-требования часто предполагают изменение лишь части полей строки, например:
- Частое обновление полей в реальном времени: в таблице пользовательских тегов необходимо постоянно поддерживать актуальные значения полей поведения, чтобы рекламные и рекомендательные системы могли анализировать данные и принимать решения в реальном времени.
- Объединение данных из нескольких источников в широкую таблицу: данные из нескольких исходных таблиц объединяются по первичному ключу в одну широкую таблицу, при этом каждая исходная таблица предоставляет лишь часть столбцов.
- Исправление данных: пакетная операция исправляет значения определённых полей в некоторых записях, оставляя остальные поля без изменений.
Традиционный оператор UPDATE обычно сначала считывает строку целиком, а затем записывает её обратно целиком. Такая транзакция по схеме «read-modify-write» работает медленно при больших объёмах записи и не обеспечивает необходимую пропускную способность.
Возможность частичного обновления столбцов в Doris позволяет при загрузке записывать только те столбцы, которые нужно изменить, без предварительного чтения всей строки. Это значительно повышает эффективность обновлений. В этом документе описано, как выполнять частичное обновление столбцов в модели Unique Key и модели Aggregate Key.
Обзор возможностей
| Модель данных | Реализация | Производительность записи | Производительность запросов | Сценарии использования |
|---|---|---|---|---|
| Unique Key (Merge-on-Write) | Восстановление полной строки при записи | Средняя (зависит от IO) | Высокая | Обновления в реальном времени, сценарии с высокими требованиями к производительности запросов |
Aggregate Key (REPLACE_IF_NOT_NULL) | Агрегация при выполнении запроса | Высокая (сопоставима с обычной загрузкой) | Ниже (запросы с агрегацией выполняются в 5–10 раз медленнее, чем при MoW) | Сценарии с высокими требованиями к пропускной способности записи, в которых допустима более низкая производительность запросов |
Обновление столбцов в модели Unique Key
При загрузке в модель Unique Key Doris может напрямую вставлять или обновлять данные отдельных столбцов без предварительного чтения всей строки, что значительно повышает эффективность обновлений.
- В версии 2.0 частичное обновление столбцов поддерживается только в реализации Unique Key с Merge-on-Write.
- Начиная с версии 2.0.2, для частичного обновления столбцов можно использовать
INSERT INTO. - Частичное обновление столбцов не поддерживается для таблиц с синхронными материализованными представлениями.
- Частичное обновление столбцов не поддерживается для таблиц, в которых выполняется Schema Change.
Пример
Предположим, в Doris есть таблица заказов order_tbl, в которой ID заказа является столбцом Key, а статус и сумма заказа являются столбцами Value. Текущие данные:
| ID заказа | Сумма заказа | Статус заказа |
|---|---|---|
| 1 | 100 | Ожидает оплаты |
+----------+--------------+------------------+
| order_id | order_amount | order_status |
+----------+--------------+------------------+
| 1 | 100 | Pending payment |
+----------+--------------+------------------+
1 row in set (0.01 sec)
Когда пользователь нажимает кнопку оплаты, статус заказа с ID 1 должен измениться на Pending shipment, при этом поле суммы заказа должно остаться без изменений.
Частичное обновление столбцов при загрузке
Stream Load / Broker Load / Routine Load
Подготовьте следующий файл CSV:
1,Pending shipment
При загрузке добавьте следующий заголовок:
partial_columns:true
В параметре columns укажите столбцы для загрузки (необходимо включить все столбцы Key, иначе обновление выполнить не удастся). Пример для Stream Load:
curl --location-trusted -u root: \
-H "partial_columns:true" \
-H "column_separator:," \
-H "columns:order_id,order_status" \
-T /tmp/update.csv \
http://127.0.0.1:8030/api/db1/order_tbl/_stream_load
INSERT INTO
Во всех моделях данных INSERT INTO с указанием части столбцов по умолчанию записывает строку целиком. Чтобы избежать ошибочного использования, в реализации Merge-on-Write оператор INSERT INTO также по умолчанию использует семантику UPSERT для всей строки. Чтобы включить частичное обновление столбцов, сначала задайте следующую переменную сеанса:
SET enable_unique_key_partial_update=true;
INSERT INTO order_tbl (order_id, order_status) VALUES (1, 'Pending shipment');
Flink Connector
При использовании Flink Connector добавьте следующую настройку:
'sink.properties.partial_columns' = 'true'
Укажите столбцы для загрузки в sink.properties.column (необходимо включить все столбцы Key, иначе обновление выполнить не удастся).
Результат обновления
После обновления получаем следующий результат:
+----------+--------------+-------------------+
| order_id | order_amount | order_status |
+----------+--------------+-------------------+
| 1 | 100 | Pending shipment |
+----------+--------------+-------------------+
1 row in set (0.01 sec)
Примечания
Чтобы обеспечить оптимальную производительность запросов, реализация Merge-on-Write должна восстанавливать полную строку при записи. Поэтому частичное обновление столбцов с Merge-on-Write в некоторой степени снижает производительность загрузки.
Рекомендации по оптимизации производительности записи:
-
Используйте высокопроизводительные SSD: рекомендуются NVMe SSD или высокоскоростные облачные SSD. При восстановлении строки считывается большой объём исторических данных, что создаёт высокую нагрузку по IOPS и пропускной способности чтения.
-
Включите построчное хранение (row store): это позволяет значительно снизить количество операций ввода-вывода при восстановлении строк и заметно повысить производительность загрузки. Включите его при создании таблицы с помощью следующего свойства:
"store_row_column" = "true"
Ограничение для пакетов: все строки в одной задаче пакетной записи (будь то задача загрузки или INSERT INTO) могут обновлять только один и тот же набор столбцов. Чтобы обновить разные столбцы, разделите данные на несколько пакетов. Это ограничение можно снять с помощью описанной ниже функции гибкого обновления столбцов.
Гибкое обновление столбцов
Ранее функция частичного обновления столбцов в Doris требовала, чтобы все строки в одной загрузке обновляли один и тот же набор столбцов. Начиная с версии 3.1.0, Doris поддерживает более гибкий способ обновления: каждая строка в одной загрузке может обновлять свой набор столбцов.
- Гибкое обновление столбцов поддерживает Stream Load, Routine Load и любые инструменты, использующие Stream Load в качестве базового способа загрузки (например, Doris Flink Connector).
- При использовании гибкого обновления столбцов файл загрузки должен быть в формате JSON.
Сценарии использования
Когда CDC используется для синхронизации данных из другой системы в Doris в реальном времени, записи из исходной системы могут содержать не полные строки, а только первичный ключ и обновлённые столбцы. В этом случае строки пакета, собранного за определённый временной интервал, могут обновлять разные столбцы. Для загрузки таких данных в Doris можно использовать гибкое обновление столбцов.
Использование
1. Включите гибкое обновление столбцов
Новые таблицы: при создании таблицы укажите следующие свойства, чтобы включить Merge-on-Write и добавить скрытый столбец bitmap, необходимый для гибкого обновления столбцов:
"enable_unique_key_merge_on_write" = "true"
"enable_unique_key_skip_bitmap_column" = "true"
Существующие таблицы: для существующей таблицы Merge-on-Write, созданной в более ранней версии Doris, после обновления Doris можно включить гибкое обновление столбцов следующим оператором:
ALTER TABLE db1.tbl1 ENABLE FEATURE "UPDATE_FLEXIBLE_COLUMNS";
После выполнения запустите SHOW CREATE TABLE db1.tbl1. Если результат содержит "enable_unique_key_skip_bitmap_column" = "true", функция успешно включена.
Перед применением этого способа к существующей таблице убедитесь, что для целевой таблицы включён light-schema-change.
2. Включите функцию в задачах загрузки
Настройки для каждого способа загрузки:
| Способ загрузки | Параметр настройки | Значение |
|---|---|---|
| Stream Load | заголовок | unique_key_update_mode:UPDATE_FLEXIBLE_COLUMNS |
| Flink Doris Connector | свойство sink | 'sink.properties.unique_key_update_mode' = 'UPDATE_FLEXIBLE_COLUMNS' |
| Routine Load | PROPERTIES | "unique_key_update_mode" = "UPDATE_FLEXIBLE_COLUMNS" |
Полный пример Routine Load:
CREATE ROUTINE LOAD db1.job1 ON tbl1
PROPERTIES (
"format" = "json",
"unique_key_update_mode" = "UPDATE_FLEXIBLE_COLUMNS"
)
FROM KAFKA (
"kafka_broker_list" = "localhost:9092",
"kafka_topic" = "my_topic",
"property.kafka_default_offsets" = "OFFSET_BEGINNING"
);
Также можно использовать ALTER ROUTINE LOAD, чтобы изменить режим обновления существующей задачи Routine Load:
-- 1. Pause the job
PAUSE ROUTINE LOAD FOR db1.job1;
-- 2. Change the update mode
ALTER ROUTINE LOAD FOR db1.job1
PROPERTIES (
"unique_key_update_mode" = "UPDATE_FLEXIBLE_COLUMNS"
);
-- 3. Resume the job
RESUME ROUTINE LOAD FOR db1.job1;
При использовании режима UPDATE_FLEXIBLE_COLUMNS в Routine Load действуют следующие ограничения:
- Формат данных должен быть JSON (
"format" = "json"). - Нельзя указывать свойство
jsonpaths. - Нельзя включать параметр
fuzzy_parse. - Нельзя использовать предложение
COLUMNS. - Нельзя использовать предложение
WHERE.
Полный пример
Шаг 1: Создайте тестовую таблицу
CREATE TABLE t1 (
`k` INT NULL,
`v1` BIGINT NULL,
`v2` BIGINT NULL DEFAULT "9876",
`v3` BIGINT NOT NULL,
`v4` BIGINT NOT NULL DEFAULT "1234",
`v5` BIGINT NULL
) UNIQUE KEY(`k`)
DISTRIBUTED BY HASH(`k`) BUCKETS 1
PROPERTIES (
"replication_num" = "3",
"enable_unique_key_merge_on_write" = "true",
"enable_unique_key_skip_bitmap_column" = "true"
);
Шаг 2: Существующие данные в таблице
MySQL root@127.1:d1> select * from t1;
+---+----+----+----+----+----+
| k | v1 | v2 | v3 | v4 | v5 |
+---+----+----+----+----+----+
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 1 | 1 | 1 |
| 2 | 2 | 2 | 2 | 2 | 2 |
| 3 | 3 | 3 | 3 | 3 | 3 |
| 4 | 4 | 4 | 4 | 4 | 4 |
| 5 | 5 | 5 | 5 | 5 | 5 |
+---+----+----+----+----+----+
Шаг 3: Подготовьте данные для гибкого обновления столбцов
Каждая строка может обновлять свой набор столбцов:
{"k": 0, "__DORIS_DELETE_SIGN__": 1}
{"k": 1, "v1": 10}
{"k": 2, "v2": 20, "v5": 25}
{"k": 3, "v3": 30}
{"k": 4, "v4": 20, "v1": 43, "v3": 99}
{"k": 5, "v5": null}
{"k": 6, "v1": 999, "v3": 777}
{"k": 2, "v4": 222}
{"k": 1, "v2": 111, "v3": 111}
Шаг 4: Загрузите данные через Stream Load
curl --location-trusted -u root: \
-H "Expect:100-continue" \
-H "strict_mode:false" \
-H "format:json" \
-H "read_json_by_line:true" \
-H "unique_key_update_mode:UPDATE_FLEXIBLE_COLUMNS" \
-T test1.json \
-XPUT http://<host>:<http_port>/api/d1/t1/_stream_load
Шаг 5: Проверьте результат обновления
MySQL root@127.1:d1> select * from t1;
+---+-----+------+-----+------+--------+
| k | v1 | v2 | v3 | v4 | v5 |
+---+-----+------+-----+------+--------+
| 1 | 10 | 111 | 111 | 1 | 1 |
| 2 | 2 | 20 | 2 | 222 | 25 |
| 3 | 3 | 3 | 30 | 3 | 3 |
| 4 | 43 | 4 | 99 | 20 | 4 |
| 5 | 5 | 5 | 5 | 5 | <null> |
| 6 | 999 | 9876 | 777 | 1234 | <null> |
+---+-----+------+-----+------+--------+
Ограничения и примечания
- Как и обычное частичное обновление столбцов, гибкое обновление столбцов требует, чтобы каждая строка содержала все столбцы Key. Строки, не соответствующие этому требованию, отфильтровываются и учитываются в
filter rows. Еслиfiltered rowsпревышает верхнюю границу, допустимую параметром загрузкиmax_filter_ratio, вся загрузка завершается ошибкой. Каждая отфильтрованная строка записывается в журнал ошибок. - Среди пар «ключ-значение» в объекте JSON учитываются только те, у которых ключ совпадает с именем столбца в целевой таблице. Остальные игнорируются. Также игнорируются пары «ключ-значение», в которых ключ равен
__DORIS_VERSION_COL__,__DORIS_ROW_STORE_COL__или__DORIS_SKIP_BITMAP_COL__. - Гибкое обновление столбцов не поддерживается для таблиц, содержащих столбцы Variant.
- Гибкое обновление столбцов не поддерживается для таблиц с синхронными материализованными представлениями.
- При использовании гибкого обновления столбцов следующие параметры загрузки нельзя указывать или включать:
merge_typedeletefuzzy_parsecolumnsjsonpathshidden_columnsfunction_column.sequence_colsqlmemtable_on_sink_nodemove-forwardgroup_commitwhere
Управление поведением при вставке новых строк
Переменная сеанса или свойство загрузки partial_update_new_key_behavior определяет поведение для вновь вставляемых строк при частичном и гибком обновлении столбцов.
| Значение | Поведение |
|---|---|
ERROR | Значение Key каждой строки должно уже присутствовать в таблице, иначе загрузка завершается ошибкой. |
APPEND | Разрешены как обновление существующих строк, так и вставка новых строк со значениями Key, отсутствующими в таблице. |
Пример схемы таблицы
CREATE TABLE user_profile (
id INT,
name VARCHAR(10),
age INT,
city VARCHAR(10),
balance DECIMAL(9, 0),
last_access_time DATETIME
) ENGINE=OLAP
UNIQUE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES (
"enable_unique_key_merge_on_write" = "true"
);
Существующие данные в таблице:
mysql> select * from user_profile;
+----+-------+-----+----------+---------+---------------------+
| id | name | age | city | balance | last_access_time |
+----+-------+-----+----------+---------+---------------------+
| 1 | kevin | 18 | shenzhen | 400 | 2023-07-01 12:00:00 |
+----+-------+-----+----------+---------+---------------------+
Сценарий 1: режим ERROR (отклонение новых строк)
Поскольку значения Key второй и третьей строк (3 и 18) отсутствуют в исходной таблице, эта вставка завершается ошибкой:
SET enable_unique_key_partial_update=true;
SET partial_update_new_key_behavior=ERROR;
INSERT INTO user_profile (id, balance, last_access_time) VALUES
(1, 500, '2023-07-03 12:00:01'),
(3, 23, '2023-07-03 12:00:02'),
(18, 9999999, '2023-07-03 12:00:03');
-- Error:
-- (1105, "errCode = 2, detailMessage = (127.0.0.1)[INTERNAL_ERROR]tablet error:
-- [E-7003]Can't append new rows in partial update when partial_update_new_key_behavior is ERROR.
-- Row with key=[3] is not in table., host: 127.0.0.1")
Сценарий 2: режим APPEND (разрешение новых строк)
SET enable_unique_key_partial_update=true;
SET partial_update_new_key_behavior=APPEND;
INSERT INTO user_profile (id, balance, last_access_time) VALUES
(1, 500, '2023-07-03 12:00:01'),
(3, 23, '2023-07-03 12:00:02'),
(18, 9999999, '2023-07-03 12:00:03');
После выполнения исходная строка обновляется и добавляются две новые строки. Для столбцов, которые пользователь не указал:
- Если столбец имеет значение по умолчанию, используется это значение.
- В противном случае, если столбец допускает NULL, используется NULL.
- В остальных случаях вставка завершается ошибкой.
Результат запроса:
mysql> select * from user_profile;
+----+-------+------+----------+---------+---------------------+
| id | name | age | city | balance | last_access_time |
+----+-------+------+----------+---------+---------------------+
| 1 | kevin | 18 | shenzhen | 500 | 2023-07-03 12:00:01 |
| 3 | NULL | NULL | NULL | 23 | 2023-07-03 12:00:02 |
| 18 | NULL | NULL | NULL | 9999999 | 2023-07-03 12:00:03 |
+----+-------+------+----------+---------+---------------------+
Обновление столбцов в модели Aggregate Key
Таблица Aggregate в основном используется в сценариях с предварительной агрегацией, но позволяет также выполнять частичное обновление столбцов, если задать агрегатную функцию REPLACE_IF_NOT_NULL.
Создание таблицы
Для каждого поля, требующего обновления по столбцам, задайте агрегатную функцию REPLACE_IF_NOT_NULL:
CREATE TABLE order_tbl (
order_id INT(11) NULL,
order_amount INT(11) REPLACE_IF_NOT_NULL NULL,
order_status VARCHAR(100) REPLACE_IF_NOT_NULL NULL
) ENGINE=OLAP
AGGREGATE KEY(order_id)
COMMENT 'OLAP'
DISTRIBUTED BY HASH(order_id) BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
Запись данных
При использовании Stream Load, Broker Load, Routine Load или INSERT INTO достаточно записать данные полей, которые нужно обновить. Дополнительные параметры не требуются.
Пример
Используется тот же пример, что и выше. Соответствующая команда Stream Load (дополнительный заголовок не требуется):
$ cat update.csv
1,Pending shipment
curl --location-trusted -u root: \
-H "column_separator:," \
-H "columns:order_id,order_status" \
-T ./update.csv \
http://127.0.0.1:8030/api/db1/order_tbl/_stream_load
Соответствующий оператор INSERT INTO (дополнительная переменная сеанса не требуется):
INSERT INTO order_tbl (order_id, order_status) VALUES (1, 'Pending shipment');
Примечания
- Производительность записи: модель Aggregate Key не выполняет дополнительную обработку при записи, поэтому производительность записи такая же, как при обычной загрузке данных.
- Производительность запросов: агрегация при выполнении запроса требует значительных ресурсов. По сравнению с реализацией Merge-on-Write модели Unique Key типичные запросы с агрегацией выполняются в 5–10 раз медленнее.
- Ограничение для значений NULL: поскольку агрегатная функция
REPLACE_IF_NOT_NULLприменяется только к значениям, отличным от NULL, значение поля нельзя изменить на NULL.