ソートキーを使用すると、AnalyticDB for PostgreSQL はテーブルスキャン中にディスクブロックの大部分をスキップできるため、範囲が限定されたクエリの応答時間を大幅に短縮できます。この機能は、列指向の追加入力専用テーブルに適用され、クエリが一貫して予測可能な列セットでフィルターされる場合に最も効果的です。
この機能は、以下に適用されます。
-
カーネルバージョンが 20200826 以降の予約モードインスタンス
-
カーネルバージョンが 20200906 以降の弾性モードインスタンス
仕組み
AnalyticDB for PostgreSQL は、列指向データをディスクブロックに格納します。各ブロックについて、データベースはすべての列の最小値と最大値を記録します。この構造はラフセットインデックスと呼ばれます。クエリの `WHERE` 句に範囲述語が含まれている場合、クエリプロセッサは述語を各ブロックの最小値/最大値と比較し、範囲外のブロックをスキップします。
データとソートキーの相関が強いほど、より多くのブロックを排除できます。たとえば、テーブルに日付でソートされた 7 年分のデータが格納されており、クエリが 1 か月分でフィルターする場合、スキャンする必要があるデータは 1/(7 × 12) のみとなり、ディスクブロックの約 98.8% が排除されます。ソートしない場合、すべてのブロックがスキャンされる可能性があります。
AnalyticDB for PostgreSQL は、2 つのソートメソッドをサポートしています。
| メソッド | 動作 | 最適なケース |
|---|---|---|
| 複合ソート | すべてのソートキー列の順序付きタプルとしてデータをソートし、先頭列を優先します | ソートキーの最初の (先頭) 列でフィルターするクエリ |
| インターリーブソート | ソートキーの各列に等しい重みを割り当てます | 先頭列以外の列を含む、ソートキー列の任意サブセットでフィルターするクエリ |
パフォーマンスの詳細な比較については、「パフォーマンス比較:複合ソート vs. インターリーブソート」をご参照ください。
ソートキーを使用する状況
ソートキーは、以下のすべての基準を満たすテーブルに有効です。
-
選択的なクエリ:クエリは `WHERE` 句の範囲述語または等価述語を使用して、行の小さなサブセットでフィルターします。
-
一貫したフィルター列:クエリの高い割合が、同じ列または複数の列でフィルターします。
-
大きなテーブルサイズ:パフォーマンスの向上はテーブルサイズとともに大きくなります。ソートキーは、数億行以上のテーブルで最も効果的です。
ソートキーはメンテナンスのオーバーヘッドを追加します。データをロードした後、テーブルを明示的にソートする必要があり、新しいデータが蓄積されるにつれて定期的に再ソートする必要があります。ワークロードの書き込みレートが高く、アドホッククエリが頻繁でない場合、メンテナンスコストがクエリの高速化によるメリットを上回る可能性があります。
ソートメソッドの選択
クエリパターンに適したメソッドを選択するには、以下のガイドラインを使用してください。
-
ほとんどのクエリがソートキーの先頭列でフィルターする場合は、複合ソートを使用します。先頭列の述語に対して最速の応答時間を生成します。複合ソートによる再ソートは、追加のデータ分析を実行するため、インターリーブソートよりも時間がかかることに注意してください。
-
クエリが先頭列以外の列、またはソートキーの任意のサブセットでフィルターする場合は、インターリーブソートを使用します。インターリーブソートキーは最大 8 列までサポートします。クエリが参照するソートキー列が多いほど、パフォーマンス上のメリットは大きくなります。
-
どちらを使用すればよいかわからない場合は、複合ソートから始めてください。これはよりシンプルな選択肢であり、クエリに明確な先頭フィルター列がある場合に最高のパフォーマンスを発揮します。
テーブル作成時のソートキーの定義
CREATE TABLE の ORDER BY 句を使用して、1 つ以上の列をソートキーとして定義します。テーブルでは、列指向の追加専用ストレージ (APPENDONLY=true, ORIENTATION=column) を使用する必要があります。
create table test(date text, time text, open float, high float, low float, volume int)
with(APPENDONLY=true,ORIENTATION=column) ORDER BY (volume);
完全な構文:
CREATE [[GLOBAL | LOCAL] {TEMPORARY | TEMP}] TABLE table_name (
column_name data_type [, ...]
)
[ DISTRIBUTED BY (column [, ...]) | DISTRIBUTED RANDOMLY ]
[ ORDER BY (column [, ...]) ]
カーネルバージョンが 20210326 より前の場合、ソートキーを定義するにはORDER BY (column [, ...])の代わりにSORTKEY (column [, ...])を使用してください。
テーブルのソート
ソートキーを定義しても、データが自動的にソートされるわけではありません。テーブルにデータを書き込んだ後、ソートコマンドを実行してソート順を適用し、ラフセットインデックスを構築します。
複合ソート:
SORT table_name;
インターリーブソート:
MULTISORT table_name;
カーネルバージョンが 20210326 より前の場合、複合ソートにはVACUUM SORT ONLY table_nameを、インターリーブソートにはVACUUM REINDEX table_nameを使用してください。
ソートされたテーブルに新しい行が追加されると、ソートされていないデータが蓄積され、ラフセットフィルタリングの効果が低下します。クエリのパフォーマンスを維持するために、定期的に SORT または MULTISORT を実行してください。
ソートキーの変更
既存の列指向テーブルのソートキーを変更するには、次のようにします。
ALTER TABLE table_name SET ORDER BY (column [, ...]);
この文はカタログのみを更新し、データはソートしません。新しいソート順を適用するには、後で SORT table_name を実行してください。
例:
ALTER TABLE test SET ORDER BY (high, low);
SORT test;
カーネルバージョンが 20210326 より前の場合、ALTER TABLE test SET SORTKEY (high, low) を使用してください。
制限事項
| 項目 | 制限 |
|---|---|
| 最大ソートキー列数 (インターリーブソート) | 8 |
| テーブルストレージタイプ | 列指向、追加入力専用 (APPENDONLY=true, ORIENTATION=column) のみ |
ORDER BY / SORT / MULTISORT 構文のカーネルバージョン |
20210326 以降 |
レガシーな SORTKEY / VACUUM SORT ONLY / VACUUM REINDEX 構文のカーネルバージョン |
20210326 以前 |
パフォーマンス比較:複合ソート vs. インターリーブソート
TPC-H ベンチマーク:範囲クエリに対するソートキーの影響
このセクションでは、7 年分のデータを格納する TPC-H の Lineitem テーブルを使用して、複合ソートがフルテーブルスキャンと比較してラフセットインデックスのクエリパフォーマンスをどのように向上させるかを示します。
この TPC の実装は TPC ベンチマークから派生したものであり、TPC ベンチマークのすべての要件に準拠しているわけではないため、公開されている TPC ベンチマークの結果とは比較できません。
テスト設定:
-
32 ノードのインスタンスを作成します。
-
Lineitem テーブルに 130 億行を書き込みます。
-
1997-09-01 から 1997-09-30 までの期間のデータをクエリし、データが
l_shipdateでソートされている場合とソートされていない場合の結果を比較します。
複合ソート vs. インターリーブソート:クエリ形状によるパフォーマンスの違い
次の例では、同一のデータとソートキーを持つ 2 つのテーブルを使用して、2 つのメソッドが異なるクエリ形状でどのようにパフォーマンスを発揮するかを示します。
テスト設定:
-
2 つのテーブル (
testとtest_multi)、それぞれに 4 つの列:id、num1、num2、value -
ソートキー:両方のテーブルで
(id, num1, num2) -
テーブルごとに 1,000 万行
-
複合ソートでソートされた
test(SORT test) -
インターリーブソートでソートされた
test_multi(MULTISORT test_multi)
テーブルの作成とデータの挿入:
CREATE TABLE test (id int, num1 int, num2 int, value varchar)
WITH (APPENDONLY=TRUE, ORIENTATION=column)
DISTRIBUTED BY (id)
ORDER BY (id, num1, num2);
CREATE TABLE test_multi (id int, num1 int, num2 int, value varchar)
WITH (APPENDONLY=TRUE, ORIENTATION=column)
DISTRIBUTED BY (id)
ORDER BY (id, num1, num2);
INSERT INTO test (id, num1, num2, value)
SELECT g,
(random() * 10000000)::int,
(random() * 10000000)::int,
(ARRAY['foo', 'bar', 'baz', 'quux', 'boy', 'girl', 'mouse', 'child', 'phone'])[floor(random() * 10 + 1)]
FROM generate_series(1, 10000000) AS g;
INSERT INTO test_multi SELECT * FROM test;
SORT test;
MULTISORT test_multi;
ポイントクエリのパフォーマンス
3 つのクエリはすべてソートキー列でフィルターしますが、位置が異なります。
-- Q1: filter on the leading column (id)
SELECT * FROM test WHERE id = 100000;
SELECT * FROM test_multi WHERE id = 100000;
-- Q2: filter on the second column (num1)
SELECT * FROM test WHERE num1 = 8766963;
SELECT * FROM test_multi WHERE num1 = 8766963;
-- Q3: filter on the second and third columns (num1, num2)
SELECT * FROM test WHERE num1 = 100000 AND num2 = 2904114;
SELECT * FROM test_multi WHERE num1 = 100000 AND num2 = 2904114;
| クエリ | フィルター列 | 複合ソート | インターリーブソート |
|---|---|---|---|
| Q1 | 先頭列 (id) | 0.026s | 0.55s |
| Q2 | 2 番目の列 (num1) | 3.95s | 0.42s |
| Q3 | 2 番目と 3 番目の列 (num1, num2) | 4.21s | 0.071s |
範囲クエリのパフォーマンス
-- Q1: range filter on the leading column (id)
SELECT count(*) FROM test WHERE id > 5000 AND id < 100000;
SELECT count(*) FROM test_multi WHERE id > 5000 AND id < 100000;
-- Q2: range filter on the second column (num1)
SELECT count(*) FROM test WHERE num1 > 5000 AND num1 < 100000;
SELECT count(*) FROM test_multi WHERE num1 > 5000 AND num1 < 100000;
-- Q3: range filter on the second and third columns (num1, num2)
SELECT count(*) FROM test WHERE num1 > 5000 AND num1 < 100000 AND num2 < 100000;
SELECT count(*) FROM test_multi WHERE num1 > 5000 AND num1 < 100000 AND num2 < 100000;
| クエリ | フィルター列 | 複合ソート | インターリーブソート |
|---|---|---|---|
| Q1 | 先頭列 (id) | 0.07s | 0.44s |
| Q2 | 2 番目の列 (num1) | 3.35s | 0.28s |
| Q3 | 2 番目と 3 番目の列 (num1, num2) | 3.64s | 0.047s |
主なポイント
-
複合ソートは先頭列で優位です。Q1 の結果は、フィルターが最初のソートキー列を対象とする場合、複合ソートのクエリ応答時間がインターリーブソートよりも短いことを示しています。
-
インターリーブソートは先頭列以外で優位です。Q2 と Q3 の結果は、クエリが先頭列をバイパスする場合、インターリーブソートが複合ソートを大幅に上回るパフォーマンスを発揮することを示しています。
-
インターリーブソートは列数に応じてスケールします。クエリが参照するソートキー列が多いほど、インターリーブソートのパフォーマンス上の利点は大きくなります (Q3 vs. Q2)。
このテストでは 1,000 万行を使用していますが、これは AnalyticDB for PostgreSQL にとっては中程度のサイズです。2 つのメソッド間のパフォーマンスの違いは、より大きなテーブルでより顕著になります。