数十億レコードなどの大量のデータを処理する場合、PostgreSQL マテリアライズドビューのリフレッシュは遅くなる可能性があります。データの鮮度が低いと、ビジネスインテリジェンス (BI) 分析とレポートの効率に影響します。PolarDB for PostgreSQL の In-Memory Column Index (IMCI) 機能は、マテリアライズドビューのリフレッシュに必要な時間を大幅に短縮しできます。これにより、データの鮮度が向上し、BI およびレポートワークロードのパフォーマンスが向上します。
ソリューションの概要
IMCI は、PolarDB for PostgreSQL が提供する分析高速化エンジンです。行ストアテーブルの列ストアインデックスを作成し、行ストアデータと列ストアインデックス間の一貫性を自動的に維持します。複雑な集約や結合を実行する場合、データベースは IMCI を計算に使用でき、従来の行ストアスキャンをはるかに超えるパフォーマンスを実現します。
このソリューションの中核は、マテリアライズドビューのベーステーブルに IMCI を作成することです。これにより、マテリアライズドビューの作成とリフレッシュが高速化されます。
前提条件
クラスターバージョン:
PostgreSQL 16 (マイナーエンジンバージョン 2.0.16.8.3.0 以降)
PostgreSQL 14 (マイナーエンジンバージョン 2.0.14.10.20.0 以降)
説明コンソールでマイナーエンジンバージョンを表示するか、
SHOW polardb_version;文を使用して表示できます。クラスターがマイナーエンジンバージョンの要件を満たしていない場合は、マイナーエンジンバージョンをアップグレードする必要があります。ソーステーブルにはプライマリキーが必要です。列ストアインデックスを作成するときは、プライマリキー列を含める必要があります。
wal_levelパラメーターはlogicalに設定する必要があります。この設定により、論理エンコーディングに必要な情報が先行書き込みログ (WAL) に追加されます。説明コンソールで wal_level パラメーターを設定できます。詳細については、「wal_level パラメーターを設定する」をご参照ください。このパラメーターを変更すると、クラスターが再起動します。この変更を行う前に、ビジネス運用を慎重に計画してください。
注意事項
各テーブルに作成できる列ストアインデックスは 1 つだけです。
列ストアインデックスは変更できません。列ストアインデックスに列を追加するには、インデックスを再構築する必要があります。
準備
環境の準備
前提条件を満たす PolarDB for PostgreSQL クラスターを準備します。
IMCI を有効にします。有効化の方法は、PolarDB for PostgreSQL クラスターのマイナーエンジンバージョンによって異なります。
ビジネスシステムに pg_hint_plan 拡張機能をインストールします。この拡張機能を使用すると、特別なコメントのヒントを使用して実行計画を調整できます。
CREATE EXTENSION pg_hint_plan;postgresシステムデータベースに pg_cron (スケジュールされたタスク) 拡張機能を作成します。この拡張機能を使用すると、指定した時間または間隔でタスクを自動的に実行できます。データベースに切り替えます。
\c postgres;拡張機能をインストールします。
CREATE EXTENSION pg_cron;
データの準備
ビジネスシステムで、customers テーブルと orders テーブルを作成し、それらに IMCI を作成して、テストデータを挿入します。
ビジネスシステムに切り替えます。この例では
testdbを使用します。\c testdb;テーブルを作成してデータを挿入します。
-- customers テーブルと IMCI を作成します。 CREATE TABLE customers ( customer_id SERIAL PRIMARY KEY, customer_name VARCHAR(100), email VARCHAR(100) ); CREATE INDEX idx_customers_csi ON customers USING csi; -- orders テーブルと IMCI を作成します。 CREATE TABLE orders ( order_id SERIAL PRIMARY KEY, order_date DATE, amount DECIMAL(10, 2), customer_id INT REFERENCES customers(customer_id) ); CREATE INDEX idx_orders_csi ON orders USING csi; -- customers テーブルにデータを挿入します。 INSERT INTO customers (customer_name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), ('Charlie', 'charlie@example.com'); -- orders テーブルにデータを挿入します。 INSERT INTO orders (order_date, amount, customer_id) VALUES ('2025-06-01', 200.00, 1), ('2025-06-02', 150.00, 2), ('2025-06-03', 300.00, 1), ('2025-06-04', 100.00, 3);
マテリアライズドビューの作成
マテリアライズドビューを作成するときは、ヒントを使用してクエリオプティマイザーに列ストアインデックスを計算に使用させます。
/*+ SET(polar_csi.enable_query on) SET(polar_csi.cost_threshold 0) SET(polar_csi.exec_parallel 6) SET(polar_csi.memory_limit 10240) */CREATE MATERIALIZED VIEW mv_customer_orders AS
SELECT
c.customer_name AS customer_name,
o.order_date AS order_date,
o.amount AS amount
FROM
orders o
JOIN
customers c ON o.customer_id = c.customer_id;ヒントパラメーター
パラメーター | 説明 |
| クエリで列ストアインデックスを使用できるようにします。 |
| コストのしきい値を 0 に設定して、列ストアインデックスを強制的に使用します。 |
| 列ストア計算の並列処理の次数を設定します。この値は、ノードの CPU コア数を超えないようにすることをお勧めします。 |
| 計算に使用可能なメモリを MB 単位で設定します。 |
マテリアライズドビューのリフレッシュ
マテリアライズドビューをリフレッシュする関数の作成
リフレッシュプロセスを関数にカプセル化します。リフレッシュには次の関数を使用することをお勧めします。インデックスと所有権を保持しながら、古いビューを安全に置き換えることができるためです。
次の関数は参照用です。安全なスイッチオーバーを保証しますが、本番環境で使用する前に十分にテストする必要があります。
-- view_name はマテリアライズドビューの名前、schema_name はマテリアライズドビューが存在するスキーマ (デフォルトは current_schema)、new_owner は新しく作成されたマテリアライズドビューのオーナーです。
CREATE OR REPLACE FUNCTION refresh_materialized_view_safely_using_csi(
view_name TEXT,
schema_name TEXT DEFAULT NULL,
new_owner TEXT DEFAULT NULL
)
RETURNS BOOL
LANGUAGE plpgsql
AS $$
DECLARE
view_definition TEXT;
new_view_name TEXT;
old_view_name TEXT;
index_record RECORD;
index_creation_sql TEXT;
explain_result TEXT;
target_schema TEXT;
qualified_old_name TEXT;
qualified_new_name TEXT;
current_owner TEXT;
grant_record RECORD;
BEGIN
-- ターゲットスキーマを決定します (入力パラメーターまたは現在のスキーマを使用)。
IF schema_name IS NULL THEN
target_schema := current_schema();
ELSE
target_schema := schema_name;
END IF;
-- 完全修飾テーブル名を構築します。
qualified_old_name := format('%I.%I', target_schema, view_name);
qualified_new_name := format('%I.%I', target_schema, view_name || '_new');
RAISE NOTICE 'Operating in schema: %', target_schema;
-- マテリアライズドビューが存在することを確認します。
IF NOT EXISTS (
SELECT 1 FROM pg_matviews
WHERE matviewname = view_name
AND schemaname = target_schema
) THEN
RAISE EXCEPTION 'Materialized view "%" does not exist in schema "%"', view_name, target_schema;
END IF;
-- マテリアライズドビューの定義と現在のオーナーを取得します。
SELECT m.definition, p.rolname INTO view_definition, current_owner
FROM pg_matviews m
JOIN pg_class c ON m.matviewname = c.relname AND m.schemaname = target_schema
JOIN pg_roles p ON c.relowner = p.oid
WHERE m.matviewname = view_name
AND m.schemaname = target_schema;
IF view_definition IS NULL THEN
RAISE EXCEPTION 'Failed to retrieve definition for materialized view "%"', view_name;
END IF;
-- 新しいビューと古いビューの名前を設定します。
old_view_name := view_name;
new_view_name := view_name || '_new';
-- IMCI のパフォーマンスパラメーター。
SET LOCAL polar_csi.cost_threshold = 0;
-- クエリプランを出力します。
RAISE NOTICE 'Query plan for materialized view refresh:';
FOR explain_result IN EXECUTE format('/*+ SET(polar_csi.enable_query on) */ EXPLAIN CREATE MATERIALIZED VIEW %s AS %s', qualified_new_name, view_definition) LOOP
RAISE NOTICE '%', explain_result;
END LOOP;
BEGIN
-- 新しいマテリアライズドビューを作成します。
EXECUTE format('/*+ SET(polar_csi.enable_query on) */ CREATE MATERIALIZED VIEW %s AS %s', qualified_new_name, view_definition);
-- 新しいオーナーが指定されている場合は、オーナーを設定します。
IF new_owner IS NOT NULL THEN
-- ユーザーが存在することを確認します。
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = new_owner) THEN
RAISE EXCEPTION 'Role "%" does not exist', new_owner;
END IF;
EXECUTE format('ALTER MATERIALIZED VIEW %s OWNER TO %I', qualified_new_name, new_owner);
RAISE NOTICE 'Changed owner from "%" to "%"', current_owner, new_owner;
END IF;
-- 古いビューから新しいビューにすべてのインデックスをコピーします。
FOR index_record IN
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = old_view_name
AND schemaname = target_schema
LOOP
-- 古いビュー名を新しいビュー名に置き換えます。
index_creation_sql := regexp_replace(
index_record.indexdef,
' ON ' || target_schema || '.' || old_view_name || ' ',
' ON ' || target_schema || '.' || new_view_name || ' ',
'i'
);
-- UNIQUE インデックスの特殊なケースを処理します。
index_creation_sql := regexp_replace(
index_creation_sql,
'INDEX ' || index_record.indexname || ' ON',
'INDEX ' || index_record.indexname || '_new ON',
'i'
);
RAISE NOTICE 'Creating index: %', index_creation_sql;
EXECUTE index_creation_sql;
END LOOP;
-- 古いビューから権限をコピーします。
RAISE NOTICE 'Restoring permissions to new view %.%', target_schema, new_view_name;
FOR grant_record IN
SELECT
(acl).grantee::regrole::text AS grantee,
(acl).privilege_type
FROM
pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
CROSS JOIN aclexplode(c.relacl) AS acl
WHERE
n.nspname = target_schema
AND c.relname = old_view_name
LOOP
CONTINUE WHEN grant_record.grantee IS NULL;
-- RAISE NOTICE 'Granting % ON %.% TO %',
-- grant_record.privilege_type, target_schema, new_view_name, grant_record.grantee;
EXECUTE format(
'GRANT %s ON %I.%I TO %s',
grant_record.privilege_type,
target_schema,
new_view_name,
quote_ident(grant_record.grantee)
);
END LOOP;
-- 古いマテリアライズドビューを削除します。
EXECUTE format('DROP MATERIALIZED VIEW %s', qualified_old_name);
-- 新しいマテリアライズドビューを元の名前に変更します。
EXECUTE format('ALTER MATERIALIZED VIEW %s RENAME TO %I', qualified_new_name, old_view_name);
-- インデックスの名前を変更します (_new サフィックスを削除)。
FOR index_record IN
SELECT indexname
FROM pg_indexes
WHERE tablename = old_view_name
AND schemaname = target_schema
LOOP
IF position('_new' in index_record.indexname) > 0 THEN
EXECUTE format(
'ALTER INDEX %I.%I RENAME TO %I',
target_schema,
index_record.indexname,
replace(index_record.indexname, '_new', '')
);
END IF;
END LOOP;
RETURN TRUE;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Failed to refresh materialized view: %', SQLERRM;
RETURN FALSE;
END;
END;
$$;パラメーター
パラメーター | 説明 |
| 関数名。必要に応じて変更できます。 |
| マテリアライズドビューの名前。 |
| マテリアライズドビューが存在するスキーマ。デフォルトは `current_schema` です。 |
| 新しく作成されたマテリアライズドビューの新しいオーナー。 |
| クエリで列ストアインデックスを使用できるようにします。 |
| コストのしきい値を 0 に設定して、列ストアインデックスを強制的に使用します。 |
| 列ストア計算の並列処理の次数を設定します。この値は、ノードの CPU コア数を超えないようにすることをお勧めします。 |
| 計算に使用可能なメモリを MB 単位で設定します。 |
リフレッシュの実行
手動リフレッシュ
必要に応じて、関数を手動で呼び出してリフレッシュを実行できます。mv_customer_orders をマテリアライズドビューの名前に置き換えてください。
SELECT refresh_materialized_view_safely_using_csi('mv_customer_orders');pg_cron を使用したスケジュールされたリフレッシュ
タスクは
postgresシステムデータベースでのみ作成でき、特権アカウントを使用する必要があります。再構築されたマテリアライズドビューのオーナーを指定して、特権アカウントによってビューが作成された後に一般ユーザーがビューを読み取れなくなるのを防ぐことができます。他の権限設定を変更するには、前述のリフレッシュ関数を調整する必要があります。
pg_cronを使用してスケジュールされたリフレッシュを行う場合、タスクがスタックするのを防ぐために、タスクの実行間隔がマテリアライズドビューの実際のリフレッシュ時間よりも長いことを確認してください。リフレッシュにはデータの書き込みが含まれるため、通常は単純なSELECT文よりもはるかに遅くなります。
スケジュールされたタスクの作成
postgres システムデータベースに切り替えます。pg_cron で、タスク名、間隔、実行する操作などのパラメーターを指定します。詳細については、「pg_cron (スケジュールされたタスク) 拡張機能」をご参照ください。
データベースに切り替えます。
\c postgres;スケジュールされたタスクを作成します。パラメーターを実際の値に置き換えてください。
説明<mv_name>をマテリアライズドビューの名前に置き換えます。<database_name>を業務システムの名前に置き換えます。<schema_name>をスキーマの名前に置き換えます。<user_name>を実際のユーザー名に置き換えます。
構文
SELECT cron.schedule_in_database( 'refresh_mv_customer_orders', -- タスク名 (カスタマイズ可能) '*/5 * * * *', -- Cron 式、たとえば 5 分ごとに実行 $$SELECT refresh_materialized_view_safely_using_csi('<mv_name>', '<schema_name>', '<user_name>')$$, '<database_name>' );例
SELECT cron.schedule_in_database( 'refresh_mv_customer_orders', -- タスク名 (カスタマイズ可能) '*/5 * * * *', -- Cron 式、たとえば 5 分ごとに実行 $$SELECT refresh_materialized_view_safely_using_csi('mv_customer_orders', 'public', 'polarpg')$$, 'testdb' );
設定済みのスケジュールされたタスクの表示
次の SQL 文を実行して、設定済みのスケジュールされたタスクを表示します。
SELECT * FROM cron.job;次の結果が返されます。
jobid | schedule | command | nodename | nodeport | database | username | active | jobname
-------+-------------+----------------------------------------------------------------------------------------------+----------+----------+----------+----------+--------+----------------------------
1 | */5 * * * * | SELECT refresh_materialized_view_safely_using_csi('mv_customer_orders', 'public', 'polarpg') | /data/. | 3000 | testdb | polarpg | t | refresh_mv_customer_orders
(1 row)スケジュールされたタスクの削除
スケジュールされたリフレッシュが不要になった場合は、次の SQL 文を実行してタスクを削除できます。
SELECT cron.unschedule('refresh_my_materialized_view');スケジュールされたタスクの実行詳細の表示
次の SQL 文を実行して、スケジュールされたタスクの実行詳細を表示できます。
SELECT * FROM cron.job_run_details;次の結果が返されます。
jobid | runid | job_pid | database | username | command | status | return_message | start_time | end_time
-------+-------+---------+----------+----------+----------------------------------------------------------------------------------------------+-----------+----------------+-------------------------------+-------------------------------
1 | 1 | 76537 | testdb | polarpg | SELECT refresh_materialized_view_safely_using_csi('mv_customer_orders', 'public', 'polarpg') | succeeded | 1 row | 2025-08-27 08:35:00.007231+00 | 2025-08-27 08:35:00.024946+00
(1 rows)マテリアライズドビューのクエリ
次の SQL 文を実行して、マテリアライズドビューをクエリします。mv_customer_orders をご自身のマテリアライズドビューの名前に置き換えてください。
クエリを実行する前に、ビジネスデータベースに切り替えていることを確認してください。
SELECT customer_name, COUNT(*) FROM mv_customer_orders GROUP BY customer_name;




