このトピックでは、ApsaraDB for SelectDB のパーティション分割とバケット分割機能、および ApsaraDB for SelectDB のパーティションとバケットの使用方法について説明します。
概要
大量のデータを効率的に保存および計算するために、ApsaraDB for SelectDB は、パーティションに基づいてデータを分割し、処理のために分散システムにデータを配布します。
ApsaraDB for SelectDB でサポートされているすべてのデータモデルは、次の 2 つのレベルでのデータパーティション分割をサポートしています。
1 レベル: データは 1 つのレベルでのみパーティション分割されます。
テーブルを作成するときに、データをパーティション分割するためのステートメントを記述する必要はありません。この場合、ApsaraDB for SelectDB は、ユーザーに対して透過的なデフォルトパーティションを作成します。1 レベルでデータをパーティション分割する場合、バケット分割のみがサポートされます。
2 レベル: データは 2 つのレベルでパーティション分割されます。
最初のレベルはパーティションであり、範囲パーティション分割とリストパーティション分割をサポートしています。
2 番目のレベルはバケットであり、タブレットとも呼ばれ、ハッシュパーティション分割をサポートしています。
パーティション分割
パーティションは、データを異なる範囲に分割するために使用されます。これは、テーブルを複数の子供テーブルに分割するのと似ています。このようにして、パーティション内のデータを管理できます。パーティションを使用する場合は、次の点に注意してください。
1 つ以上の列をパーティションキー列として指定できます。パーティションキー列はキー列である必要があります。
パーティションキー列の型に関係なく、パーティションキー値を二重引用符 (") で囲む必要があります。
作成できるパーティションの数は理論上無制限です。
パーティションを指定せずにテーブルを作成すると、システムは自動的に、名前がテーブルと同じで、テーブルの全データを含むパーティションを作成します。このパーティションはユーザーには表示されず、削除または変更することはできません。
パーティションを作成するときに、パーティションの範囲が別のパーティションの範囲と重複することはできません。
範囲パーティション分割
ほとんどの場合、範囲パーティション分割中は、新規データと履歴データの管理を容易にするために、時間列がパーティションキー列として使用されます。 範囲 パーティションでは、VALUES LESS THAN (...) ステートメントを実行することで、上限のみを指定できます。システムは、前のパーティションの上限を現在のパーティションの下限として使用して、範囲が左閉右開であるパーティションを作成します。VALUES [...] ステートメントを実行して上限と下限を指定することにより、範囲が左閉右開であるパーティションを作成することもできます。
単一列パーティション分割
このセクションでは、VALUES LESS THAN (...) ステートメントを実行してテーブルのパーティションを作成または削除した場合、テーブルのパーティション範囲がどのように変化するかについて説明します。次のサンプルコードは例を示しています。
test_tabletest_tableCREATE 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: [最小値, 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: [最小値, 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: [最小値, 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: [最小値, 2017-02-01) p201705: [2017-04-01, 2017-06-01)空の範囲は [2017-02-01,2017-04-01) になります。
`p201702new` VALUES LESS THAN ("2017-03-01")ステートメントを実行してパーティションを作成します。次のサンプルコードは、パーティション分割の結果を示しています。p201701: [最小値, 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: [最小値, 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: [(最小値, 最小値), ("2017-02-01", "1000") )
* p201702_2000: [("2017-02-01", "1000"), ("2017-03-01", "2000") )
* p201703_all: [("2017-03-01", "2000"), ("2017-04-01", 最小値)) 最後のパーティションでは、date 列の値のみが指定されています。デフォルトでは、最小値 が id 列の値として使用されます。データを挿入すると、システムはデータを指定されたパーティションキー値と順番に比較して、データを挿入するパーティションを決定します。次のサンプルコードは例を示しています。
* データ --> パーティション
* 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, and VARCHAR. パーティションキー値は列挙値です。インポートされるデータは、データにパーティションの列挙値のいずれかが含まれている場合にのみ、パーティションにヒットします。
VALUES IN (...) ステートメントを実行することで、各パーティションに含まれる列挙値を指定できます。
単一列パーティション分割
このセクションでは、VALUES IN (...) ステートメントを実行してテーブルのパーティションを作成または削除した場合、テーブルのパーティションがどのように変化するかについて説明します。次のサンプルコードは例を示しています。
test_table1 という名前のテーブルを作成します。
test_table1.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;test_table1テーブルが作成されると、次の 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...ステートメントは、各パーティション内のデータを分割するためのルールを記述します。パーティションが使用されていない場合、このステートメントはテーブルの全データを分割するためのルールを記述します。複数の列をバケット列として指定できます。 Aggregate モデルまたは Unique モデルの場合、バケット列はキー列である必要があります。 Duplicate モデルの場合、バケット列はキー列または値列にすることができます。バケット列は、パーティションキー列と同じにすることも、異なるものにすることもできます。
バケット列を指定するには、クエリのスループット と クエリの同時実行性 のバランスをとる必要があります。
複数のバケット列を指定すると、データはより均等に分散されます。クエリの条件にすべてのバケット列の等価条件が含まれていない場合、システムはすべてのバケットをスキャンします。これにより、クエリのスループットが向上し、クエリのレイテンシが短縮されます。この方法は、高スループットで低同時実行性のクエリシナリオに適しています。
1 つまたは少数のバケット列のみを指定すると、システムはポイントクエリに対して 1 つのバケットのみをスキャンします。この場合、複数のポイントクエリが同時に実行されると、システムは異なるバケットをスキャンする可能性があります。クエリの I/O 操作は互いに影響を与えません。特に、異なるバケットが異なるディスクに分散されている場合に顕著です。したがって、この方法は、高同時実行ポイントクエリシナリオに適しています。
作成できるバケットの数は理論上無制限です。
ベストプラクティス
構成に関する推奨事項パーティションおよびバケット
テーブル内のバケットの総数は、次の式に基づいて計算されます。バケットの総数 = パーティションの数 × 各パーティションのバケットの数。
クラスタの構成が変更されていない場合、テーブルのパーティション内の推奨バケット数は、クラスタ内のディスクの総数よりわずかに多くなります。
理論的には、単一のバケットに格納できるデータ量は無制限です。ただし、1 つのバケットに 1 ~ 10 GB のデータを格納することをお勧めします。単一のバケットに少量のデータが格納されている場合、データ集約がビジネス要件を満たせず、メタデータ管理の負荷が高くなる可能性があります。単一のバケットに大量のデータが格納されている場合、レプリカの移行と補充には役立ちません。スキーマの変更やロールアップなどの操作はバケットレベルで実行されるため、これらの操作が失敗した場合の再試行のコストが増加します。
単一のバケットに格納されるデータ量とバケット数のバランスをとることができない場合は、格納されるデータ量を優先することをお勧めします。
テーブルを作成するときは、各パーティションに同じ数のバケットが指定されます。ただし、
ADD PARTITIONステートメントを実行してパーティションを動的に作成するときは、新しいパーティションのバケット数を個別に指定できます。この機能を使用して、データの削減または拡張を処理できます。パーティションの作成後、パーティション内の バケット 数を変更することはできません。したがって、バケット数を決定するときは、事前にクラスタのスケールアウトを考慮する必要があります。たとえば、クラスタが 3 台のマシンで構成され、各マシンにディスクが 1 つあるとします。バケット数が 3 以下に設定されている場合、クラスタ内のマシンの数が増えても同時実行性は向上しません。
次の表は、10 個のバックエンドで構成され、各バックエンドにディスクが 1 つあるクラスタのパーティションとバケットの構成に関する推奨事項を示しています。
テーブルサイズ | 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; ステートメントを実行して、テーブルのサイズをクエリできます。
ランダム分散方式の構成と使用
集約または更新する必要のない詳細データについては、Duplicate モデルとランダム分散方式を使用してテーブルを作成できます。次のサンプルコードは例を示しています。
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 ステートメントを実行して、特定のパーティション内のデータを削除することもできます。
データの偏りを防ぐために、パーティションごとにバケット数を個別に指定できます。たとえば、データが日単位でパーティション分割されるシナリオでは、データ量が毎日大きく変動する場合、パーティションごとにバケット数をカスタマイズできます。簡単に識別でき、データが均等に分散されるバケット列を指定することをお勧めします。