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

Как работают секционирование и бакетирование

На этой странице объясняется, как Doris распределяет данные по секциям, бакетам и Tablet. Также рассматриваются расширенные режимы секционирования и бакетирования, рекомендации по проектированию и команды управления. Рекомендуемую начальную конфигурацию и руководство по выбору можно найти на странице Секционирование и бакетирование. Затем прочитайте эту страницу, чтобы разобраться в лежащей в их основе модели.

1. Обзор​

Doris распределяет данные таблицы по кластеру на двух уровнях: секции и бакеты:

  • Секция: делит таблицу по горизонтали на меньшие подмножества по значениям столбцов, например времени или региону, упрощая отсечение данных при выполнении запросов и управление жизненным циклом данных.
  • Бакет: дополнительно равномерно распределяет данные внутри каждой секции по нескольким шардам данных (Tablet), позволяя полностью использовать возможности параллельной обработки в кластере.

doris-data-partitioning

Поток данных можно представить следующим образом:

Table ──► Partition ──► Bucket ──► Tablet (data shard, stored on BE nodes)

Правильно выбранные секционирование и бакетирование дают сразу три преимущества: более быстрые запросы благодаря отсечению секций и параллельному сканированию, более простое управление благодаря архивированию и очистке по времени, а также более равномерную запись благодаря снижению риска возникновения горячих точек.

2. Основные понятия​

2.1 Структура таблицы: строки и столбцы​

В Doris данные логически представлены в виде таблиц. Таблица состоит из строк (Row) и столбцов (Column):

ПонятиеОписание
СтрокаСтрока пользовательских данных
СтолбецОписывает отдельное поле в строке данных

Столбцы, в свою очередь, делятся на две категории:

Тип столбцаНазначение в бизнес-моделиОписание
Ключевой столбецСтолбец измеренияСтолбцы, указанные после ключевого слова unique key, aggregate key или duplicate key в операторе CREATE TABLE
Столбец значенияСтолбец показателяСтолбцы, не входящие в число ключевых; метод агрегации задаётся при создании таблицы

В модели Aggregate строки с одинаковыми значениями ключевых столбцов объединяются в одну строку, а метод агрегации столбцов значений пользователь задаёт при создании таблицы. Подробнее о модели Aggregate см. в разделе Модель данных Doris.

2.2 Секция​

Секция разделяет данные на меньшие непересекающиеся подмножества на основе значений определённых столбцов таблицы. Каждая строка данных принадлежит ровно одной секции, а секция является наименьшей логической единицей управления.

Doris поддерживает два типа секционирования:

  • Секционирование Range: разделение по числовым или временным диапазонам; обычно используется для временных рядов.
  • Секционирование List: разделение по наборам перечисленных значений; обычно используется для разделения по дискретным измерениям, например региону или категории.

Если при создании таблицы секционирование не задано, Doris создаёт неявную для пользователя секцию по умолчанию, содержащую все данные таблицы.

Продуманное секционирование обеспечивает:

  • Более быстрые запросы: отсечение секций позволяет пропускать секции, которые не могут удовлетворять условиям запроса, поэтому Doris сканирует меньше данных и выполняет меньше операций ввода-вывода. Это особенно существенно для больших наборов данных.
  • Более простое управление: разделение по времени или региону упрощает архивирование, очистку и резервное копирование. Например, секционирование по времени позволяет отдельно управлять историческими и поступающими данными.

2.3 Бакет​

Бакетирование дополнительно разделяет данные внутри секции на меньшие непересекающиеся единицы по определённым правилам. Каждая строка данных принадлежит ровно одному бакету.

Секции разделяют данные по диапазонам или спискам значений столбцов. Задача бакетирования состоит в том, чтобы равномерно распределить данные между бакетами секции, уменьшая перекос и обеспечивая параллельное сканирование. Результат зависит от метода бакетирования и распределения значений ключа.

Doris поддерживает два метода бакетирования:

  • Бакетирование Hash: вычисляет хеш crc32 значений столбцов бакетирования и берёт остаток от деления на количество бакетов для равномерного распределения данных.
  • Бакетирование Random: случайным образом распределяет данные по бакетам. При использовании бакетирования Random можно дополнительно задать параметр load_to_single_tablet, чтобы ускорить запись небольших объёмов данных.

Правильно выбранное бакетирование обеспечивает:

  • Более равномерное распределение данных: подходящий ключ бакетирования снижает риск перекоса данных и неравномерной нагрузки на узлы или диски.
  • Меньше горячих точек: распределение работы между бакетами помогает снизить перегрузку и повысить стабильность.
  • Больше возможностей параллельной обработки: Doris параллельно читает разные бакеты одной секции, что повышает пропускную способность.

2.4 Архитектура Tablet и узлов​

Физически бакету соответствует Tablet (шард данных). Tablet является наименьшей единицей управления данными в Doris, а также базовой физической единицей для таких операций, как перемещение данных и репликация.

Кластер Doris состоит из узлов двух типов:

  • Узел FE (Frontend): управляет метаданными кластера, например сведениями о таблицах и Tablet, а также отвечает за разбор SQL и планирование выполнения.
  • Узел BE (Backend): хранит данные Tablet и отвечает за выполнение вычислительных задач. FE объединяет результаты, полученные от узлов BE, и возвращает их пользователю.

При записи данных и выполнении запросов секции и бакеты взаимодействуют следующим образом:

  • Запись данных: строки данных сначала направляются в соответствующую секцию согласно стратегии секционирования, а затем сопоставляются с определённым Tablet внутри секции согласно стратегии бакетирования.
  • Выполнение запроса: оптимизатор FE отсекает ненужные секции и бакеты на основе условий запроса, сводя объём сканирования к минимуму. При выполнении запросов с JOIN или агрегацией может потребоваться Shuffle между узлами. Продуманное секционирование и бакетирование, при необходимости в сочетании с Colocate, могут существенно сократить затраты на Shuffle.

3. Первый пример CREATE TABLE​

Создание таблицы в Doris выполняется синхронной командой. Результат возвращается после завершения оператора SQL, и успешное выполнение означает, что таблица создана. Полный синтаксис CREATE TABLE приведён в разделе CREATE TABLE. Дополнительную справку можно получить с помощью HELP CREATE TABLE.

Ниже приведён минимальный пример CREATE TABLE с секционированием Range и бакетированием Hash:

-- Range Partition
CREATE TABLE example_range_tbl
(
`user_id` LARGEINT NOT NULL COMMENT "User ID",
`date` DATE NOT NULL COMMENT "Data ingestion date",
`timestamp` DATETIME NOT NULL COMMENT "Data ingestion timestamp",
`city` VARCHAR(20) COMMENT "User's city",
`age` SMALLINT COMMENT "User age",
`sex` TINYINT COMMENT "User gender",
`last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "User's last visit time",
`cost` BIGINT SUM DEFAULT "0" COMMENT "Total user spending",
`max_dwell_time` INT MAX DEFAULT "0" COMMENT "Maximum user dwell time",
`min_dwell_time` INT MIN DEFAULT "99999" COMMENT "Minimum user dwell time"
)
PARTITION BY RANGE(`date`)
(
PARTITION `p201701` VALUES LESS THAN ("2017-02-01"),
PARTITION `p201702` VALUES LESS THAN ("2017-03-01"),
PARTITION `p201703` VALUES LESS THAN ("2017-04-01"),
PARTITION `p2018` VALUES [("2018-01-01"), ("2019-01-01"))
)
DISTRIBUTED BY HASH(`user_id`) BUCKETS 16
PROPERTIES
(
"replication_num" = "1"
);

Как в этом примере происходят запись и выполнение запроса:

  • При записи каждая строка данных сначала попадает в соответствующую секцию Range по значению date (например, 2017-02-15 попадает в p201702), а затем сопоставляется с одним из 16 Tablet этой секции по хешу значения user_id.
  • Для запроса с условием WHERE date = '2017-02-15' оптимизатор сканирует только секцию p201702. Если дополнительно указано условие user_id = 123, запрос обращается только к одному Tablet.

4. Расширенные возможности: режимы секционирования​

Помимо ручного объявления секций при создании таблицы, Doris поддерживает автоматическое создание секций по расписанию (Dynamic Partition) и создание секций по мере необходимости на основе поступающих данных (Auto Partition). Ниже приведено сравнение этих трёх режимов:

Режим секционированияКогда создаются секцииСценарии применения
Ручное секционированиеЯвно объявляются при создании таблицы или добавляются через ALTERСтабильный набор секций, требующий точного управления
Динамическое секционированиеАвтоматически создаются и удаляются системой по заданным временным правиламВременные ряды, для которых нужно автоматически поддерживать скользящий набор секций за последние N дней/недель/месяцев
Автоматическое секционированиеСоздаются по мере необходимости при записи данныхЗначения секционирования непредсказуемы, например при работе с несколькими арендаторами или разреженными временными данными, и предварительного создания следует избегать

Готовые примеры CREATE TABLE для каждого режима, в том числе для сочетания автоматического и динамического секционирования, приведены в разделах Автоматическое секционирование, Динамическое секционирование и Ручное секционирование.

5. Расширенные возможности: бакетирование​

5.1 Автоматическое бакетирование​

Если вы не уверены, сколько бакетов использовать, задайте BUCKETS AUTO, чтобы Doris определил их количество по оценочному объёму данных (estimate_partition_size). Этот подход не подходит для чрезвычайно больших таблиц. Подробнее см. в разделе Бакетирование данных.

5.2 Colocate​

Для нескольких больших таблиц, которые часто участвуют в запросах с JOIN или агрегацией, можно включить стратегию Colocate. Она размещает данные с одинаковыми значениями столбцов бакетирования на одном физическом узле, позволяя избежать Shuffle между узлами и существенно повысить производительность запросов. Подробные инструкции приведены в документации Colocate Join.

6. Рекомендации по проектированию​

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

6.1 Цели проектирования​

  1. Равномерное распределение данных: выбирайте столбцы с высокой кардинальностью для бакетирования, чтобы избежать перекоса данных, приводящего к перегрузке отдельных узлов.
  2. Оптимизация производительности запросов: отсечение секций может значительно сократить объём сканируемых данных; подходящее количество бакетов повышает параллелизм вычислений; при необходимости используйте Colocate для сокращения затрат на Shuffle.
  3. Гибкое управление данными: секционирование по времени упрощает разделение холодных и горячих данных по уровням хранения (HDD/SSD), а удаление исторических секций позволяет освобождать место в хранилище.

6.2 Практические ориентиры и ограничения​

  • Объём метаданных: метаданные каждого Tablet хранятся и в FE, и в BE, поэтому их общий объём необходимо контролировать:
    • На каждые 10 миллионов Tablet узлу FE требуется как минимум около 100 GB памяти.
    • На одном узле BE должно находиться менее 20,000 Tablet.
  • Пропускная способность записи:
    • Рекомендуемое количество бакетов в секции составляет < 128. Слишком большое количество бакетов существенно ухудшает производительность записи.
    • Каждая операция записи должна затрагивать небольшое количество секций, чтобы распределённая по множеству секций запись не приводила к образованию слишком большого количества мелких файлов.
  • Выбор столбцов секционирования и бакетирования:
    • Для секционирования предпочтительны временные столбцы или перечисления с низкой кардинальностью.
    • Для бакетирования выбирайте столбцы с высокой кардинальностью, например user_id, чтобы обеспечить равномерное распределение.
    • Рекомендуемый объём данных одного Tablet составляет 1-10 GB.

7. Просмотр и изменение секций​

7.1 Просмотр оператора CREATE TABLE с помощью SHOW CREATE TABLE​

SHOW CREATE TABLE позволяет просмотреть полный оператор CREATE TABLE для таблицы, включая сведения о секциях:

> show create table  example_range_tbl 
+-------------------+---------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------------------+---------------------------------------------------------------------------------------------------------+
| example_range_tbl | CREATE TABLE `example_range_tbl` ( |
| | `user_id` largeint(40) NOT NULL COMMENT 'User ID', |
| | `date` date NOT NULL COMMENT 'Data ingestion date', |
| | `timestamp` datetime NOT NULL COMMENT 'Data ingestion timestamp', |
| | `city` varchar(20) NULL COMMENT 'User city', |
| | `age` smallint(6) NULL COMMENT 'User age', |
| | `sex` tinyint(4) NULL COMMENT 'User gender', |
| | `last_visit_date` datetime REPLACE NULL DEFAULT "1970-01-01 00:00:00" COMMENT 'User last visit time', |
| | `cost` bigint(20) SUM NULL DEFAULT "0" COMMENT 'Total user spending', |
| | `max_dwell_time` int(11) MAX NULL DEFAULT "0" COMMENT 'Maximum user dwell time', |
| | `min_dwell_time` int(11) MIN NULL DEFAULT "99999" COMMENT 'Minimum user dwell time' |
| | ) |
| | PARTITION BY RANGE(`date`) |
| | (PARTITION p201701 VALUES [('0000-01-01'), ('2017-02-01')), |
| | PARTITION p201702 VALUES [('2017-02-01'), ('2017-03-01')), |
| | PARTITION p201703 VALUES [('2017-03-01'), ('2017-04-01'))) |
| | DISTRIBUTED BY HASH(`user_id`) BUCKETS 16 |
| | PROPERTIES ( |
| | "replication_allocation" = "tag.location.default: 1", |
| | "storage_format" = "V2", |
| | "light_schema_change" = "true", |
| | "disable_auto_compaction" = "false" |
| | ); |
+-------------------+---------------------------------------------------------------------------------------------------------+

7.2 Просмотр списка секций с помощью SHOW PARTITIONS​

SHOW PARTITIONS FROM <table_name> позволяет просмотреть список секций таблицы и подробные сведения о них:

> show partitions from example_range_tbl
+-------------+---------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------
+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+
| PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium
| CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable |
+-------------+---------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------
+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+
| 28731 | p201701 | 1 | 2024-01-25 10:50:51 | NORMAL | date | [types: [DATEV2]; keys: [0000-01-01]; ..types: [DATEV2]; keys: [2017-02-01]; ) | user_id | 16 | 1 | HDD
| 9999-12-31 23:59:59 | | | 0.000 | false | tag.location.default: 1 | true |
| 28732 | p201702 | 1 | 2024-01-25 10:50:51 | NORMAL | date | [types: [DATEV2]; keys: [2017-02-01]; ..types: [DATEV2]; keys: [2017-03-01]; ) | user_id | 16 | 1 | HDD
| 9999-12-31 23:59:59 | | | 0.000 | false | tag.location.default: 1 | true |
| 28733 | p201703 | 1 | 2024-01-25 10:50:51 | NORMAL | date | [types: [DATEV2]; keys: [2017-03-01]; ..types: [DATEV2]; keys: [2017-04-01]; ) | user_id | 16 | 1 | HDD
| 9999-12-31 23:59:59 | | | 0.000 | false | tag.location.default: 1 | true |
+-------------+---------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------
+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+

7.3 Изменение секций​

ALTER TABLE ADD PARTITION позволяет добавлять в таблицу новые секции:

ALTER TABLE example_range_tbl ADD  PARTITION p201704 VALUES LESS THAN("2020-05-01") DISTRIBUTED BY HASH(`user_id`) BUCKETS 5;

Другие операции изменения секций описаны в справочнике SQL в разделе ALTER-TABLE-PARTITION.

7.4 Поиск секций​

Табличная функция partitions и системная таблица information_schema.partitions содержат сведения о секциях кластера. Их можно использовать для получения сведений о секциях при автоматизации управления ими.

Поиск секции, которой принадлежит заданное значение​

Найдите секцию, соответствующую значению в таблице с Auto Partition:

mysql> select * from partitions("catalog"="internal", "database"="optest", "table"="DAILY_TRADE_VALUE") where PartitionName = auto_partition_name('range', 'year', '2008-02-03');
+-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+-----------+------------+-------------------------+-----------+--------------------+--------------+
| PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables |
+-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+-----------+------------+-------------------------+-----------+--------------------+--------------+
| 127095 | p20080101000000 | 2 | 2024-11-14 17:29:02 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2008-01-01]; ..types: [DATEV2]; keys: [2009-01-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | \N | 985.000 B | 0 | tag.location.default: 1 | 1 | 1 | \N |
+-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+-----------+------------+-------------------------+-----------+--------------------+--------------+
1 row in set (0.30 sec)

Найдите соответствующую секцию в таблице с секционированием List:

mysql> select * from information_schema.partitions where TABLE_SCHEMA='optest' and TABLE_NAME='list_table1' and PARTITION_NAME=auto_partition_name('list', null);
+---------------+--------------+-------------+----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+-----------------------+------------+----------------+-------------+-----------------+--------------+-----------+-------------+---------------------+---------------------+----------+-------------------+-----------+-----------------+
| TABLE_CATALOG | TABLE_SCHEMA | TABLE_NAME | PARTITION_NAME | SUBPARTITION_NAME | PARTITION_ORDINAL_POSITION | SUBPARTITION_ORDINAL_POSITION | PARTITION_METHOD | SUBPARTITION_METHOD | PARTITION_EXPRESSION | SUBPARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH | MAX_DATA_LENGTH | INDEX_LENGTH | DATA_FREE | CREATE_TIME | UPDATE_TIME | CHECK_TIME | CHECKSUM | PARTITION_COMMENT | NODEGROUP | TABLESPACE_NAME |
+---------------+--------------+-------------+----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+-----------------------+------------+----------------+-------------+-----------------+--------------+-----------+-------------+---------------------+---------------------+----------+-------------------+-----------+-----------------+
| internal | optest | list_table1 | pX | NULL | 0 | 0 | LIST | NULL | str | NULL | (NULL) | 1 | 1266 | 1266 | 0 | 0 | 0 | 0 | 2024-11-14 19:58:45 | 0000-00-00 00:00:00 | 0 | | | |
+---------------+--------------+-------------+----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+-----------------------+------------+----------------+-------------+-----------------+--------------+-----------+-------------+---------------------+---------------------+----------+-------------------+-----------+-----------------+
1 row in set (0.24 sec)

Поиск секций с заданной нижней границей​

mysql> select * from information_schema.partitions where TABLE_NAME='DAILY_TRADE_VALUE' and PARTITION_DESCRIPTION like "[('2012-01-01'),%";
+---------------+--------------+-------------------+-----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+----------------------------------+------------+----------------+-------------+-----------------+--------------+-----------+-------------+---------------------+---------------------+----------+-------------------+-----------+-----------------+
| TABLE_CATALOG | TABLE_SCHEMA | TABLE_NAME | PARTITION_NAME | SUBPARTITION_NAME | PARTITION_ORDINAL_POSITION | SUBPARTITION_ORDINAL_POSITION | PARTITION_METHOD | SUBPARTITION_METHOD | PARTITION_EXPRESSION | SUBPARTITION_EXPRESSION | PARTITION_DESCRIPTION | TABLE_ROWS | AVG_ROW_LENGTH | DATA_LENGTH | MAX_DATA_LENGTH | INDEX_LENGTH | DATA_FREE | CREATE_TIME | UPDATE_TIME | CHECK_TIME | CHECKSUM | PARTITION_COMMENT | NODEGROUP | TABLESPACE_NAME |
+---------------+--------------+-------------------+-----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+----------------------------------+------------+----------------+-------------+-----------------+--------------+-----------+-------------+---------------------+---------------------+----------+-------------------+-----------+-----------------+
| internal | optest | DAILY_TRADE_VALUE | p20120101000000 | NULL | 0 | 0 | RANGE | NULL | TRADE_DATE | NULL | [('2012-01-01'), ('2013-01-01')) | 1 | 985 | 985 | 0 | 0 | 0 | 0 | 2024-11-14 17:29:02 | 0000-00-00 00:00:00 | 0 | | | |
+---------------+--------------+-------------------+-----------------+-------------------+----------------------------+-------------------------------+------------------+---------------------+----------------------+-------------------------+----------------------------------+------------+----------------+-------------+-----------------+--------------+-----------+-------------+---------------------+---------------------+----------+-------------------+-----------+-----------------+
1 row in set (0.65 sec)

8. Дополнительные материалы​