ハッシュクラスタリングテーブルは、シャッフルとソートのプロパティを使用してデータを整理します。MaxCompute はこれらのプロパティを使用して実行計画を最適化し、効率を向上させてリソースを節約します。本トピックでは、MaxCompute でハッシュクラスタリングテーブルを使用する方法について説明します。
背景情報
テーブルの結合は、MaxCompute のクエリでよく使用されます。たとえば、次のクエリは単純な内部結合を実行します。このクエリは、 id 列に基づいてテーブル t1 とテーブル t2 を結合します。
SELECT t1.a, t2.b FROM t1 JOIN t2 ON t1.id = t2.id;MaxCompute は、主に 3 つの方法で結合を実装します。
ブロードキャストハッシュ結合
結合するテーブルの 1 つが小さい場合、MaxCompute はこの方法を使用して、小さいテーブルをすべての結合タスクインスタンスにブロードキャストします。次に、大きいテーブルとハッシュ結合を実行します。
シャッフルハッシュ結合
結合テーブルが大きい場合、ブロードキャストはできません。代わりに、MaxCompute は結合キーに基づいて両方のテーブルに対してハッシュシャッフルを実行します。同じキー値を持つレコードは同じハッシュ結果を生成し、同じ結合タスクインスタンスに送信されます。各インスタンスは、小さい方のデータセットのハッシュテーブルを構築し、大きい方のデータセットに対してシーケンシャル読み取り結合を実行します。
ソートマージ結合
結合テーブルが大きすぎ、ハッシュテーブルを構築するための十分なメモリがない場合、シャッフルハッシュ結合は使用できません。この方法では、まず結合キーでハッシュシャッフルを実行し、結合キーでデータをソートしてから、結合の両側のデータをマージします。次の図にこのプロセスを示します。
MaxCompute で一般的なデータ量と規模の場合、ほとんどのケースでソートマージ結合が使用されます。ただし、これは非常にコストのかかる操作です。図に示すように、シャッフル操作には計算が必要であり、中間結果をディスクに書き込む必要があります。その後のレデューサーは、このデータを読み取ってソートする必要があります。M個のマッパーとR個のレデューサーがあるシナリオでは、M × R回の I/O 読み取り操作が発生します。対応する Fuxi 物理実行計画を以下に示します。2 つのマッパーステージと 1 つの Join ステージが必要です。赤い部分は、シャッフルとソート操作を示しています。
さらに、一部の結合は繰り返し発生する場合があります。たとえば、クエリを次のように変更した場合を考えます。SELECT t1.c, t2.d FROM t1 JOIN t2 ON t1.id = t2.id;選択される列は異なりますが、結合操作は同じです。シャッフルとソートのプロセス全体も同じです。
あるいは、クエリを次のように変更した場合を考えます。
SELECT t1.c, t3.d FROM t1 JOIN t3 ON t1.id = t3.id;このクエリは、テーブル t1 とテーブル t3 を結合します。テーブル t1 については、シャッフルとソートのプロセス全体は依然として同じです。
したがって、初期テーブルデータがハッシュシャッフルおよびソートされて格納されている場合、後続のクエリではデータの再シャッフルと再ソートを回避できます。利点は、テーブル作成時の 1 回限りのコストで、後続のクエリでの繰り返しのシャッフルと結合のコストを節約できることです。結合の Fuxi 物理実行計画は、次の図に示すように変更されます。この変更により、シャッフルとソート操作が省略されるだけでなく、クエリが 3 つのステージから 1 つに削減されます。

注意事項
ハッシュクラスタリングテーブルの作成
次のステートメントを使用して、ハッシュクラスタリングテーブルを作成できます。ハッシュキーであるクラスターキーと、ハッシュバケットの数を指定する必要があります。ソートはオプションです。ただし、最適なパフォーマンスを得るには、ほとんどの場合、ソートキーをクラスターキーと同じに設定することを推奨します。
構文
CREATE TABLE [IF NOT EXISTS] <table_name> [(<col_name> <data_type> [comment <col_comment>], ...)] [comment <table_comment>] [PARTITIONED BY (<col_name> <data_type> [comment <col_comment>], ...)] [CLUSTERED BY (<col_name> [, <col_name>, ...]) [SORTED BY (<col_name> [ASC | DESC] [, <col_name> [ASC | DESC] ...])] INTO <number_of_buckets> BUCKETS] [AS <select_statement>]例
非パーティションテーブル
CREATE TABLE T1 (a string, b string, c bigint) CLUSTERED BY (c) SORTED by (c) INTO 1024 BUCKETS;パーティションテーブル
CREATE TABLE T1 (a string, b string, c bigint) PARTITIONED BY (dt string) CLUSTERED BY (c) SORTED by (c) INTO 1024 BUCKETS;
プロパティ
CLUSTERED BY
ハッシュキーを指定します。MaxCompute は、指定された列に対してハッシュ操作を実行し、ハッシュ値に基づいてデータをバケットに分散します。データスキューを回避し、ホットスポットを防ぎ、適切な並列実行を実現するには、
CLUSTERED BY句に値の範囲が広く、重複キー値が少ない列を選択してください。結合を最適化するために、従来のデータベースの主キーに似た、頻繁に使用される結合キーまたは集約キーを選択することもできます。SORTED BY
バケット内のフィールドのソート順を指定します。パフォーマンスを向上させるには、
SORTED BYキーをCLUSTERED BYキーと同じに設定することを推奨します。SORTED BY句が指定されている場合、MaxCompute は自動的にインデックスを作成し、このインデックスを使用してクエリを高速化します。INTO number_of_buckets BUCKETS
ハッシュバケットの数を指定します。この数は必須であり、データ量に依存します。バケット数が多いほど並行性が高まり、ジョブの実行時間を短縮できます。ただし、バケット数が多すぎると、過剰な数の小さなファイルが作成され、高い並行性により CPU 時間が増加する可能性があります。各バケットのサイズが 500 MB から 1 GB になるようにバケット数を設定することを推奨します。非常に大きなテーブルの場合、この数はさらに大きくなる可能性があります。シャッフルとソートのステップを省略して結合を最適化するには、2 つのテーブルのバケット数が互いに倍数である必要があります (
256と512など)。バケット数には、512、1024、2048、4096 などの 2 のべき乗を使用することを推奨します。これにより、システムがハッシュバケットを自動的に分割およびマージし、シャッフルとソートのステップを省略できます。
テーブルのハッシュクラスタリングプロパティの変更
ALTER TABLE ステートメントを使用して、パーティションテーブルのハッシュクラスタリングプロパティを追加または削除できます。
構文
-- テーブルをハッシュクラスタリングテーブルに変更 ALTER TABLE <table_name> [CLUSTERED BY (<col_name> [, <col_name>, ...]) [SORTED BY (<col_name> [ASC | DESC] [, <col_name> [ASC | DESC] ...])] INTO <number_of_buckets> BUCKETS]; -- ハッシュクラスタリングテーブルを非ハッシュクラスタリングテーブルに変更 ALTER TABLE <table_name> NOT CLUSTERED;注意事項
ALTER TABLEステートメントは、パーティションテーブルのクラスタリングプロパティのみを変更します。非パーティションテーブルの場合、クラスタリングプロパティは設定後に変更できません。ALTER TABLEステートメントは、INSERT OVERWRITEによって生成されるパーティションを含む、パーティションテーブルの新しいパーティションにのみ影響します。新しいパーティションは、新しいクラスタリングプロパティに従って格納されます。既存のデータパーティションは変更されません。このステートメントは新しいパーティションにのみ影響するため、
PARTITION句は指定しないでください。
ALTER TABLE ステートメントは、既存のテーブルに適しています。新しいクラスタリングプロパティを追加すると、新しいパーティションはハッシュクラスタリングを用いて格納されます。
テーブルプロパティの確認
ハッシュクラスタリングテーブルを作成した後、次のコマンドを実行してそのプロパティを表示できます。ハッシュクラスタリングのプロパティは、拡張情報セクションに表示されます。
DESC EXTENDED <table_name>;返される結果の例は次のとおりです。
| Owner: ALIYUN$ | Project:
| TableComment:
|
| CreateTime: 2017-06-19 14:10:55
| LastDDLTime: 2017-06-19 14:10:55
| LastModifiedTime: 2017-06-19 14:13:13
|
| InternalTable: YES | Size: 21680295746
|
| Native Columns:
|
| Field | Type | Label | Comment
|
| l_orderkey | bigint | |
| l_partkey | bigint | |
| l_suppkey | bigint | |
| l_linenumber | bigint | |
| l_quantity | double | |
| l_extendedprice | double | |
| l_discount | double | |
| l_tax | double | |
| l_returnflag | string | |
| l_linestatus | string | |
| l_shipdate | string | |
| l_commitdate | string | |
| l_receiptdate | string | |
| l_shipinstruct | string | |
| l_shipmode | string | |
| l_comment | string | |
|
| Extended Info:
|
| TableID:
| IsArchived: false
| PhysicalSize: 65040887238
| FileNum: 1001
| ClusterType: hash
| BucketNum: 1000
| ClusterColumns: [l_orderkey]
| SortColumns: [l_orderkey ASC]パーティションテーブルの場合、テーブルプロパティを表示した後、次のコマンドを実行してパーティションプロパティを表示できます。
DESC EXTENDED <table_name> partition(<pt_spec>);返される結果の例は次のとおりです。
| PartitionSize: 754
| CreateTime: 2017-07-07 14:01:03
| LastDDLTime: 2017-07-07 14:01:03
| LastModifiedTime: 2017-07-07 14:01:03
| IsExstore: false
| IsArchived: false
| PhysicalSize: 2262
| FileNum: 2
| ClusterType: hash
| BucketNum: 500
| ClusterColumns: [c1]
| SortColumns: [c1 ASC]ハッシュクラスタリングの利点
バケットプルーニングとインデックスの最適化
CREATE TABLE t1 (id bigint,
a string,
b string)
CLUSTERED BY (id)
SORTED BY (id) into 1000 BUCKETS;
...
SELECT t1.a, t1.b FROM t1 WHERE t1.id=12345;idid
クエリは、値
12345に対応するハッシュバケットを検索します。これにより、1,000 個すべてのバケットではなく、1 つのバケットをスキャンするだけで済みます。このプロセスはバケットプルーニングと呼ばれます。バケット内のデータは
idでソートされているため、MaxCompute は自動的にインデックスを作成し、インデックス検索を使用して該当するレコードを直接特定します。
この最適化により、マッパーの数が大幅に削減されるだけでなく、マッパーがインデックスを使用してデータページを直接特定できるようになります。これにより、ロードおよび読み取られるデータ量が大幅に削減されます。
たとえば、あるビッグデータタスクでは、1,111 個のマッパーを起動し、427 億件のレコードを読み取って 26 件の一致するレコードを見つけました。合計実行時間は 1 分 48 秒でした。ハッシュクラスタリングテーブルを使用すると、同じデータに対する同じクエリで、単一のバケットを直接特定し、インデックスを使用してクエリデータを含むページのみを読み取ることができます。このプロセスでは、わずか 4 個のマッパーを使用し、10,000 件のレコードを読み取り、わずか 6 秒で完了します。
集約の最適化
次のクエリの場合:
SELECT department, SUM(salary) FROM employee GROUP BY (department);通常、このクエリは department 列のデータをシャッフルおよびソートし、ストリーム集計を行って各 department グループをカウントします。ただし、テーブルデータがすでに department によってクラスター化およびソートされている場合、シャッフルおよびソート操作は不要になります。
ストレージの最適化
計算の最適化を考慮しなくても、テーブルデータをシャッフルおよびソートして格納するだけで、スペースを大幅に節約できます。MaxCompute は、基盤レイヤーでカラムストアを使用します。ソートにより、同じまたは類似のキー値を持つレコードが一緒に配置されます。これにより、圧縮とエンコーディングの効果が向上し、圧縮率が高くなります。テストでは、ソートされたテーブルは、極端なケースでは、ソートされていないテーブルと比較して最大 50% 少ないストレージスペースを使用できます。ライフサイクルが長いテーブルの場合、ストレージにハッシュクラスタリングを使用することは価値のある最適化です。
次の実験では、TPC-H データセットの 100 GB の lineitem テーブルを使用します。このテーブルには、int、double、string などのさまざまなデータ型が含まれています。同じデータと圧縮方法で、ハッシュクラスタリングを使用した場合と使用しない場合のテーブルのストレージサイズを比較しました。次の図に示すように、ハッシュクラスタリングを使用したテーブルでは、ストレージの使用量が約 10% 少なくなりました。
ハッシュクラスタリングなし
odps@xxx>desc tpch_lineitem; +------------------------------------------------------------------------------------+ | Owner: xxx | Project: xxx | | TableComment: | +------------------------------------------------------------------------------------+ | CreateTime: 2016-04-17 21:48:08 | | LastDDLTime: 2016-04-17 21:48:08 | | LastModifiedTime: 2016-04-17 21:50:10 | +------------------------------------------------------------------------------------+ | InternalTable: YES | Size: 23573055432 | +------------------------------------------------------------------------------------+ | Native Columns: | +------------------------------------------------------------------------------------+ | Field | Type | Label | Comment | +------------------------------------------------------------------------------------+ | l_orderkey | bigint | | | | l_partkey | bigint | | | | l_suppkey | bigint | | | | l_linenumber | bigint | | | | l_quantity | double | | | | l_extendedprice | double | | | | l_discount | double | | | | l_tax | double | | | | l_returnflag | string | | | | l_linestatus | string | | | | l_shipdate | string | | | | l_commitdate | string | | | | l_receiptdate | string | | | | l_shipinstruct | string | | | | l_shipmode | string | | | | l_comment | string | | | +------------------------------------------------------------------------------------+ハッシュクラスタリングあり
odps@ xxx >desc tpch_lineitem_hash_500; | Owner: xxx | Project: xxx | TableComment: | CreateTime: 2017-07-13 14:40:11 | LastDDLTime: 2017-07-13 14:40:11 | LastModifiedTime: 2017-07-13 15:05:04 | InternalTable: YES | Size: 21658913950 | Native Columns: | Field | Type | Label | Comment | l_orderkey | bigint | | | l_partkey | bigint | | | l_suppkey | bigint | | | l_linenumber | bigint | | | l_quantity | double | | | l_extendedprice | double | | | l_discount | double | | | l_tax | double | | | l_returnflag | string | | | l_linestatus | string | | | l_shipdate | string | | | l_commitdate | string | | | l_receiptdate | string | | | l_shipinstruct | string | | | l_shipmode | string | | | l_comment | string | |
テストデータと分析
ハッシュクラスタリングの全体的なパフォーマンス上のメリットは、標準の TPC-H テストセットを使用して測定されました。このテストでは、すべてのテーブルに 1 TB のデータと 500 個のバケットが使用されました。nation と region の 2 つの小さなテーブルを除き、他のすべてのテーブルでは最初の列がクラスタキーおよびソートキーとして使用されました。全体的なテスト結果では、ハッシュクラスタリングの使用後、合計 CPU 時間が約 17.3%、合計ジョブ実行時間が約 12.8% 削減されたことが示されています。
なお、TPC-H のすべてのクエリがクラスタリングプロパティを使用できるわけではありません。特に、最も実行時間が長い 2 つのクエリはこのプロパティを使用できません。したがって、全体的な効率の向上は劇的ではありません。しかし、クラスタリングプロパティを使用できるクエリの場合、そのメリットは大幅です。たとえば、Q4 は約 68% 高速になり、Q12 は約 62% 高速になり、Q10 は約 47% 高速になりました。
次の図は、標準テーブルでの TPC-H Q4 の Fuxi 実行計画を示しています。
次の図は、ハッシュクラスタリングを使用した後の実行計画を示しています。ご覧のとおり、有向非巡回グラフ (DAG) が大幅に簡素化されています。これが、パフォーマンスが大幅に向上する主な理由です。