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

Обновление столбцов

При обновлении данных бизнес-требования часто предполагают изменение лишь части полей строки, например:

  • Частое обновление полей в реальном времени: в таблице пользовательских тегов необходимо постоянно поддерживать актуальные значения полей поведения, чтобы рекламные и рекомендательные системы могли анализировать данные и принимать решения в реальном времени.
  • Объединение данных из нескольких источников в широкую таблицу: данные из нескольких исходных таблиц объединяются по первичному ключу в одну широкую таблицу, при этом каждая исходная таблица предоставляет лишь часть столбцов.
  • Исправление данных: пакетная операция исправляет значения определённых полей в некоторых записях, оставляя остальные поля без изменений.

Традиционный оператор 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 может напрямую вставлять или обновлять данные отдельных столбцов без предварительного чтения всей строки, что значительно повышает эффективность обновлений.

Примечание
  1. В версии 2.0 частичное обновление столбцов поддерживается только в реализации Unique Key с Merge-on-Write.
  2. Начиная с версии 2.0.2, для частичного обновления столбцов можно использовать INSERT INTO.
  3. Частичное обновление столбцов не поддерживается для таблиц с синхронными материализованными представлениями.
  4. Частичное обновление столбцов не поддерживается для таблиц, в которых выполняется Schema Change.

Пример​

Предположим, в Doris есть таблица заказов order_tbl, в которой ID заказа является столбцом Key, а статус и сумма заказа являются столбцами Value. Текущие данные:

ID заказаСумма заказаСтатус заказа
1100Ожидает оплаты
+----------+--------------+------------------+
| 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 добавьте следующую настройку:

'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 поддерживает более гибкий способ обновления: каждая строка в одной загрузке может обновлять свой набор столбцов.

Примечание
  1. Гибкое обновление столбцов поддерживает Stream Load, Routine Load и любые инструменты, использующие Stream Load в качестве базового способа загрузки (например, Doris Flink Connector).
  2. При использовании гибкого обновления столбцов файл загрузки должен быть в формате 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 LoadPROPERTIES"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;
Ограничения Routine Load

При использовании режима 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> |
+---+-----+------+-----+------+--------+

Ограничения и примечания​

  1. Как и обычное частичное обновление столбцов, гибкое обновление столбцов требует, чтобы каждая строка содержала все столбцы Key. Строки, не соответствующие этому требованию, отфильтровываются и учитываются в filter rows. Если filtered rows превышает верхнюю границу, допустимую параметром загрузки max_filter_ratio, вся загрузка завершается ошибкой. Каждая отфильтрованная строка записывается в журнал ошибок.
  2. Среди пар «ключ-значение» в объекте JSON учитываются только те, у которых ключ совпадает с именем столбца в целевой таблице. Остальные игнорируются. Также игнорируются пары «ключ-значение», в которых ключ равен __DORIS_VERSION_COL__, __DORIS_ROW_STORE_COL__ или __DORIS_SKIP_BITMAP_COL__.
  3. Гибкое обновление столбцов не поддерживается для таблиц, содержащих столбцы Variant.
  4. Гибкое обновление столбцов не поддерживается для таблиц с синхронными материализованными представлениями.
  5. При использовании гибкого обновления столбцов следующие параметры загрузки нельзя указывать или включать:
    • merge_type
    • delete
    • fuzzy_parse
    • columns
    • jsonpaths
    • hidden_columns
    • function_column.sequence_col
    • sql
    • memtable_on_sink_node move-forward
    • group_commit
    • where

Управление поведением при вставке новых строк​

Переменная сеанса или свойство загрузки 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');

После выполнения исходная строка обновляется и добавляются две новые строки. Для столбцов, которые пользователь не указал:

  1. Если столбец имеет значение по умолчанию, используется это значение.
  2. В противном случае, если столбец допускает NULL, используется NULL.
  3. В остальных случаях вставка завершается ошибкой.

Результат запроса:

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.