統計の維持、シャードの設定、結合と集計の最適化、効率的なテーブルスキーマの設計を用いて、Hologres 内部テーブルのクエリをチューニングする方法を説明します。
クイック判断ガイド
|
症状 |
考えられる原因 |
推奨アクション |
|
大規模な内部テーブルでの結合が遅い |
古い統計情報 |
結合に関係するすべてのテーブルで ANALYZE を実行してください |
|
クエリが範囲または等価フィルターで過剰な行をスキャンします |
クラスタリングキー、ビットマップ列、またはセグメントキーの欠如 |
範囲フィルターには clustering_key を、等価フィルターには bitmap_columns を、時間ベースの範囲には segment_key を追加してください |
|
ポイントクエリのレイテンシーが高い |
不適切なストレージタイプ、またはプライマリキー/インデックスの欠如 |
行ストレージまたはハイブリッドストレージを使用し、適切なプライマリキーとインデックスを定義してください |
|
COUNT DISTINCT 集計が遅い |
リソースを大量に消費する正確な重複排除 |
APPROX_COUNT_DISTINCT または UNIQ を使用し、distinct キーを分散キーとして使用することを検討してください |
|
GROUP BY 集計が遅い |
GROUP BY キーでのデータの再分散とデータスキュー |
可能な場合は GROUP BY キーを分散キーとして設定し、データスキューを修正してください |
統計情報の維持
統計情報 (例:データ分布、行、列) は、オプティマイザが効率的な実行計画を選択するうえで役立ちます。統計情報が古いと、結合順序の選択が適切でなくなり、OOM エラーが発生する可能性があります。
統計情報が最新かどうかの確認
クエリに対して EXPLAIN を実行し、各テーブルの rows の推定値を確認します。
大規模なテーブルで rows=1000 (デフォルト) と表示される場合、統計情報が古くなっています。
統計情報の更新
統計情報が古いテーブルに対して ANALYZE を実行します:
analyze <tablename>;
統計情報を更新するタイミングの特定
次の場合に analyze <tablename> を実行します:
-
データのインポート後。
-
複数回の INSERT、UPDATE、または DELETE 操作の後。
-
内部テーブルと外部テーブルの両方。
-
パーティションテーブルの親テーブル。
結合中に OOM エラーが発生する場合やクエリが遅い場合は、analyze <tablename> を実行してください。
シャード数の設定
シャード数はクエリの並列度を決定します。シャード数が少なすぎると並列度が制限され、多すぎると起動オーバーヘッドが増加します。
デフォルトのシャード数
Hologres は、インスタンス仕様に基づいて、利用可能なクエリ CU とほぼ等しいデフォルトのシャード数を設定します。スケーリング後、既存のデータベースは元のシャード数を維持し、新規データベースにのみ更新後のデフォルト値が適用されます。
シャード数を調整するタイミング
-
5 倍以上にスケールアップした場合:より大きなシャード数で新しいテーブルグループを作成してください。
-
新しいビジネスワークロードの場合:適切なシャード数で新しいテーブルグループを作成してください。
-
並列処理に問題が発生した場合:合計シャード数が推奨デフォルト値を超えていないか確認してください。
最適な CPU 使用率を確保するために、すべてのテーブルグループの合計シャード数がインスタンスのデフォルトのシャード数を超えないようにする必要があります。
JOIN クエリの最適化
以下の方法を使用して、JOIN のパフォーマンスを向上させます。
JOIN クエリのための統計情報の更新
統計情報の維持で説明しているように、古い統計情報により、大きい方のテーブルでハッシュテーブルが作成され、JOIN 効率が低下する可能性があります。 ANALYZE を実行して、テーブルの統計情報を更新してください。
ローカル結合のための分散キーの選択
分散キーは、データがシャード間でどのように分散されるかを決定します。適切に選択することで、ローカル結合が可能になり、データシャッフルが削減されます。
分散キーを選択するための原則:
-
JOIN 列を分散キーとして使用します。
-
頻繁に使用される GROUP BY 句の列を使用します。
-
データ分布が均等で離散的な列を選択します。
例:特定の列でテーブルを頻繁に JOIN する場合、その列を両方のテーブルの分散キーとして設定します:
-- 一致する分散キーを持つテーブルを作成
BEGIN;
CREATE TABLE orders (order_id INT, customer_id INT, amount DECIMAL);
CALL set_table_property('orders', 'distribution_key', 'customer_id');
COMMIT;
BEGIN;
CREATE TABLE customers (id INT, name TEXT);
CALL set_table_property('customers', 'distribution_key', 'id');
COMMIT;
分散キーが正しく設定されていると、実行計画に Redistribute Motion 演算子が表示されなくなり、ローカル結合が行われていることを確認できます。
JOIN におけるランタイムフィルターの使用
V2.0 以降、Hologres は大きいテーブルと小さいテーブルの JOIN に対して、自動的にランタイムフィルターによる複数テーブル結合の高速化を適用し、手動設定なしでスキャン対象のデータを削減します。
結合順序アルゴリズムのチューニング
多数のテーブルを JOIN するクエリの場合、オプティマイザが最適な結合順序を見つけるのに時間がかかりすぎることがあります。必要に応じてアルゴリズムを調整してください:
set optimizer_join_order = '<value>';
|
アルゴリズム |
ユースケース |
トレードオフ |
|
exhaustive2 |
ほとんどのクエリのデフォルト |
最適なプラン、最も高い最適化コスト |
|
greedy |
10 を超えるテーブル |
より速い最適化、準最適なプランになる可能性 |
|
query |
シンプルで順序の良い SQL |
SQL の順序で実行、最も低い最適化コスト |
JOIN における Motion 演算子の最適化
Hologres は Motion 演算子を使用して、シャード間でデータを再分散します:
|
Motion タイプ |
説明 |
|
Redistribute Motion |
ハッシュまたはランダムでデータをシャッフルします。 |
|
Broadcast Motion |
すべてのシャードにデータをコピーします。 |
|
Gather Motion |
単一のシャードにデータを収集します。 |
|
Forward Motion |
フェデレーテッドクエリで、外部ソースと Hologres 間でデータを転送します。 |
実行計画でコストの高い Motion 演算子を確認し、テーブル設計を調整してください:
-
時間のかかる Motion 演算子:分散を再設計してください。
-
古い統計情報によって Motion の動作が非効率になっている場合:
analyzeで統計情報を更新してください。 -
小さいテーブルのブロードキャスト:シャード数を減らして Broadcast Motion の効率を最適化してください。
集計の最適化
COUNT DISTINCT の最適化
-
わずかなばらつきが許容される場合は、正確な COUNT DISTINCT (リソース集約型) を APPROX_COUNT_DISTINCT (高速、エラー率 0.1%~1%) に置き換えます。
-
COUNT DISTINCT を UNIQ (V1.3+) に置き換えます。
-
適切な分散キーの設定
シャード間のデータシャッフルを回避するため、COUNT DISTINCT キーを分散キーとして使用します。
-
組み込み最適化のための V2.1+ へのアップグレード
V2.1+ には、1 つ以上の COUNT DISTINCT、データスキュー、GROUP BY なしのクエリなど、COUNT DISTINCT シナリオ向けの組み込みの最適化が含まれています。
マルチステージ集約の強制
マルチステージ集約では、まず各シャード内で部分的な集約を実行し、データ転送を削減します:
set optimizer_force_multistage_agg = on;同一列に対する複数の集計関数の最適化
V4.0 以降、Hologres は同一列に対する複数の関連する集計関数を最適化し、計算を 1 回にまとめることで計算量を削減します。この最適化を使用するには、V4.0+ にアップグレードしてください。
例:
-- テストテーブルを作成します。
CREATE TABLE tbl(x int4, y int4);
-- テストデータを挿入します。
INSERT INTO tbl VALUES (1,2), (null,200), (1000,null), (10000,20000);
-- データをクエリします。
SELECT
sum(x + 1),
sum(x + 2),
sum(x - 3),
sum(x - 4)
FROM
tbl;
クエリプランから、x に対する集計が最適化されていることを確認できます。
無効化するには:
-- セッションレベルで無効化します。
SET hg_experimental_remove_related_group_by_key = off;
-- データベースレベルで無効化します。
ALTER DATABASE <database_name> SET hg_experimental_remove_related_group_by_key = off;
テーブルスキーマとインデックスの最適化
格納形式の選択
Hologres は、行ストア、列ストア、ハイブリッド行列ストアをサポートしています。ワークロードに基づいて選択します。
|
格納形式 |
最適な用途 |
トレードオフ |
|
行ストア |
プライマリキーによるポイントクエリ、頻繁な UPDATE/DELETE |
範囲スキャンと集計のパフォーマンスが低い |
|
列ストア |
分析、複数列のクエリ、集計 |
UPDATE/DELETE とポイントクエリが遅い |
|
ハイブリッド行列ストア |
混合ワークロード |
ストレージオーバーヘッドが高い |
データ型の選択
-
可能な限り小さい型を使用します (例:
BIGINTの代わりにINTを使用) -
DECIMAL/NUMERIC型には精度を指定します。 -
FLOATまたはDOUBLEをGROUP BY列には使用しないでください。 -
汎用性のために
TEXTを使用します。VARCHAR(N)またはCHAR(N)を使用する場合は、N は最小限に抑えます。 -
日付には
TIMESTAMPTZとDATEを使用し、TEXTの使用は避けてください。 -
暗黙的な変換を避けるため、結合条件ではデータ型を統一します。
プライマリキーの設計
プライマリキーはデータの一意性を保証します。インポート時に重複排除方法を選択します。
-
ignore: 新しいデータを無視します。
-
update: 古いデータを上書きします。
適切なプライマリキーは、特に GROUP BY クエリの実行計画を改善します。
列ストアモードでは、プライマリキーによって書き込みが遅くなります。通常、プライマリキーがない場合のスループットは 3 倍高くなります。
パーティションテーブルの使用
Hologres は単一レベルのパーティショニングをサポートしています。適切なパーティショニングはクエリを高速化しますが、パーティションが多すぎると小さなファイルが作成され、パフォーマンスが低下します。
増分データに対して日次パーティションを作成し、ストレージとアクセスを分離します。
適用シナリオ:
-
パーティション全体を DROP または TRUNCATE することで、DELETE よりもパフォーマンスが向上し、他のパーティションへの影響もありません。
-
スキャン対象を特定のパーティションまたは子テーブルに限定します。
-
定期的なリアルタイムインポートにはパーティションテーブルを使用します。たとえば、日付をパーティションキーとして使用します。ステートメントの例:
begin;
create table insert_partition(c1 bigint not null, c2 boolean, c3 float not null, c4 text, c5 timestamptz not null) partition by list(c4);
call set_table_property('insert_partition', 'orientation', 'column');
commit;
create table insert_partition_child1 partition of insert_partition for values in('20190707');
create table insert_partition_child2 partition of insert_partition for values in('20190708');
create table insert_partition_child3 partition of insert_partition for values in('20190709');
select * from insert_partition where c4 >= '20190708';
select * from insert_partition_child3;
適切なインデックスの選択
Hologres は複数のインデックスタイプを提供しています。テーブル作成時にインデックスを定義します。
|
タイプ |
目的 |
クエリ例 |
|
クラスタリングキー |
範囲クエリとフィルタリング |
|
|
ビットマップ列 |
等価クエリ |
|
|
セグメントキー (event_time_column とも呼ばれます) |
時間ベースのフィルタリング (ファイルレベル) ビットマップまたはクラスタリングキーの前に、高速なファイルレベルのフィルタリングを実行します。最左プレフィックス一致の原則に従います (通常は 1 列)。最初の空でないタイムスタンプをセグメントキーとして使用します。 |
|
注:
-
クラスタリングキーとセグメントキーは、最左プレフィックス一致の原則に従います。
-
ビットマップインデックスは、複数列での AND/OR クエリをサポートします。
-
時間ベースのフィルタリングには最初に
segment_keyを使用し、次に等価クエリにはbitmap_columnsを、範囲クエリにはclustering_keyを使用します。
例:
BEGIN;
CREATE TABLE events (
event_id INT NOT NULL,
user_id INT NOT NULL,
event_time TIMESTAMPTZ NOT NULL,
event_type TEXT
);
CALL set_table_property('events', 'clustering_key', 'event_time');
CALL set_table_property('events', 'segment_key', 'event_time');
CALL set_table_property('events', 'bitmap_columns', 'user_id,event_type');
COMMIT;
bitmap_columns はテーブル作成後に追加できます。clustering_key と segment_key は作成時に指定する必要があります。
クエリでインデックスが使用されているか確認するには、EXPLAIN を実行します。
EXPLAIN SELECT * FROM events WHERE event_time > '2026-01-01';
文字列列における辞書エンコードの無効化
辞書エンコードは文字列比較を高速化しますが、エンコード/デコードのオーバーヘッドが発生します。比較コストが低い列では無効にしてください。
BEGIN;
CREATE TABLE logs (id INT, message TEXT);
CALL set_table_property('logs', 'dictionary_encoding_columns', '');
COMMIT;SQL 文の最適化
NOT IN などの外部 SQL (Postgres) の回避
Hologres は、最高のパフォーマンスを得るために Hologres クエリエンジン (HQE) を使用します。サポートされていない演算子は Postgres クエリエンジン (PQE) にフォールバックし、速度が低下します。
実行計画で PQE へのフォールバックを確認するには、次のコマンドを実行します:
EXPLAIN SELECT * FROM orders WHERE id NOT IN (SELECT id FROM cancelled_orders);
External SQL (Postgres) が表示される場合は、クエリを書き換えてください。
|
HQE 非対応 |
書き換え先 |
例 |
備考 |
|
|
|
|
該当なし。 |
|
|
|
|
Hologres V2.0.4 以降、 HQE は |
|
|
|
書き換え後の例:
|
V0.10 以前の一部のバージョンでは substring がサポートされていません。 V1.3 以降、 HQE は正規表現以外の入力に対して substring をサポートしています。 |
|
|
|
書き換え後の例:
|
|
|
|
|
書き換え後の例:
|
該当なし。 |
|
|
|
書き換え後の例:
|
Hologres V2.0 以降、 HQE でサポートされています。 |
|
|
|
書き換え後の例:
|
Hologres V2.0 以降、 HQE でサポートされています。 |
あいまいな LIKE クエリの回避
LIKE 操作のようなあいまい検索は、インデックスを使用しないため回避してください。
ORDER BY LIMIT クエリの最適化
V1.3 以降、 Hologres は ORDER BY ... LIMIT クエリに対してマージソートをサポートし、冗長なソート操作を排除します。
GROUP BY クエリの最適化
GROUP BY 列を分散キーとして設定すると、データ再分散が削減されます。
-- データが列 a の値に基づいて分散されている場合、実行時のデータ再分散が削減され、シャードの並列計算能力が最大限に活用されます。
select a, count(1) from t1 group by a;
V4.0 以降、 Hologres は関連する GROUP BY 列を自動的に書き換えることでマージを削減します (最大検索深度: 5 層)。 GROUP BY COL_A, ((COL_A + 1)), ((COL_A + 2)) のような句は GROUP BY COL_A に書き換えられます。例:
CREATE TABLE tbl (
a int,
b int,
c int
);
-- クエリ
SELECT
a,
a + 1 as a1,
a + 2 as a2,
sum(b)
FROM tbl
GROUP BY
a,
a1,
a2;
実行計画で書き換えを確認できます。GROUP BY 句には列 a のみが含まれています。
QUERY PLAN
Gather (cost=0.00..5.00 rows=1 width=20)
-> Project (cost=0.00..5.00 rows=1 width=20)
-> HashAggregate (cost=0.00..5.00 rows=1 width=12)
Group Key: a
-> Redistribution (cost=0.00..5.00 rows=1 width=8)
Hash Key: a
-> Local Gather (cost=0.00..5.00 rows=1 width=8)
-> Seq Scan on tbl (cost=0.00..5.00 rows=1 width=8)
Query Queue: init_warehouse.default_queue
Optimizer: HQO version 4.0.0
無効化するには、次のコマンドを実行します:
-- セッションレベルで機能を無効化します。
SET hg_experimental_remove_related_group_by_key = off;
-- データベースレベルで機能を無効化します。
ALTER DATABASE <database_name> SET hg_experimental_remove_related_group_by_key = off;
CTE 再利用の有効化
CTE が複数回参照される場合、 CTE の再利用を有効化することで再計算を回避します (V1.3 以降):
SET optimizer_cte_inlining=off;
-
CTE の再利用はデフォルトで無効です。 GUC を使用して手動で有効化してください。
-
CTE の再利用は、シャッフルステージでのスピルに依存しています。データ量が大きい場合、消費速度の違いによりパフォーマンスに影響を与える可能性があります。
Top-N 分析の最適化
-
OLAP シナリオでは、グループ内の上位 N 件のレコードを取得することが一般的な要件です。たとえば、次の SQL クエリは、
tテーブルの各bパーティションについて、aでソートした上位 2 件のレコードを取得します。CREATE TABLE t ( a int, b int ); INSERT INTO t VALUES (2, 1), (3, 1), (4, 1), (5, 2), (6, 2); SELECT * FROM ( SELECT a, b, row_number() OVER (PARTITION BY b ORDER BY a) AS rn FROM t) t1 WHERE rn <= 2;実行結果は次のとおりです。
a b rn 5 2 1 6 2 2 2 1 1 3 1 2 -
Hologres V4.1 から、
Partition Sort演算子はLIMIT句をPartitionにプッシュし、ソート中にデータを早期にフィルタリングします。これにより、Top-N シナリオにおけるrow_numberやrankなどのウィンドウ関数のメモリが削減され、OOM のリスクが低下します。デフォルトで有効になっています。無効にするには:-- セッションレベルで機能を無効化します。 SET hg_experimental_enable_hash_partitioned_sort_v2 = off; -- データベースレベルで機能を無効化します。 ALTER DATABASE <database_name> SET hg_experimental_enable_hash_partitioned_sort_v2 = off;
データスキューへの対処
データ分布が不均一だと、クエリが遅くなります。シャードごとの行数を確認して、スキューを検出します:
-- hg_shard_id は、各テーブルに組み込まれている非表示列で、対応するデータ行が配置されているシャードを示します。
SELECT hg_shard_id, count(1) FROM t1 GROUP BY hg_shard_id;
一部のシャードの行数が他よりも大幅に多い場合:
-
データ分布が均一な列に
distribution_keyを変更してください。重要分散キーを変更するには、テーブルを再作成してデータを再インポートする必要があります。
-
データが本質的に偏っている場合は、ビジネスの観点から最適化してください。
テスト時の結果キャッシュ無効化
Hologres は、デフォルトでクエリ結果をキャッシュします。パフォーマンスのベンチマークを行う際は、キャッシュを無効にしてください。
set hg_experimental_enable_result_cache = off;