PostgreSQL 19 Beta 2 を Amazon RDS Database Preview Environment で試してみた
はじめに
2026年7月16日、Amazon RDS Database Preview EnvironmentでPostgreSQL 19 Beta 2が利用可能になりました。PostgreSQL 19は2026年6月4日にBeta 1、7月16日にBeta 2がリリースされています。
RDS Database Preview Environmentは、正式リリース前のPostgreSQLバージョンをRDSで試すための専用環境です。利用にあたっては次の制約があります。
- リージョンはus-east-2(Ohio)に固定される
- 作成したDBインスタンスは最大60日で自動削除される
- 本番用途には利用できない(検証・評価目的のみ)
PostgreSQL 19 Beta 1については、前回の記事でSQL/PGQの基本クエリ、REPACKの基本動作、論理レプリケーションの動的有効化を確認しました。
Beta 2は新機能の大幅な追加ではなく、Beta 1のテスト期間に見つかった問題の修正と安定性向上が中心です。公式発表では、SQL/PGQに関する複数の修正、REPACKワーカーの終了処理の修正、autovacuumのmultixact ageスコア計算の修正などが挙げられています。本記事ではBeta 1記事で確認した基本動作の再掲を避け、SQL/PGQとREPACKをより掘り下げるとともに、前回未検証だった機能を確認します。
Beta 1記事からの追加検証:
- SQL/PGQ — 相互フォロー検出、
EXPLAIN、カタログテーブル、未サポート構文の確認 REPACK/REPACK CONCURRENTLY—USING INDEXによるクラスタリング、同時アクセス、ロック挙動
今回新たに検証:
- autovacuumの並列化(
autovacuum_max_parallel_workers) INSERT ... ON CONFLICT DO SELECTCOPY TO (FORMAT JSON)WAIT FOR LSN
検証内容
検証環境
| 項目 | 値 |
|---|---|
| リージョン | us-east-2(Ohio)— Preview Environment固定 |
| エンジンバージョン | PostgreSQL 19beta2(19.20260716) |
| インスタンスクラス | db.t4g.medium |
| プラットフォーム | aarch64-unknown-linux-gnu, GCC 12.4.0, 64-bit |
| 接続方法 | psql(ローカル端末から直接接続) |
カスタムパラメータグループを作成し、次の設定を行いました。
| パラメータ | 値 | 目的 |
|---|---|---|
| rds.logical_replication | 1 | REPACK (CONCURRENTLY)用(wal_level → logical) |
| autovacuum_max_parallel_workers | 4 | 並列autovacuum検証用 |
設定後にインスタンスを再起動して反映しています。
SQL/PGQグラフクエリ
PostgreSQL 19ではSQL:2023で標準化されたSQL/PGQ(SQL Property Graph Queries)がサポートされます。リレーショナルテーブルの上にプロパティグラフを定義し、SELECT文のGRAPH_TABLE句でパターンマッチングによるグラフクエリを記述できます。ユーザーとフォロー関係を表すSNS的なデータを題材に試しました。
まず、ユーザーを表すusersと、フォロー関係を表すfollowsを用意します。
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
joined DATE
);
CREATE TABLE follows (
follower_id INT NOT NULL REFERENCES users(user_id),
followee_id INT NOT NULL REFERENCES users(user_id),
since DATE,
PRIMARY KEY (follower_id, followee_id)
);
INSERT INTO users (name, joined) VALUES
('Alice', '2023-01-01'),
('Bob', '2023-02-15'),
('Charlie', '2023-03-20'),
('Diana', '2023-04-10'),
('Eve', '2023-05-05');
INSERT INTO follows (follower_id, followee_id, since) VALUES
(1, 2, '2024-01-15'), -- Alice -> Bob
(1, 3, '2024-02-01'), -- Alice -> Charlie
(1, 4, '2024-04-05'), -- Alice -> Diana
(2, 3, '2024-01-20'), -- Bob -> Charlie
(2, 4, '2024-03-01'), -- Bob -> Diana
(3, 5, '2024-02-15'), -- Charlie -> Eve
(4, 1, '2024-04-01'), -- Diana -> Alice
(5, 2, '2024-03-10'); -- Eve -> Bob
続いて、この2テーブルの上にプロパティグラフsocial_graphを定義します。
CREATE PROPERTY GRAPH social_graph
VERTEX TABLES (
users AS person
KEY (user_id)
PROPERTIES (user_id, name, joined)
)
EDGE TABLES (
follows AS follows_rel
KEY (follower_id, followee_id)
SOURCE KEY (follower_id) REFERENCES person (user_id)
DESTINATION KEY (followee_id) REFERENCES person (user_id)
PROPERTIES (since)
);
GRAPH_TABLEのパスパターンは(頂点) -[辺]-> (頂点)の形で記述します。Aliceが直接フォローしているユーザーを取得する1ホップのクエリです。
SELECT * FROM GRAPH_TABLE (social_graph
MATCH (a IS person WHERE a.name = 'Alice') -[f IS follows_rel]-> (b IS person)
COLUMNS (a.name AS follower, b.name AS followee, f.since AS since)
);
follower | followee | since
----------+----------+------------
Alice | Bob | 2024-01-15
Alice | Charlie | 2024-02-01
Alice | Diana | 2024-04-05
次に、辺を2つ連結して2ホップ先まで辿るfriends-of-friendsのクエリです。WHERE a.name <> c.nameで起点自身への戻りを除外しています。
SELECT * FROM GRAPH_TABLE (social_graph
MATCH (a IS person WHERE a.name = 'Alice')
-[f1 IS follows_rel]-> (b IS person)
-[f2 IS follows_rel]-> (c IS person)
WHERE a.name <> c.name
COLUMNS (a.name AS origin, b.name AS via, c.name AS destination)
);
origin | via | destination
--------+---------+-------------
Alice | Bob | Charlie
Alice | Bob | Diana
Alice | Charlie | Eve
さらに、相互フォロー(AがBを、BがAをフォローしている関係)も、辺を2本使って起点に戻るパターンで検出できます。WHERE a.name < b.nameは同じペアが2回出力されるのを防ぐためのものです。
SELECT * FROM GRAPH_TABLE (social_graph
MATCH (a IS person) -[f1 IS follows_rel]-> (b IS person) -[f2 IS follows_rel]-> (a)
WHERE a.name < b.name
COLUMNS (a.name AS person1, b.name AS person2)
);
person1 | person2
---------+---------
Alice | Diana
AliceとDianaが相互フォロー関係にあることが検出できました。このほか、辺の向きを逆にした被フォロー方向のクエリや、GROUP BYを組み合わせた集約クエリについても、通常のSQLと同様に動作しました。
friends-of-friendsクエリをEXPLAINすると、内部的には結合として展開されていることが分かります。
Nested Loop
Join Filter: (users.name <> users_2.name)
-> Nested Loop
Join Filter: (users_1.user_id = follows_1.follower_id)
-> Nested Loop
-> Hash Join
Hash Cond: (follows.follower_id = users.user_id)
-> Seq Scan on follows
-> Hash
-> Seq Scan on users
Filter: (name = 'Alice'::text)
-> Index Scan using users_pkey on users users_1
Index Cond: (user_id = follows.followee_id)
-> Index Only Scan using follows_pkey on follows follows_1
Index Cond: (follower_id = follows.followee_id)
-> Index Scan using users_pkey on users users_2
Index Cond: (user_id = follows_1.followee_id)
グラフ専用のオペレータは出現せず、通常の結合として実行されています。
一方で、Beta 2時点では未サポートの構文もありました。ホップ数を指定する量化パスパターン({1,3}のような繰り返し)はエラーになります。
ERROR: element pattern quantifier is not supported
最短経路を求めるANY SHORTESTも同様にパースされませんでした。
ERROR: syntax error at or near "ANY"
なお、プロパティグラフの定義はPostgreSQL 19で新設された次のカタログテーブルで管理されています。
pg_propgraph_element— グラフの頂点/辺要素pg_propgraph_label— ラベル定義pg_propgraph_label_property— ラベルのプロパティpg_propgraph_property— プロパティ定義pg_propgraph_element_label— 要素とラベルの関連
REPACK CONCURRENTLY
PostgreSQL 19では、テーブルの肥大化(bloat)を解消するREPACKコマンドが追加されました。従来VACUUM FULLや外部拡張pg_repackが担っていた領域を、CONCURRENTLYによるオンライン再編成まで含めて外部拡張なしにPostgreSQL本体のSQLコマンドで実行できます。
100万行のうち大半を削除して肥大化させたテーブルに対してREPACKを実行したところ、テーブルサイズは279 MBから56 MBへ縮小しました。
REPACK repack_test;
REPACKには再編成中の大部分で他トランザクションからのアクセスを許容するCONCURRENTLYオプションがあります。REPACK (CONCURRENTLY)は内部で論理デコーディングと専用のレプリケーションスロットを使用します。今回のRDS Preview環境では、rds.logical_replicationが未設定の状態で実行すると次のエラーになりました。
ERROR: cannot execute REPACK (CONCURRENTLY) in this configuration
RDSではwal_levelを直接変更できないため、パラメータグループでrds.logical_replication = 1を設定して再起動し、wal_levelをlogicalに引き上げました。設定変更後にREPACK (CONCURRENTLY)を実行すると正常に完了し、同様にテーブルサイズが縮小しました。
REPACKはUSING INDEXを指定することで、指定インデックスの順序にテーブルを物理的に並び替えるクラスタリング再編成も行えます。
REPACK repack_test USING INDEX idx_repack_cat;
実行後にctidを確認すると、category = 1の行が(0,1)〜(2499,14)、category = 6の行が(2500,1)〜(4999,14)のように連続配置されていました。REPACK (CONCURRENTLY) USING INDEXでも同様にクラスタリングが成功しました。
CONCURRENTLY実行中に別セッションから同じテーブルへアクセスした際の挙動は次のとおりです。なお、最終的なファイル入れ替え時には短時間のACCESS EXCLUSIVEロックが取得されます。
| 操作 | 今回の検証結果 |
|---|---|
| SELECT | 目立った待ちなく完了 |
| INSERT | 目立った待ちなく完了、REPACK完了後も永続化 |
| UPDATE | 目立った待ちなく完了、REPACK完了後も永続化 |
今回の検証ではSELECT・INSERT・UPDATEのいずれも目立った待ちなく完了し、再編成中に発生した書き込みもREPACK完了後に保持されていました。
並列autovacuum
PostgreSQL 19ではautovacuumのインデックス処理を並列化できるようになり、新パラメータautovacuum_max_parallel_workersが追加されました。あわせて手動VACUUMの並列度を制御するmax_parallel_maintenance_workersとの関係も確認しました。
| パラメータ | デフォルト | 範囲 | 備考 |
|---|---|---|---|
| autovacuum_max_parallel_workers | 0 | 0〜1024 | PG19新規。RDS Previewで変更可能(dynamic / pending-reboot) |
| max_parallel_maintenance_workers | 2 | 0〜1024 | 手動VACUUMの並列度上限。SETで変更可能 |
RDSではカスタムパラメータグループでautovacuum_max_parallel_workersを変更し、インスタンスを再起動して反映しました。
まず手動VACUUM (PARALLEL)の動作を確認します。6つのインデックスを持つテーブルに対して、並列度4を指定して実行しました。
SET max_parallel_maintenance_workers = 4;
VACUUM (PARALLEL 4, VERBOSE) vacuum_test;
INFO: launched 4 parallel vacuum workers for index vacuuming (planned: 4)
INFO: finished vacuuming "testdb.public.vacuum_test": index scans: 1
pages: 5645 removed, 29033 remain, 34678 scanned (100.00% of total)
tuples: 83342 removed, 366667 remain
index scan needed: 34678 pages from table (100.00% of total) had 683333 dead item identifiers removed
parallel workers: index vacuum: 4 planned, 4 launched in total
index "vacuum_test_pkey": pages: 5486 in total, 1367 newly deleted
index "idx_vac_a": pages: 1299 in total, 201 newly deleted
index "idx_vac_b": pages: 1401 in total, 11 newly deleted
index "idx_vac_c": pages: 1356 in total, 448 newly deleted
index "idx_vac_d": pages: 1405 in total, 198 newly deleted
index "idx_vac_e": pages: 4577 in total, 3312 newly deleted
6つのインデックスに対して4ワーカーが起動し、インデックスのvacuumが並列に処理されたことがVERBOSE出力から読み取れます。
次に、autovacuumが自動起動した際に並列ワーカーが使われるかを観測しました。pg_stat_progress_vacuumとpg_stat_activityは別々に問い合わせたため、以下の出力は同一時点のスナップショットではありません。
pg_stat_progress_vacuum: phase=scanning heap, indexes_total=0, mode=normal, started_by=autovacuum
pg_stat_activity: pid=2656, leader_pid=2643, backend_type=parallel worker, state=active
started_by=autovacuumのvacuum(pid=2643)に対して、leader_pid=2643を持つparallel workerが起動していました。PIDの対応から当該autovacuumに属するワーカーであることを確認しました。autovacuum_max_parallel_workers = 4の設定により、autovacuumでも並列ワーカーが使われています。
pg_stat_progress_vacuumの新列
pg_stat_progress_vacuumにはPostgreSQL 19でmodeとstarted_byが追加され、VACUUMの動作モードと起動元を確認できるようになりました。既存のindexes_total、indexes_processed、delay_timeと組み合わせることで、並列処理や進捗の内訳をより詳しく追跡できます。
| 列名 | 型 | 説明 | 備考 |
|---|---|---|---|
| mode | text | vacuumモード(本検証ではnormalを確認) |
PG19新規 |
| started_by | text | 起動元(本検証ではautovacuumを確認。ドキュメント上はmanual / autovacuum / autovacuum_wraparound) |
PG19新規 |
| indexes_total | bigint | 処理対象インデックス数 | PG18以前から存在 |
| indexes_processed | bigint | 処理済みインデックス数 | PG18以前から存在 |
| delay_time | double precision | コストベース遅延による累積スリープ時間 | PG18以前から存在 |
なお、手動VACUUM (PARALLEL n)の並列度はmax_parallel_maintenance_workersの影響を受けます。VACUUM (PARALLEL 4)を指定しても、max_parallel_maintenance_workers = 2の場合は2ワーカーまでしか起動しませんでした。
INFO: launched 2 parallel vacuum workers for index cleanup (planned: 2)
parallel workers: index cleanup: 2 planned, 2 launched
SET max_parallel_maintenance_workers = 4に変更したうえで再実行すると、4ワーカーが起動します。手動VACUUMの並列度は要求値とmax_parallel_maintenance_workersの小さいほうに制限されます。
その他の新機能
主軸の3機能に加えて、ON CONFLICT DO SELECT・COPY TO (FORMAT JSON)・WAIT FOR LSNも確認しました。以降の確認では、次のキャッシュ的なテーブルを共通で使用します。
CREATE TABLE cache_table (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);
INSERT INTO cache_table (key, value) VALUES ('user:1', 'Alice');
ON CONFLICT DO SELECT
INSERT ... ON CONFLICTにDO SELECTが追加され、衝突時に既存行をRETURNINGで返せるようになりました。既存のkey = 'user:1'に対してINSERTを試みると、UPDATEを行わずに既存行がそのまま返ります。
INSERT INTO cache_table (key, value) VALUES ('user:1', 'Alice_updated')
ON CONFLICT (key) DO SELECT
RETURNING key, value, created_at;
key | value | created_at
--------+-------+-------------------------------
user:1 | Alice | 2026-07-16T22:25:18.832559
valueはINSERTで指定したAlice_updatedではなく既存のAliceのままで、更新は発生していません。衝突しない場合は通常のINSERTと同じ挙動になります。
INSERT INTO cache_table (key, value) VALUES ('user:2', 'Bob')
ON CONFLICT (key) DO SELECT
RETURNING key, value, created_at;
key | value | created_at
--------+-------+-------------------------------
user:2 | Bob | 2026-07-16T22:25:18.99338
user:2は存在しなかったため通常のINSERTが実行され、新規行が返りました。1回のINSERT文でatomicなget-or-createを表現できます。
COPY TO (FORMAT JSON)
COPY TOでJSON形式の出力がサポートされました。
COPY (SELECT key, value, created_at FROM cache_table ORDER BY key) TO STDOUT (FORMAT JSON);
{"key":"user:1","value":"Alice","created_at":"2026-07-16T22:25:18.832559"}
{"key":"user:2","value":"Bob","created_at":"2026-07-16T22:25:18.99338"}
デフォルトでは1行につき1つのJSONオブジェクトが出力されます(いわゆるNDJSON形式)。FORCE_ARRAYオプションを付けると、全体を1つのJSON配列として出力できました。
WAIT FOR LSN
WAIT FORは、指定したLSNへの到達を待機するコマンドで、リードレプリカでのread-your-writes一貫性を実現する用途を想定しています。デフォルトのモードはstandby_replayのため、モードを指定せずにプライマリで実行するとエラーになりました。
ERROR: recovery is not in progress
なお、MODE 'primary_flush'を指定すればプライマリで指定LSNのフラッシュを待機することもできます。
TIMEOUTはWITH句の中に指定する構文です。本検証ではTIMEOUTを単独の句として記述したため構文エラーになりました。公式ドキュメントによれば、正しくは次のように記述します(本検証ではプライマリ環境のためWITH句の実動作までは確認していません)。
WAIT FOR LSN '0/12345678' WITH (TIMEOUT '5s', NO_THROW);
NO_THROWを指定すると、タイムアウト時にエラーではなくtimeoutというステータスが返ります。
まとめ
PostgreSQL 19 Beta 2をAmazon RDS Database Preview Environment上で確認しました。Beta 2は新機能の追加というよりも、Beta 1で導入された機能の不具合修正と安定性向上が中心のリリースです。
前回のBeta 1記事ではSQL/PGQとREPACKの基本動作を確認しましたが、今回はSQL/PGQの実行計画や未サポート構文、REPACKのクラスタリングと同時アクセスを追加検証しました。グラフクエリでは既存のリレーショナルテーブル上にプロパティグラフを定義でき、今回の実行計画では既存のインデックスも利用されていました。フォロー関係などの固定長パスパターンを追加のグラフDBなしにSQL/PGQで記述できます。
また、前回未検証だった並列autovacuum、ON CONFLICT DO SELECT、JSON形式のCOPY、WAIT FOR LSNについても動作を確認しました。今回のRDS Preview環境では、REPACK (CONCURRENTLY)と並列autovacuumの検証にあたりカスタムパラメータグループの変更とDBインスタンスの再起動が必要でした。
Beta 2時点では、量化パスパターンとANY SHORTESTが未サポートでした。これらはGA時に改めて対応状況を確認します。







