このトピックでは、Hologres の動的テーブルのクエリリライト機能について、その使用方法と制限事項を説明します。
クエリリライト
ビッグデータやデータウェアハウジングのシナリオでは、詳細テーブルはしばしば数億行から数百億行に及ぶ大規模なものになります。ビジネスや分析のクエリは、これらのテーブルに対する多次元の GROUP BY 集計に大きく依存しています。たとえば、都市ごとの日次または時間単位の注文量や GMV の計算、あるいはチャネルやデバイスごとの PV/UV やコンバージョン率の追跡などです。これらの集計を詳細テーブルに対して直接実行すると、いくつかの問題が発生します。
-
高い集計コスト:各クエリが詳細テーブルの大部分または全体をスキャンして集計するため、大量の CPU および I/O リソースを消費します。
-
詳細テーブルへの高負荷:これにより、同じデータベース内の他のタスクに影響を与え、頻繁なスケーリングが必要になる場合があります。
Hologres の動的テーブルは、クエリリライト機能を提供します。ベーステーブルが動的テーブルによって事前集計されている場合、特定の条件を満たせば、オプティマイザはベーステーブルに対する集計クエリを自動的に書き換え、代わりに動的テーブルをクエリします。このアプローチは、コストのかかる集計計算をスキップし、いくつかの重要な利点があります。
-
詳細テーブルの集計負荷を軽減:注文数、GMV、PV/UV などの頻繁に使用されるメトリクスを、動的テーブル内の事前集計された結果から直接読み取ることができるため、詳細テーブルでの繰り返しのスキャンと集計を最小限に抑えます。
-
クエリの応答時間を大幅に改善:動的テーブルを利用するレポート、セルフサービス分析、インタラクティブクエリでは、集計が大幅に削減され、レイテンシーが低下します。その体験は、ワイドテーブルをクエリするのと似ています。
-
アップストリームユーザーに対して透過的:データアナリストやアプリケーション開発者は、何の変更もなしにベーステーブルをクエリし続けることができます。データウェアハウスやプラットフォームチームは動的テーブルを設計・維持でき、パフォーマンスの最適化をユーザーから透過的にできます。
Hologres の動的テーブルのクエリリライトは、以下のユースケースに適しています。
-
リアルタイムおよびニアリアルタイムの運用ダッシュボードとモニタリング。
-
多次元 BI 分析とセルフサービスデータアクセス。
-
GMV、注文量、アクティブユーザー数など、統一されたコアメトリクスシステムの高速化。
使用方法と制限事項
-
バージョン要件:この機能には Hologres V4.1 以降が必要です。
-
クエリの整合性:クエリリライトは、動的テーブルの最新のリフレッシュのデータを使用します。これは、データがベーステーブルの最新の状態から遅れていることを意味し、結果として弱い整合性となります。
-
ベーステーブルの制限事項:
-
サポートされているベーステーブルの種類:Hologres 内部テーブル、Paimon 外部テーブル (外部テーブルとして作成) 、および MaxCompute 外部テーブル (外部テーブルとして作成) 。
-
ベーステーブルが Hologres のパーティションテーブルである場合、物理パーティションはサポートされていません。ただし、ベーステーブルは論理パーティションを使用できます。
外部テーブルはサポートされていません。
-
-
動的テーブルの種類の制限事項:
-
サポート対象:非パーティション化された動的テーブルおよび論理的にパーティション化された動的テーブル。
-
サポート対象外:物理的にパーティション化された動的テーブルおよび外部の動的テーブル。
-
-
動的テーブルのクエリ定義に関する制限事項:
-
現在、単一テーブルのクエリのみがサポートされています。
-
FILTER句を持つ集計関数、たとえばsum(x) FILTER (WHERE ...)はサポートされていません。 -
クエリは SELECT リストで集計結果から新しい列を計算することはできません。たとえば
sum(x)/count(x)などです。
-
クエリリライトの有効化と設定
推奨 : この機能は、数秒から数分のレイテンシーを許容できるダッシュボード、モニタリング、分析シナリオに適しています。強力なリアルタイム保証や厳密なデータ照合が要求されるシナリオでは、ベーステーブルを直接クエリするか、他の強い整合性ソリューションを使用することを推奨します。
クエリリライトの有効化
ベーステーブルをクエリする際に、hg_enable_query_rewrite GUC パラメーターを設定して、クエリがクエリリライトを使用できるかどうかを制御します。
この機能をデータベースレベルで有効にすると、パフォーマンスが低下する可能性があるため、推奨しません。
-- クエリリライトを有効にする (セッションレベル)
SET hg_enable_query_rewrite = on;
-- データベースレベルで設定する (非推奨)
ALTER DATABASE <db_name> SET hg_enable_query_rewrite = on;
動的テーブルのクエリリライトの有効化
動的テーブルを作成する際に、allowed_to_rewrite_query プロパティを使用して、そのテーブルをクエリリライトに使用できるかどうかを制御します。デフォルトでは、このプロパティは 'false' に設定されており、テーブルはクエリリライトに使用されません。
CREATE [ OR REPLACE ] DYNAMIC TABLE [ IF NOT EXISTS ] [<schema_name>.]<table_name> (
[col_name],
[col_name],
[col_name]
)
[LOGICAL PARTITION BY LIST(<partition_key>)]
WITH (
...,
allowed_to_rewrite_query = '[true | false]',
...
)
AS
<query>;
パラメーターの説明:
-
allowed_to_rewrite_query:この動的テーブルがクエリリライトの候補となるかを指定します。-
'true':テーブルをクエリリライトに使用することを許可します。 -
'false':デフォルト値です。テーブルはクエリリライトに使用されません。
-
推奨事項:
-
集計クエリを高速化するために特別に作成された動的テーブルについては、このプロパティを
'true'に設定します。 -
現在のリライトルールでは使用できない複雑な定義を持つ動的テーブルについては、このプロパティを
'false'に設定して、不要なオプティマイザのオーバーヘッドを削減します。
クエリリライトプロパティの変更
ALTER DYNAMIC TABLE ... SET を使用して、動的テーブルがクエリリライトに使用できるかどうかを変更できます。
ALTER DYNAMIC TABLE [IF EXISTS] [<schema_name>.]<table_name>
SET (allowed_to_rewrite_query = '[true | false]');
候補となる動的テーブルの制御
複数の動的テーブルが利用可能な場合、ヒントで候補テーブルのセットを指定して、オプティマイザの検索範囲を絞り込み、優先順位を制御できます。ヒントの構文の詳細については、「ヒント」 をご参照ください。
SELECT /*+HINT query_rewrite_candidates(<schema.dt_name1> <schema.dt_name2> ...) */
...
FROM ...;
使用上の注意:
-
複数の動的テーブルがある場合は、スペースで区切ります。
-
スキーマ名を含めることができます。
例:
-- dt_sales のみをクエリリライトに使用することを許可する
SELECT /*+HINT query_rewrite_candidates(dt_sales) */
day, hour, min(amount), max(amount)
FROM base_sales_table
GROUP BY day, hour;
サポートされている機能
現在のバージョンでは、単一テーブル集計 のクエリリライトを、主に次の3つのパターンでサポートしています。
-
集計ディメンションが一致する場合の透過的なリライト。
-
ロールアップ集計 (動的テーブルのディメンションのサブセットでの集計) 。
-
フィルター条件付きのロールアップ集計。
集計ディメンションの一致
条件:
-
クエリ内の
GROUP BYディメンションが、動的テーブル定義のGROUP BYディメンションと完全に一致する。 -
クエリ内の集計関数が、動的テーブルの集計列から導出できる。
-
DISTINCTを含むあらゆる種類の集計関数がサポートされます。ただし、動的テーブルに対応する結果列が存在する場合に限ります。
例:
-- ベーステーブルの作成
CREATE TABLE base_sales_table(
day text not null,
hour int,
amount int
);
-- データの挿入
INSERT INTO base_sales_table
VALUES ('20250529', 12, 1),
('20250529', 12, 2),
('20250529', 12, 2),
('20250529', 13, 3),
('20250530', 13, 4),
('20250530', 14, 5),
('20250531', 14, 6);
-- 動的テーブルの作成
CREATE DYNAMIC TABLE dt_sales
WITH (
freshness = '1 minutes',
auto_refresh_mode='incremental',
auto_refresh_enable='false',
allowed_to_rewrite_query='true'
)
AS
SELECT
day,
hour,
min(amount),
max(amount),
sum(amount),
count(amount),
count(*) as rows,
count(1) as rows1,
count(distinct amount) as cd
FROM base_sales_table
GROUP BY day, hour;
-- 動的テーブルの手動リフレッシュ
REFRESH TABLE dt_sales;
クエリの例: 集計ディメンションが一致する場合、実行計画は、ベーステーブルに対するクエリが動的テーブルをクエリするように書き換えられたことを示します。
-- クエリのディメンションが動的テーブルと一致
EXPLAIN SELECT day, hour, min(amount), max(amount) FROM base_sales_table GROUP BY day, hour;
返された実行計画は、クエリが動的テーブル dt_sales のシーケンシャルスキャンを実行するように書き換えられたことを示しています。
QUERY PLAN
Gather (cost=0.00..5.00 rows=7 width=20)
-> Local Gather (cost=0.00..5.00 rows=7 width=20)
-> Project (cost=0.00..5.00 rows=7 width=20)
-> Seq Scan on dt_sales (cost=0.00..5.00 rows=5 width=20)
Query Queue: init_warehouse.default_queue
Optimizer: HQO version 4.1.0
ロールアップ集計
条件:
-
動的テーブルの
GROUP BYディメンションが、クエリのGROUP BYディメンションのスーパーセットである。つまり、動的テーブルはより細かい粒度で集計されている。 -
クエリ内の集計関数が、動的テーブルの列を集計することで導出できる。
-
サポートされている集計関数:
min、max、count、sum、およびavg。 -
DISTINCTを用いたロールアップ集計はサポートされていません。ただし、ディメンションが一致するシナリオで結果を動的テーブルから直接読み取れる場合を除きます。
集計関数のマッピング:
|
元の集計関数 |
必要な集計列 |
書き換え後の集計関数 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
例:
-- ベーステーブルの作成
CREATE TABLE base_sales_table(
day text not null,
hour int,
amount int
);
-- データの挿入
INSERT INTO base_sales_table
VALUES ('20250529', 12, 1),
('20250529', 12, 2),
('20250529', 12, 2),
('20250529', 13, 3),
('20250530', 13, 4),
('20250530', 14, 5),
('20250531', 14, 6);
-- 動的テーブルの作成。リライトを検証するため、まず自動リフレッシュを無効にします。
CREATE DYNAMIC TABLE dt_sales
WITH (
freshness = '1 minutes',
auto_refresh_mode='incremental',
auto_refresh_enable='false',
allowed_to_rewrite_query='true'
)
AS
SELECT
day,
hour,
min(amount),
max(amount),
sum(amount),
count(amount),
count(*) as rows,
count(1) as rows1,
count(distinct amount) as cd
FROM base_sales_table
GROUP BY day, hour;
-- 動的テーブルの手動リフレッシュ
REFRESH TABLE dt_sales;
クエリの例 1: day で集計 (ロールアップ) 。クエリの GROUP BY 列は動的テーブル定義のディメンションのサブセットであるため、クエリは書き換え可能です。
-- 元のクエリ
EXPLAIN SELECT day, min(amount), max(amount)
FROM base_sales_table
GROUP BY day;
実行計画は、最も低いレベルのスキャン演算子が Seq Scan on dt_sales であることを示しており、クエリがベーステーブル base_sales_table の代わりに動的テーブルから読み取っていることを示します。
QUERY PLAN
Gather (cost=0.00..5.00 rows=4 width=16)
-> HashAggregate (cost=0.00..5.00 rows=4 width=16)
Group Key: day
-> Redistribution (cost=0.00..5.00 rows=5 width=16)
Hash Key: day
-> Local Gather (cost=0.00..5.00 rows=5 width=16)
-> Seq Scan on dt_sales (cost=0.00..5.00 rows=5 width=16)
Query Queue: init_warehouse.default_queue
Optimizer: HQO version 4.1.0
クエリの例 2: sum + count + avg のロールアップ。ベーステーブルのクエリは avg を使用します。動的テーブルには事前計算された sum と count の値が含まれているため、avg を導出でき、クエリは書き換えられます。
-- 元のクエリ
EXPLAIN SELECT day, sum(amount), count(amount), avg(amount)
FROM base_sales_table
GROUP BY day;
実行計画は、クエリが動的テーブル dt_sales をスキャンするように書き換えられたことを示しています。基盤となる演算子が base_sales_table ではなく Seq Scan on dt_sales であることに注意してください。
QUERY PLAN
Gather (cost=0.00..5.00 rows=4 width=32)
-> Project (cost=0.00..5.00 rows=4 width=32)
-> Project (cost=0.00..5.00 rows=4 width=40)
-> HashAggregate (cost=0.00..5.00 rows=4 width=24)
Group Key: day
-> Redistribution (cost=0.00..5.00 rows=5 width=24)
Hash Key: day
-> Local Gather (cost=0.00..5.00 rows=5 width=24)
-> Seq Scan on dt_sales (cost=0.00..5.00 rows=5 width=24)
Query Queue: init_warehouse.default_queue
Optimizer: HQO version 4.1.0
フィルター条件付きのロールアップ集計
条件:
-
ベーステーブルに対するクエリは
WHERE句を含みますが、動的テーブルの定義にはWHERE句を含めることはできません。 -
WHERE句で使用されるすべての列は、動的テーブルのGROUP BYディメンションの一部でなければなりません。 -
サポートされている集計関数は
min、max、count、sum、およびavgです。DISTINCTはサポートされていません。
例:
-- ベーステーブルの作成
CREATE TABLE base_sales_table(
day text not null,
hour int,
amount int
);
-- データの挿入
INSERT INTO base_sales_table
VALUES ('20250529', 12, 1),
('20250529', 12, 2),
('20250529', 12, 2),
('20250529', 13, 3),
('20250530', 13, 4),
('20250530', 14, 5),
('20250531', 14, 6);
-- 動的テーブルの作成。リライトを検証するため、まず自動リフレッシュを無効にします。
CREATE DYNAMIC TABLE dt_sales
WITH (
freshness = '1 minutes',
auto_refresh_mode='incremental',
auto_refresh_enable='false',
allowed_to_rewrite_query='true'
)
AS
SELECT
day,
hour,
min(amount),
max(amount),
sum(amount),
count(amount),
count(*) as rows,
count(1) as rows1,
count(distinct amount) as cd
FROM base_sales_table
GROUP BY day, hour;
-- 動的テーブルの手動リフレッシュ
REFRESH TABLE dt_sales;
次の例では、ベーステーブルに対するクエリに WHERE 句があり、フィルター列は GROUP BY キーの一部です。したがって、クエリは書き換え可能です。
EXPLAIN SELECT day, sum(amount), count(amount), avg(amount)
FROM base_sales_table
WHERE day > '20250528' AND day <= '20250531'
GROUP BY day;
この EXPLAIN ステートメントを実行すると、Seq Scan on dt_sales 演算子がクエリがベーステーブルの代わりに動적テーブルをスキャンするように書き換えられたことを確認できるクエリプランが返されます。フィルター条件は元の WHERE 句と一致します。
QUERY PLAN
Gather (cost=0.00..5.00 rows=1 width=32)
-> Project (cost=0.00..5.00 rows=1 width=32)
-> Project (cost=0.00..5.00 rows=1 width=40)
-> HashAggregate (cost=0.00..5.00 rows=1 width=24)
Group Key: day
-> Redistribution (cost=0.00..5.00 rows=1 width=24)
Hash Key: day
-> Local Gather (cost=0.00..5.00 rows=1 width=24)
-> Seq Scan on dt_sales (cost=0.00..5.00 rows=1 width=24)
Filter: ((day > '20250528'::text) AND (day <= '20250531'::text))
RowGroupFilter: ((day > '20250528'::text) AND (day <= '20250531'::text))
Query Queue: init_warehouse.default_queue
Optimizer: HQO version 4.1.0
クエリリライトのステータス確認
クエリリライトを有効にした後、クエリが動的テーブルを使用したかどうかを次の方法で確認できます。
-
実行計画の確認: EXPLAIN の出力で、Scan 演算子のテーブル名を確認して、クエリが動的テーブルを使用したかどうかを判断します。
-
スロークエリログの確認:
hologres.hg_query_logテーブルのextended_infoフィールドには、リライトに使用された動的テーブルが記録されます。リライトが失敗した場合、このフィールドには失敗理由が含まれます。
select extended_info::json->>'rewrite_query_info' from hologres.hg_query_log where query_id = 'xxxxx';
?column?
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{"rewrite_failed_dt": [[\"public.dt3\", {\"rewrite_failed_cause\": \"Doesn't include all query required output columns\"}]], \"rewrite_succeeded_and_selected_dt\": [\"public.dt2\"], \"rewrite_succeeded_but_not_selected_dt\": [\"public.dt1\"]}
(1 row)
例
例 1:Hologres 内部テーブル
ベーステーブルは、TPC-H データセットの 100 GB の lineitem テーブルです。テーブルの作成とデータのインポート手順については、「ワンクリックでパブリックデータセットをインポート」 をご参照ください。この例では、動的テーブルは増分リフレッシュを使用し、パーティション化されていません。
CREATE DYNAMIC TABLE dt_lineitem_100g_incremental
WITH (
freshness = '10 minutes',
auto_refresh_mode='incremental',
auto_refresh_enable='false',
allowed_to_rewrite_query='true')
AS
select
l_returnflag,
l_linestatus,
l_shipdate,
sum(l_quantity) as sum_qty,
count(*) as count_order
from
hologres_dataset_tpch_100g.lineitem
group by
l_returnflag,
l_linestatus,
l_shipdate;
-- 手動リフレッシュ
REFRESH DYNAMIC TABLE dt_lineitem_100g_incremental;
ベーステーブルをクエリします。
set hg_enable_query_rewrite = on;
explain
select
l_returnflag,
l_linestatus,
l_shipdate,
sum(l_quantity) as sum_qty,
count(*) as count_order
from
hologres_dataset_tpch_100g.lineitem
where l_shipdate = '1998-12-01'
group by
l_returnflag,
l_linestatus,
l_shipdate;
実行計画は、クエリが動的テーブルをクエリするように書き換えられたことを示しています。
QUERY PLAN
Gather (cost=0.00..5.00 rows=1 width=22)
-> Local Gather (cost=0.00..5.00 rows=1 width=22)
-> Project (cost=0.00..5.00 rows=1 width=22)
-> Seq Scan on dt_lineitem_100g_incremental (cost=0.00..5.00 rows=1 width=20)
Filter: (l_shipdate = '1998-12-01'::date)
RowGroupFilter: (l_shipdate = '1998-12-01'::date)
Query Queue: init_warehouse.default_queue
Optimizer: HQO version 4.1.0
ベーステーブルをクエリした結果は次のとおりです。
l_returnflag | l_linestatus | l_shipdate | sum_qty | count_order
--------------+--------------+------------+----------+-------------
N | O | 1998-12-01 | 52841.00 | 2070
(1 row)
動的テーブルを直接クエリすると同じ結果が返されますが、これは最後の手動リフレッシュと一致しています (この例では自動リフレッシュが無効にされていました) 。
select
l_returnflag,
l_linestatus,
l_shipdate,
sum_qty,
count_order
from
dt_lineitem_100g_incremental
where l_shipdate = '1998-12-01' ;
l_returnflag | l_linestatus | l_shipdate | sum_qty | count_order
--------------+--------------+------------+----------+-------------
N | O | 1998-12-01 | 52841.00 | 2070
(1 row)
例 2:Paimon 外部テーブル
クエリリライトは、ベーステーブルが Paimon 外部テーブルである場合にも使用できます。次の手順に従ってください。
-
Paimon テーブルの準備: この例では、TPC-H 100 GB の
customerテーブルを Paimon にインポートします。詳細については、「Paimonテーブル」 をご参照ください。 -
Hologres で Paimon 外部テーブルを作成: テーブルを外部テーブルとして作成する必要があります。詳細については、「DLFカタログ経由でPaimonデータにアクセス」 をご参照ください。
-- 外部サーバーの作成
CREATE SERVER IF NOT EXISTS paimon_server FOREIGN DATA WRAPPER dlf_fdw OPTIONS (
catalog_type 'paimon',
metastore_type 'dlf-rest',
dlf_catalog '<dlf_catalog_name>'
);
-- IMPORT FOREIGN SCHEMA を使用して Paimon 外部テーブルを作成
IMPORT FOREIGN SCHEMA <schema_name>
limit to (customer)
FROM SERVER paimon_server into public
options (if_table_exist 'update');
-- データのクエリ
SELECT * FROM customer;
3. Paimon 外部テーブルを増分的に消費する動的テーブルの作成: Hologres で、Paimon 外部テーブルから増分的にリフレッシュする動的テーブルを作成します。この例では、リライトの検証を容易にするために自動リフレッシュは無効にされています。
-- 動的テーブルの作成
CREATE DYNAMIC TABLE dt_paimon_customer
WITH (
freshness = '10 minutes',
auto_refresh_mode='incremental',
auto_refresh_enable='false',
allowed_to_rewrite_query='true')
AS
SELECT
c_custkey,
avg(c_acctbal) ,
sum(c_acctbal) ,
count(c_acctbal)
FROM customer
group by c_custkey;
-- 動的テーブルの手動リフレッシュ
REFRESH DYNAMIC TABLE dt_paimon_customer;
4. クエリリライトを有効にして Paimon 外部テーブルをクエリします。
set hg_enable_query_rewrite = on;
SELECT
c_custkey,
avg(c_acctbal) ,
sum(c_acctbal) ,
count(c_acctbal)
FROM
customer
group by c_custkey ORDER BY 3 DESC LIMIT 3;
c_custkey | avg |sum |count
----------|-------------|---------|-----
3605586 |9999.990000 | 9999.99 |1
10705496 |9999.990000 |9999.99 |1
14959900 |9999.990000 |9999.99 |1
5. 動的テーブルのクエリ: 結果は、最新のリフレッシュのデータを反映します。
SELECT * FROM dt_paimon_customer ORDER BY 3 DESC LIMIT 3;
c_custkey | avg |sum |count
----------|-------------|---------|-----
3605586 |9999.990000 | 9999.99 |1
10705496 |9999.990000 |9999.99 |1
14959900 |9999.990000 |9999.99 |1
6. 実行計画で確認: プランは、クエリが動的テーブルにアクセスするように書き換えられたことを示しています。
QUERY PLAN
Limit (cost=0.00..5.07 rows=1 width=28)
-> Project (cost=0.00..5.07 rows=1 width=28)
-> Limit (cost=0.00..5.07 rows=1 width=36)
-> Sort (cost=0.00..5.07 rows=1 width=36)
Sort Key: sum DESC
-> Gather (cost=0.00..5.07 rows=1 width=36)
-> Local Gather (cost=0.00..5.07 rows=1 width=36)
-> Limit (cost=0.00..5.07 rows=1 width=36)
-> Sort (cost=0.00..5.07 rows=1 width=36)
Sort Key: sum DESC
-> Project (cost=0.00..5.07 rows=1 width=36)
-> Project (cost=0.00..5.07 rows=1 width=20)
-> Seq Scan on dt_paimon_customer (cost=0.00..5.07 rows=15000000 width=17)
Query Queue: init_warehouse.default_queue
Optimizer: HQO version 4.1.0