ソートキーを設定すると、AnalyticDB for PostgreSQL はテーブルスキャン時にディスクブロックの大半をスキップできるため、範囲制限付きクエリのクエリ応答時間が大幅に短縮されます。この機能は、列指向・追記専用(append-only)テーブルに適用され、クエリが予測可能な一連の列に対して一貫してフィルター処理を行う場合に最も効果的です。
この機能は以下の環境で利用可能です:
カーネルバージョンが 20200826 より新しい、予約モード(Reserved mode)のインスタンス
カーネルバージョンが 20200906 より新しい、弾力的モード(Elastic mode)のインスタンス
仕組み
AnalyticDB for PostgreSQL は、列指向データをディスクブロックに格納します。各ブロックについて、データベースはすべての列の最小値および最大値を記録します。この構造を「ラフセットインデックス」と呼びます。WHERE 句に範囲条件(range predicate)を含むクエリを実行すると、クエリプロセッサはその条件を各ブロックの最小値/最大値と比較し、範囲外にあるブロックをスキップします。
データとソートキーの相関性が高ければ高いほど、スキップ可能なブロック数も増えます。たとえば、日付順に並べられた 7 年分のデータを保持するテーブルがあり、クエリで 1 ヶ月分のデータをフィルターする場合、対象となるのは全データの 1/(7 × 12) だけ — つまり、約 98.8 % のディスクブロックをスキップできます。ソートを行わないと、すべてのブロックをスキャンする必要がある可能性があります。
AnalyticDB for PostgreSQL では、以下の 2 種類のソート方法がサポートされています:
| 方法 | 動作 | 推奨ユースケース |
|---|---|---|
| 複合ソート | すべてのソートキー列を順序付きタプルとしてソートし、先頭列(leading column)を最優先します | ソートキーの先頭列でフィルター処理を行うクエリ |
| インタリーブドソート | ソートキー内の各列に均等な重みを割り当てます | ソートキーの任意の部分集合(先頭列以外を含む)でフィルター処理を行うクエリ |
詳細なパフォーマンス比較については、「パフォーマンス比較:複合ソート 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 より古い場合は、ソートキーを定義する際にSORTKEY (column [, ...])を使用してください(ORDER BY (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 つのテーブル(
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: 先頭列(id)でのフィルター
SELECT * FROM test WHERE id = 100000;
SELECT * FROM test_multi WHERE id = 100000;
-- Q2: 第 2 列(num1)でのフィルター
SELECT * FROM test WHERE num1 = 8766963;
SELECT * FROM test_multi WHERE num1 = 8766963;
-- Q3: 第 2 列および第 3 列(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: 先頭列(id)での範囲フィルター
SELECT count(*) FROM test WHERE id > 5000 AND id < 100000;
SELECT count(*) FROM test_multi WHERE id > 5000 AND id < 100000;
-- Q2: 第 2 列(num1)での範囲フィルター
SELECT count(*) FROM test WHERE num1 > 5000 AND num1 < 100000;
SELECT count(*) FROM test_multi WHERE num1 > 5000 AND num1 < 100000;
-- Q3: 第 2 列および第 3 列(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 と Q2 の比較)。
本テストでは 1,000 万行を使用していますが、これは AnalyticDB for PostgreSQL にとって比較的小規模なサイズです。実際には、より大規模なテーブルで両手法のパフォーマンス差はさらに顕著になります。