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

Партиционирование и бакетирование

Кратко Каждая таблица Apache Doris делится дважды: предложение PARTITION BY разбивает строки по значению столбца (обычно даты), а предложение DISTRIBUTED BY делит каждую партицию на N tablet. Партиции позволяют планировщику исключать из чтения крупные блоки данных, а администратору архивировать их; бакеты обеспечивают кластеру параллельное сканирование и запись. В качестве ключа партиционирования выберите столбец даты, по которому запросы фильтруют данные, а в качестве ключа бакетирования выберите столбец с высокой кардинальностью, по которому выполняется фильтрация на равенство. Остальное доверьте BUCKETS AUTO.

Партиционирование и бакетирование в Apache Doris: двухуровневое шардирование данных в Doris. Партиции делят таблицу по значениям столбца для отсечения данных; бакеты делят каждую партицию на tablet для параллельного чтения.

Зачем использовать партиционирование и бакетирование в Apache Doris?​

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

  • Запрос WHERE date BETWEEN '2026-04-01' AND '2026-04-07' к таблице событий за 365 дней читает все строки за каждый день, пока планировщик не узнает, что остальные 358 дней можно пропустить.
  • Таблица объёмом 4 TB, загруженная в один tablet, нагружает один BE до 100% CPU, а остальные девять простаивают. Кластер способен выполнять работу параллельно, но структура таблицы не позволяет этим воспользоваться.
  • Удаление данных за прошлый квартал превращается в многочасовое задание DELETE вместо изменения метаданных.

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

Что такое партиционирование и бакетирование в Apache Doris?​

Партиционирование и бакетирование в Apache Doris задают двухуровневую физическую организацию каждой таблицы. Предложение PARTITION BY делит таблицу на непересекающиеся подмножества по значению столбца (Range или List), а DISTRIBUTED BY дополнительно делит каждую партицию на фиксированное число tablet, служащих единицами репликации и параллельного выполнения. Строка сначала попадает в свою партицию по значению, а затем в свой tablet по хешу (или путём случайного назначения).

Основные термины

  • Partition: подмножество строк, заданное диапазоном Range или списком List по одному или нескольким столбцам KEY. Единица отсечения данных, архивирования и TTL.
  • Bucket: часть партиции, в которую строки назначаются по хешу или случайным образом. Каждому бакету соответствует один tablet для каждой реплики.
  • Tablet: физический шард данных. Единица репликации, планирования задач и параллельного выполнения внутри BE.
  • PARTITION BY RANGE / LIST: явное партиционирование, объявленное в DDL. Range подходит для временных и числовых диапазонов; List подходит для перечислимых измерений, например региона или арендатора.
  • AUTO PARTITION: партиции создаются по мере необходимости при записи: по date_trunc(col, 'month') для диапазонов или по значению перечислимого измерения для списков. Заменяет статический список партиций.
  • DISTRIBUTED BY HASH(col) BUCKETS N: шарды с распределением по хешу. Бакет выбирается с помощью crc32(col) % N; предикаты равенства по col позволяют отсекать бакеты.
  • DISTRIBUTED BY RANDOM BUCKETS N: строки распределяются по бакетам без ключа. Позволяет избежать перекоса данных, но не поддерживает отсечение бакетов. Доступно только для таблиц Duplicate-Key.
  • BUCKETS AUTO: FE выбирает число бакетов на основе estimate_partition_size, количества узлов BE и дисков. Задаётся для каждой партиции.

Как работают партиционирование и бакетирование в Apache Doris?​

Партиционирование и бакетирование в Apache Doris вместе определяют путь строки от INSERT до диска через два уровня: выбор партиции и выбор tablet.

  1. Определение партиции для строки. Планировщик вычисляет выражение партиционирования. Для партиций Range выполняется двоичный поиск в отсортированном списке интервалов; для партиций List значение ищется в хеш-таблице. Если подходящей партиции нет, Auto Partition создаёт её на лету.
  2. Определение бакета для строки. При распределении по хешу вычисляется crc32(bucket_cols) % bucket_num; при случайном распределении tablet выбирается по кругу (либо весь пакет направляется в один tablet, если load_to_single_tablet = true).
  3. Запись в реплики выбранного tablet. У каждого tablet есть N реплик (по умолчанию 3) на разных узлах BE. Coordinator передаёт строку потоком во все реплики.
  4. Отсечение данных при выполнении запроса (FE). PruneOlapScanPartition сопоставляет предикаты WHERE с деревом партиций; PruneOlapScanTablet извлекает предикаты равенства по ключу бакетирования и определяет с помощью HashDistributionPruner, в какие бакеты они попадают по хешу. EXPLAIN показывает оставшиеся партиции и tablet как partitions=1/365 и tablets=1/32.
  5. Параллельное сканирование оставшихся tablet (BE). Каждый оставшийся tablet служит единицей параллельного сканирования. Конвейерный движок распределяет их по ядрам BE, поэтому запрос, обращающийся к 16 tablet на 4 узлах BE, выполняется в 16 параллельных потоках без настройки сеанса.

Иерархия хранения на диске выглядит так: Table → Partition → Tablet → Rowset → Segment. Compaction и репликация работают на уровне tablet, поэтому число партиций и бакетов определяет как параллелизм запросов, так и затраты на эксплуатацию. Подробнее о многоуровневой цепочке отсечения данных, которая выполняется после выбора планировщиком оставшихся партиций и tablet, см. в разделе Отсечение данных.

Быстрый старт​

CREATE TABLE orders (
order_id BIGINT,
user_id BIGINT,
order_date DATE NOT NULL,
amount DECIMAL(10,2)
)
DUPLICATE KEY(order_id, user_id)
AUTO PARTITION BY RANGE (date_trunc(order_date, 'month')) ()
DISTRIBUTED BY HASH(user_id) BUCKETS 16;

INSERT INTO orders VALUES
(1, 100, '2026-04-15', 99.00),
(2, 200, '2026-05-02', 50.00);

EXPLAIN SELECT SUM(amount) FROM orders
WHERE order_date >= '2026-05-01' AND user_id = 200;

Ожидаемый результат (фрагмент)

0:VOlapScanNode
TABLE: orders
PREDICATES: order_date >= '2026-05-01', user_id = 200
partitions=1/2 (p20260501000000)
tablets=1/16

Для данных за два месяца автоматически созданы две партиции. По предикату даты планировщик оставил одну из этих партиций, а по условию равенства для user_id оставил один tablet из шестнадцати, поэтому запрос читает 1/32 таблицы. Записи распределяются по всем шестнадцати tablet, поэтому одна загрузка использует шестнадцать рабочих потоков BE параллельно.

Когда следует использовать партиционирование и бакетирование в Apache Doris?​

Партиционирование в Apache Doris подходит для таблиц временных рядов и таблиц с перечислимыми измерениями, которым нужно отсечение данных или архивирование; бакетирование подходит для любой таблицы, достаточно большой, чтобы один tablet не справлялся со сканированием или записью.

Подходящие сценарии

  • Таблицы фактов с временными рядами (события, заказы, журналы). Range или Auto Range по временной метке позволяет планировщику пропускать почти все партиции при типичном запросе дашборда, а удаление данных за месяц сводится к изменению метаданных.
  • Точечные запросы с высокой конкурентностью по user_id, device_id или любому другому столбцу с высокой кардинальностью, по которому выполняется фильтрация на равенство. Выберите этот столбец в качестве ключа хеш-бакетирования, и отдельный запрос будет обращаться к одному tablet.
  • Таблицы для нескольких арендаторов или регионов со стабильным перечислимым измерением. Используйте партиционирование List по арендатору, а при отключении арендатора удаляйте его партицию.
  • Таблицы, загружаемые инкрементально, для которых нельзя заранее предсказать диапазон значений. AUTO PARTITION BY RANGE(date_trunc(...)) создаёт партиции при первой записи, полностью избавляя от DDL для управления партициями.
  • Соединения двух больших таблиц по одному ключу, требующие обмена данными между многими узлами. Используйте одинаковый ключ бакетирования в обеих таблицах и включите Colocate Join, чтобы избежать перераспределения данных.

Неподходящие сценарии

  • Партиционирование по столбцу с низкой кардинальностью, по которому запросы не фильтруют данные (флаг статуса, столбец страны с 5 значениями). Планировщику нечего отсекать, а вы тратите ресурсы на метаданные партиций, которые никогда не пропускаются.
  • Бакетирование по столбцу, по которому вы не фильтруете данные. Вы несёте затраты на вычисление хеша при записи, но не получаете отсечения данных. Выберите столбец с наиболее селективным предикатом равенства или используйте RANDOM, если такого столбца нет.
  • Бакетирование по столбцу с низкой кардинальностью (например, gender). Два значения не могут распределиться по 16 бакетам; в результате одни tablet перегружены, а другие простаивают. Выберите столбец с высокой кардинальностью или объедините несколько столбцов в ключ бакетирования.
  • Сотни бакетов на партицию. В документации указан предел 128 бакетов на партицию; при его превышении пропускная способность записи падает, а потребление памяти FE растёт (практический ориентир: 100 GB памяти FE на 10 миллионов tablet). Сначала разбейте таблицу на партиции, затем на бакеты.
  • Крошечные tablet объёмом в мегабайты. Каждый tablet требует метаданных в FE и отдельного цикла compaction. Ориентируйтесь на объём сжатых данных от 1 GB до 10 GB на tablet (для таблиц Unique-Key объём должен оставаться меньше 10 GB).
  • Случайное бакетирование таблиц Unique или Aggregate. Случайное распределение нарушает корректность слияния; оно допускается только для таблиц Duplicate.

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