パーティショニングとバケット分割の仕組み
このページでは、Doris がパーティション、バケット、Tablet にデータを分散する仕組みを説明します。高度なパーティションモードとバケット分割方式、設計上の推奨事項、運用コマンドも取り上げます。推奨される初期設定と選択の指針については、まずパーティショニングとバケット分割を参照してください。その後、基礎となる仕組みを理解したい場合にこのページをお読みください。
1. 概要
Doris は、テーブルのデータをパーティションとバケットの二段階でクラスタ全体に分散します。
- パーティション: 列の値(時刻や地域など)に基づいてテーブルを水平方向に小さな部分集合へ分割し、クエリ時の枝刈りやデータのライフサイクル管理を容易にします。
- バケット: 各パーティション内のデータを複数のデータシャード(Tablet)へさらに均等に分散し、クラスタの並列処理能力を十分に活用します。

データの流れは次のようにまとめられます。
Table ──► Partition ──► Bucket ──► Tablet (data shard, stored on BE nodes)
適切なパーティショニングとバケット分割には、クエリの高速化(パーティションの枝刈りと並列スキャン)、管理の簡素化(時刻に基づくアーカイブやクリーンアップ)、書き込みの均等化(ホットスポットのリスク低減)という利点があります。
2. 基本概念
2.1 テーブルの構造: 行と列
Doris では、データは論理的にテーブルとして表現されます。テーブルは行(Row)と列(Column)で構成されます。
| 概念 | 説明 |
|---|---|
| 行 | ユーザーデータの1行 |
| 列 | データの1行に含まれる各フィールドを表します |
列は、さらに次の2種類に分けられます。
| 列の種類 | ビジネス上の対応 | 説明 |
|---|---|---|
| Key 列 | ディメンション列 | CREATE TABLE 文で unique key、aggregate key、または duplicate key キーワードの後に指定する列 |
| Value 列 | 指標列 | Key 列以外の列。集約方法はテーブル作成時に指定します |
Aggregate モデルでは、Key 列が同じ行は1行に集約されます。Value 列の集約方法は、テーブル作成時にユーザーが指定します。Aggregate モデルの詳細については、Doris のデータモデルを参照してください。
2.2 パーティション
パーティションは、テーブル内の特定の列の値に基づいて、データを互いに重複しない小さな部分集合に分割します。データの各行は必ず1つのパーティションに属します。パーティションは論理的な管理の最小単位です。
Doris は、次の2つのパーティション方式をサポートします。
- Range パーティション: 数値または時刻の範囲で分割します。時系列データでよく使われます。
- List パーティション: 列挙された値の集合で分割します。地域やカテゴリなど、離散的なディメンションによる分割でよく使われます。
テーブル作成時にパーティションを指定しない場合、Doris はユーザーからは見えないデフォルトのパーティションを生成し、テーブル内のすべてのデータをそこに格納します。
適切なパーティション設計には、次の利点があります。
- クエリの高速化: パーティションの枝刈りにより、クエリ条件に一致しないパーティションをスキップできます。これにより、Doris がスキャンするデータ量と I/O が減ります。特に大規模なデータセットで効果的です。
- 管理の簡素化: 時刻や地域で分割すると、アーカイブ、クリーンアップ、バックアップが容易になります。たとえば、時刻でパーティショニングすれば、過去のデータと新しく入るデータを別々に管理できます。
2.3 バケット
バケット分割では、一定の規則に従って、パーティション内のデータを互いに重複しない小さなデータ単位へさらに分割します。データの各行は必ず1つのバケットに属します。
パーティションは、列値の範囲またはリストに基づいてデータを分割します。一方、バケット分割は、パーティション内の各バケットにデータを均等に分散し、偏りを抑えて並列スキャンを可能にすることを目的とします。実際の分散状況は、バケット分割方式とキーの分布によって異なります。
Doris は、次の2つのバケット分割方式をサポートします。
- Hash バケット分割: バケット分割に使う列の値について
crc32ハッシュを計算し、バケット数で割った余りを使用してデータを均等に分散します。 - Random バケット分割: データをバケットにランダムに割り当てます。Random バケット分割では、
load_to_single_tabletパラメータを併用すると、少量のデータを高速に書き込む処理を最適化できます。
適切なバケット分割には、次の利点があります。
- データ分散の均等化: 適切なバケットキーを選ぶことで、データの偏りや、ノードまたはディスク間の負荷の偏りを抑えられます。
- ホットスポットの削減: 処理を複数のバケットに分散することで、過負荷を抑え、安定性を高められます。
- 同時実行性能の向上: Doris は同じパーティション内の異なるバケットを並列に読み取るため、スループットが向上します。
2.4 Tablet とノードのアーキテクチャ
バケットは、物理的にはTablet(データシャード)に対応します。Tablet は Doris におけるデータ管理の最小単位であり、データの移動やレプリケーションなどの操作における基本的な物理単位でもあります。
Doris クラスタは、次の2種類のノードで構成されます。
- FE ノード(Frontend): テーブルや Tablet の情報など、クラスタのメタデータを管理し、SQL の解析と実行計画の作成を担当します。
- BE ノード(Backend): Tablet のデータを格納し、計算タスクを実行します。BE からの結果は FE が集約し、ユーザーに返します。
データの書き込みとクエリの実行では、パーティションとバケットが次のように連携します。
- データの書き込み: データの各行は、まずパーティショニングの方針に従って対応するパーティションに割り当てられ、次にバケット分割の方針に従って、そのパーティション内の特定の Tablet に割り当てられます。
- クエリの実行: FE オプティマイザはクエリ条件に基づいて不要なパーティションとバケットを枝刈りし、スキャン範囲を最小化します。JOIN や集約クエリでは、ノード間の Shuffle が発生することがあります。適切なパーティショニングとバケット分割の設計により、必要に応じて Colocate を組み合わせることで、Shuffle のコストを大幅に削減できます。
3. 最初の CREATE TABLE の例
Doris でのテーブル作成は同期的に実行されます。SQL 文の実行が完了すると結果が返され、正常に結果が返ればテーブルは作成されています。CREATE TABLE の完全な構文については、CREATE TABLEを参照するか、HELP CREATE TABLE でヘルプを確認してください。
次に、Range パーティショニングと Hash バケット分割を使用する最小限の CREATE TABLE の例を示します。
-- 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"
);
この例で書き込みとクエリの流れを理解する:
- 書き込み時には、データの各行がまず
dateに基づいて対応する Range パーティションに入ります(たとえば、2017-02-15はp201702に入ります)。次に、user_idのハッシュ値に基づいて、そのパーティションに属する16個の Tablet のいずれかに割り当てられます。 WHERE date = '2017-02-15'を指定したクエリでは、オプティマイザはp201702パーティションのみをスキャンします。さらにuser_id = 123を指定すると、対象は1つの Tablet に絞られます。
4. 高度な機能: パーティションモード
Doris では、テーブル作成時にパーティションを手動で宣言するほか、時刻に基づくスケジュールに従った自動作成(Dynamic Partition)と、入力データに応じた必要時の作成(Auto Partition)もサポートします。3つのモードの違いは次のとおりです。
| パーティションモード | パーティションが作成されるタイミング | 適用場面 |
|---|---|---|
| Manual partition | テーブル作成時に明示的に宣言するか、ALTER で追加する | パーティションの集合が安定しており、細かな制御が必要な場合 |
| Dynamic partition | 時刻に基づくスケジュール規則に従って、システムが自動的に作成・再利用する | 過去 N 日/週/月分のローリングパーティションを自動的に維持したい時系列データ |
| Auto partition | データの書き込み時に必要に応じて作成する | マルチテナントや疎な時系列データなど、パーティションの値を予測できず、事前作成を避けたい場合 |
各モードですぐに使える CREATE TABLE の例と、Auto Partition と Dynamic Partition を組み合わせる例については、Auto Partition、Dynamic Partition、Manual Partitionを参照してください。
5. 高度な機能: バケット分割
5.1 Auto Bucketing
適切なバケット数が分からない場合は、BUCKETS AUTO を設定すると、Doris が推定データ量(estimate_partition_size)に基づいてバケット数を決定します。これは極めて大きなテーブルには適していません。詳細については、データのバケット分割を参照してください。
5.2 Colocate
JOIN や集約クエリを頻繁に実行する複数の大きなテーブルでは、Colocate 戦略を有効にできます。バケット分割に使う列の値が同じデータを同じ物理ノードに配置することで、ノード間の Shuffle を回避し、クエリ性能を大幅に向上させます。詳しい使用方法については、Colocate Join のドキュメントを参照してください。
6. 設計上の推奨事項
適切なパーティショニングとバケット分割の設計では、クエリ性能、データ管理、システムの安定性のバランスを取る必要があります。一般的な指針を以下に示します。
6.1 設計目標
- データの均等な分散: 一部のノードに過負荷をもたらすデータの偏りを避けるため、カーディナリティの高い列をバケット分割に使う列として選びます。
- クエリ性能の最適化: パーティションの枝刈りはスキャンするデータ量を大幅に削減できます。適切なバケット数は計算処理の並列性を高めます。必要に応じて Colocate を使用し、Shuffle のコストを削減します。
- 柔軟なデータ管理: 時刻によるパーティショニングはコールド/ホットデータの階層化(HDD/SSD)に役立ちます。また、過去のパーティションを削除することでストレージ容量を回収できます。
6.2 経験則に基づく値と制限
- メタデータの規模: 各 Tablet のメタデータは FE と BE の両方に保存されるため、その規模を適切に管理する必要があります。
- Tablet が1,000万個あるごとに、FE には少なくとも約100 GBのメモリが必要です。
- 単一の BE が保持する Tablet の数は20,000個未満にする必要があります。
- 書き込みスループット:
- パーティションあたりのバケット数は128未満にすることを推奨します。バケットが多すぎると、書き込み性能に大きな影響が出ます。
- 分散した書き込みによって小さなファイルが大量に生成されるのを避けるため、各書き込みの対象を少数のパーティションに集中させます。
- パーティション列とバケット分割に使う列の選択:
- パーティション列には、時刻またはカーディナリティの低い列挙値を優先します。
- バケット分割に使う列には、均等な分散を確保するため、カーディナリティの高い列(
user_idなど)を選びます。 - 単一の Tablet のデータ量は1-10 GBにすることを推奨します。
7. パーティションの表示と変更
7.1 SHOW CREATE TABLE で 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)