ApsaraDB for SelectDBApsaraDB for SelectDB
概要
大量のデータを効率的に保存し処理するために、ApsaraDB for SelectDB はデータをパーティションに分割し、クラスター全体に分散させて並列処理を行います。
ApsaraDB for SelectDB のすべてのデータモデルは、以下の 2 段階のデータパーティショニングをサポートしています:
-
1 段階:データは 1 段階でのみパーティショニングされます。
-
パーティションを指定せずにテーブルを作成すると、ApsaraDB for SelectDB はユーザーからは見えないデフォルトパーティションを作成します。この場合、バケット化のみがサポートされます。
-
-
2 段階:データは 2 段階でパーティショニングされます。
-
第 1 段階はパーティションで、範囲パーティショニングとリストパーティショニングをサポートします。
-
第 2 段階はバケットで、タブレットとも呼ばれ、ハッシュパーティショニングをサポートします。
-
パーティショニング
パーティションは、テーブルを複数のサブテーブルに分割して管理を容易にするのと同様に、データを個別の範囲に分割します。パーティションを使用する際は、以下の点にご注意ください:
-
1 つ以上の列をパーティションキー列として指定できます。パーティションキー列はキー列でなければなりません。
-
パーティションキー列の型に関係なく、パーティションキーの値は二重引用符 (") で囲む必要があります。
-
作成できるパーティションの数に理論上の制限はありません。
-
パーティションを指定せずにテーブルを作成すると、システムはテーブルと同じ名前で、テーブルの全データを含むパーティションを自動的に作成します。このパーティションはユーザーには見えず、削除も変更もできません。
-
パーティションを作成する際、そのパーティションの範囲は、他のパーティションの範囲と重複できません。
範囲パーティショニング
時間列は、新規データと履歴データの管理を容易にするため、範囲パーティショニングのパーティションキー列として一般的に使用されます。 範囲パーティションでは、 VALUES LESS THAN (...) ステートメントを実行して上限のみを指定できます。システムは、前のパーティションの上限を現在のパーティションの下限として使用し、左閉右開の範囲を持つパーティションを作成します。また、 VALUES [... ステートメントを実行して上限と下限を指定し、左閉右開の範囲を持つパーティションを作成することもできます。
単一列でのパーティショニング
以下の例は、 VALUES LESS THAN (...) ステートメントを使用してパーティションを作成または削除した際のパーティション範囲の変化を示します。
-
test_tableという名前のテーブルを作成します。CREATE TABLE IF NOT EXISTS test_db.test_table ( `user_id` LARGEINT NOT NULL COMMENT "ユーザー ID", `date` DATE NOT NULL COMMENT "データがテーブルにインポートされた日付", `timestamp` DATETIME NOT NULL COMMENT "データがテーブルにインポートされた時刻", `city` VARCHAR(20) COMMENT "ユーザーが居住する都市", `age` SMALLINT COMMENT "ユーザーの年齢", `sex` TINYINT COMMENT "ユーザーの性別", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "ユーザーの最終訪問日時", `cost` BIGINT SUM DEFAULT "0" COMMENT "ユーザーの消費金額", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "ユーザーの最大滞在時間", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "ユーザーの最小滞在時間" )ENGINE=OLAP AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) 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") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;test_tableテーブルが作成されると、以下の 3 つのパーティションが自動的に作成されます:p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01) -
ALTER TABLE test_db.test_table ADD PARTITION p201705 VALUES LESS THAN ("2017-06-01");ステートメントを実行して、p201705という名前のパーティションを作成します。パーティショニングの結果を以下のサンプルコードに示します:p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01) p201705: [2017-04-01, 2017-06-01) -
ALTER TABLE test_db.test_table DROP PARTITION p201703;ステートメントを実行して、p201703パーティションを削除します。パーティショニングの結果を以下のサンプルコードに示します:p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)重要上記の例では、
p201703パーティションを削除した後も、p201702パーティションとp201705パーティションの範囲は変更されません。ただし、2 つの範囲の間にある [2017-03-01, 2017-04-01) の範囲は空になります。この範囲内の既存データも削除されます。この場合、インポートするデータがこの空の範囲内にあると、データをインポートできません。 -
p201702パーティションを削除します。パーティショニングの結果を以下のサンプルコードに示します:p201701: [MIN_VALUE, 2017-02-01) p201705: [2017-04-01, 2017-06-01)空の範囲は [2017-02-01, 2017-04-01) になります。
-
`p201702new` VALUES LESS THAN ("2017-03-01")ステートメントを実行して、パーティションを作成します。パーティショニングの結果を以下のサンプルコードに示します:p201701: [MIN_VALUE, 2017-02-01) p201702new: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)空の範囲は [2017-03-01, 2017-04-01) になります。
-
p201701 パーティションを削除し、
`p201612` VALUES LESS THAN ("2017-01-01")ステートメントを実行してパーティションを作成します。パーティショニングの結果を以下のサンプルコードに示します:p201612: [MIN_VALUE, 2017-01-01) p201702new: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01)空の範囲は [2017-01-01, 2017-02-01) と [2017-03-01, 2017-04-01) になります。
上記の例で示したように、パーティションを削除した後も既存のパーティション範囲は変更されませんが、空の範囲が出現する可能性があります。 VALUES LESS THAN (...) ステートメントを使用して新しいパーティションを作成する場合、その下限は前のパーティションの上限に隣接していなければなりません。
複数列でのパーティショニング
複数列に基づいてデータをパーティショニングできます。例:
PARTITION BY RANGE(`date`, `id`)
(
PARTITION `p201701_1000` VALUES LESS THAN ("2017-02-01", "1000"),
PARTITION `p201702_2000` VALUES LESS THAN ("2017-03-01", "2000"),
PARTITION `p201703_all` VALUES LESS THAN ("2017-04-01")
)
この例では、 date 列と id 列がパーティションキー列として指定されています。date 列は DATE 型で、id 列は INT 型です。パーティショニングの結果を以下のサンプルコードに示します:
* p201701_1000: [(MIN_VALUE, MIN_VALUE), ("2017-02-01", "1000") )
* p201702_2000: [("2017-02-01", "1000"), ("2017-03-01", "2000") )
* p201703_all: [("2017-03-01", "2000"), ("2017-04-01", MIN_VALUE))
最後のパーティションでは、 date 列の値のみが指定されています。デフォルトでは、 id 列の値として MIN_VALUE が使用されます。データを挿入する際、システムは指定されたパーティションキーの値とデータを順番に比較し、データがどのパーティションに挿入されるかを決定します。以下のサンプルコードに例を示します:
* データ --> パーティション
* 2017-01-01, 200 --> p201701_1000
* 2017-01-01, 2000 --> p201701_1000
* 2017-02-01, 100 --> p201701_1000
* 2017-02-01, 2000 --> p201702_2000
* 2017-02-15, 5000 --> p201702_2000
* 2017-03-01, 2000 --> p201703_all
* 2017-03-10, 1 --> p201703_all
* 2017-04-01, 1000 --> インポートに失敗。
* 2017-05-01, 1000 --> インポートに失敗。
リストパーティショニング
リストパーティショニングは、 BOOLEAN、TINYINT、SMALLINT、INT、BIGINT、LARGEINT、DATE、DATETIME、CHAR、VARCHAR のデータ型のパーティションキー列をサポートします。 データは、パーティションの列挙値のいずれかと一致する場合にのみ、そのパーティションにインポートされます。
VALUES IN (...) ステートメントを実行して、各パーティションに含まれる列挙値を指定できます。
単一列でのパーティショニング
以下の例は、 VALUES IN (...) ステートメントを使用してパーティションを作成または削除した際のパーティションの変化を示します。
-
example_list_tbl1という名前のテーブルを作成します。CREATE TABLE IF NOT EXISTS test_db.example_list_tbl1 ( `user_id` LARGEINT NOT NULL COMMENT "ユーザー ID", `date` DATE NOT NULL COMMENT "データがテーブルにインポートされた日付", `timestamp` DATETIME NOT NULL COMMENT "データがテーブルにインポートされた時刻", `city` VARCHAR(20) NOT NULL COMMENT "ユーザーが居住する都市", `age` SMALLINT COMMENT "ユーザーの年齢", `sex` TINYINT COMMENT "ユーザーの性別", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "ユーザーの最終訪問日時", `cost` BIGINT SUM DEFAULT "0" COMMENT "ユーザーの消費金額", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "ユーザーの最大滞在時間", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "ユーザーの最小滞在時間" ) ENGINE=olap AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY LIST(`city`) ( PARTITION `p_cn` VALUES IN ("Beijing", "Shanghai", "Hong Kong"), PARTITION `p_usa` VALUES IN ("New York", "San Francisco"), PARTITION `p_jp` VALUES IN ("Tokyo") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16;example_list_tbl1テーブルが作成されると、以下の 3 つのパーティションが自動的に作成されます:p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo") -
`p_uk` VALUES IN ("London")ステートメントを実行して、パーティションを作成します。パーティショニングの結果を以下のサンプルコードに示します:p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo") p_uk: ("London") -
p_jpパーティションを削除します。パーティショニングの結果を以下のサンプルコードに示します:p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_uk: ("London")
複数列でのパーティショニング
複数列に基づいてデータをパーティショニングできます。例:
PARTITION BY LIST(`id`, `city`)
(
PARTITION `p1_city` VALUES IN (("1", "Beijing"), ("1", "Shanghai")),
PARTITION `p2_city` VALUES IN (("2", "Beijing"), ("2", "Shanghai")),
PARTITION `p3_city` VALUES IN (("3", "Beijing"), ("3", "Shanghai"))
)
この例では、 id 列と city 列がパーティションキー列として指定されています。id 列は INT 型で、city 列は VARCHAR 型です。パーティショニングの結果を以下のサンプルコードに示します:
* p1_city: (("1", "Beijing"), ("1", "Shanghai"))
* p2_city: (("2", "Beijing"), ("2", "Shanghai"))
* p3_city: (("3", "Beijing"), ("3", "Shanghai"))
データを挿入する際、システムは指定されたパーティションキーの値とデータを順番に比較し、データがどのパーティションに挿入されるかを決定します。以下のサンプルコードに例を示します:
* データ ---> パーティション
* 1, Beijing ---> p1_city
* 1, Shanghai ---> p1_city
* 2, Shanghai ---> p2_city
* 3, Beijing ---> p3_city
* 1, Tianjin ---> インポートに失敗。
* 4, Beijing ---> インポートに失敗。
バケット化
データは、指定されたバケット列のハッシュ値に基づいてバケット全体に分散されます。
-
パーティションが使用される場合、
DISTRIBUTED...ステートメントは各パーティション内のデータを分割するルールを定義します。パーティションが使用されない場合、このステートメントはテーブルの全データを分割するルールを定義します。 -
複数の列をバケット列として指定できます。集約モデルまたはユニークモデルの場合、バケット列はキー列でなければなりません。デュプリケートモデルの場合、バケット列はキー列または値列にすることができます。バケット列は、パーティションキー列と同じでも異なっていてもかまいません。
-
バケット列を選択する際は、 クエリスループットと クエリ同時実行性のバランスを取る必要があります。
-
複数のバケット列を指定すると、データはより均等に分散されます。ただし、クエリにすべてのバケット列に対する同値条件が含まれていない場合、システムはすべてのバケットをスキャンします。これによりクエリスループットが向上し、レイテンシが短縮されるため、高スループット、低同時実行性のシナリオに適しています。
-
1 つまたは少数のバケット列のみを指定した場合、システムはポイントクエリに対して 1 つのバケットのみをスキャンします。複数のポイントクエリが同時に実行されると、それぞれが異なるバケットをスキャンする可能性があり、特にバケットが異なるディスク上にある場合、それらの I/O 操作は互いに干渉しません。これは、高同時実行性のポイントクエリシナリオに適しています。
-
-
作成できるバケットの数に理論上の制限はありません。
ベストプラクティス
パーティションとバケットの設定に関する推奨事項
-
テーブル内のバケットの総数は、次の式に基づいて計算されます:バケット総数 = パーティション数 × 各パーティションのバケット数。
-
クラスターの構成が変更されない場合、テーブルのパーティション内の推奨バケット数は、クラスター内のディスク総数よりわずかに多くなるように設定できます。
-
バケットあたり 1 GB から 10 GB のデータを格納することを推奨します。バケット内のデータが少なすぎると、集約効果が弱まり、メタデータ管理のオーバーヘッドが増加します。バケット内のデータが多すぎると、レプリカの移行や補充が遅くなり、スキーマ変更やロールアップなどのバケットレベルの操作を再試行するコストが増加します。
-
バケットあたりのデータサイズとバケット数のバランスが取れない場合は、バケットあたりのデータサイズを優先してください。
-
テーブルを作成する際、各パーティションに同じ数のバケットが指定されます。ただし、
ADD PARTITIONステートメントを実行して動的にパーティションを作成する場合、新しいパーティションのバケット数を個別に指定できます。この機能を使用して、データの縮小または拡大に対応できます。 -
パーティション作成後に、そのパーティション内のバケット数を変更できません。将来のクラスターのスケールアウトを考慮してバケット数を計画してください。たとえば、それぞれ 1 つのディスクを持つ 3 台のマシンで構成されるクラスターがあり、バケット数を 3 以下に設定した場合、マシンを追加しても同時実行性は向上しません。
次の表は、それぞれ 1 つのディスクを持つ 10 のバックエンドで構成されるクラスターに対する、パーティションとバケットの推奨事項を示しています。
|
テーブルサイズ |
500 MB |
5 GB |
50 GB |
500 GB |
5 TB |
|
パーティション |
パーティションは不要です。 |
パーティションは不要です。 |
パーティションは不要です。 |
各パーティションのサイズは 50 GB です。 |
各パーティションのサイズは 50 GB です。 |
|
バケット |
テーブルには 4〜8 個のバケットが含まれます。 |
テーブルには 8〜16 個のバケットが含まれます。 |
テーブルには 32 個のバケットが含まれます。 |
各パーティションには 16〜32 個のバケットが含まれます。 |
各パーティションには 16〜32 個のバケットが含まれます。 |
SHOW DATA; ステートメントを実行して、テーブルのサイズを照会できます。
ランダム分散方式の設定と使用
集約や更新を必要としない詳細データの場合、デュプリケートモデルとランダム分散方式を使用してテーブルを作成できます。例:
CREATE TABLE IF NOT EXISTS test.example_tbl
(
`timestamp` DATETIME NOT NULL COMMENT "ログが生成された時刻",
`type` INT NOT NULL COMMENT "ログのタイプ",
`error_code` INT COMMENT "エラーコード",
`error_msg` VARCHAR(1024) COMMENT "エラーメッセージ",
`op_id` BIGINT COMMENT "所有者 ID",
`op_time` DATETIME COMMENT "エラーが処理された時刻"
)
DUPLICATE KEY(`timestamp`, `type`, `error_code`)
DISTRIBUTED BY RANDOM BUCKETS 16;
-
デュプリケートキーモデルを使用するテーブルには、集約タイプが REPLACE の列は含まれません。テーブルのデータバケット化モードを RANDOM に設定できます。これにより、深刻なデータスキューを防ぐことができます。テーブルにデータをインポートする際、1 つのインポートジョブはパーティションのランダムなバケットにデータを書き込みます。
-
RANDOM バケット化では、バケット列が指定されていないため、バケット列の値で特定のバケットを照会できません。システムは、一致したパーティション内のすべてのバケットをスキャンします。このアプローチは、高同時実行性のポイントクエリよりも、テーブル全体の集約クエリや分析に適しています。
-
ランダム分散方式を使用するデュプリケートモデルのテーブルでは、
load_to_single_tabletパラメーターを true (デフォルト:false) に設定することで、単一バケットインポートモードを有効にできます。このモードでは、各インポートジョブはパーティションごとに 1 つのバケットにのみデータを書き込むため、インポートの同時実行性とスループットが向上し、コンパクションによる書き込み増幅が削減され、クラスターの安定性維持に役立ちます。
パーティションとバケットを併用するシナリオ
-
テーブルに時間ディメンション列や順序付けられた値を持つディメンション列が含まれる場合、これらのディメンション列をパーティションキー列として使用できます。データをパーティショニングする粒度は、インポートの頻度と各パーティションに格納されるデータ量に基づいて評価できます。
-
過去 N 日間のデータのみを保持するために履歴データを削除したい場合は、複合パーティショニングを使用して履歴パーティションを削除できます。または、DELETE ステートメントを実行して、特定のパーティション内のデータを削除することもできます。
-
データスキューを防ぐために、各パーティションのバケット数を個別に指定できます。たとえば、日単位でデータをパーティショニングするシナリオで、日々のデータ量が大幅に異なる場合、各パーティションのバケット数をカスタマイズできます。識別が容易で、データを均等に分散できるバケット列を指定することを推奨します。