Skip to content

PostgreSQL 19 の新しいシステムビュー

image 512x512 8
2026年9月1日 · 23分で読む

PostgreSQL 19 のモニタリング機能強化についての記事を執筆し、10 月の PostgreSQL Conference Europe で行う Postgres のオブザーバビリティに関する新しい講演の準備を進める中で、今回のリリースではシステムビューが独立したセクションとして取り上げられていることに気づきました。このように大きく扱われたのは、PG13 と PG14 以来のことです。そこで、何が変わったのかを詳しく解説するために、システムビューを単独で取り上げるブログ記事を書くことにしました。PostgreSQL 19 では、pg_stat_lock、pg_stat_recovery、pg_stat_autovacuum_scores、pg_dsm_registry_allocations という 4 つの新しいビューが追加されました。これらはどれも、以前のモニタリングに関するブログでの 1 行の言及だけでは収まらない内容ですので、ここで詳しく見ていきましょう。

免責事項: 本記事の執筆時点で PostgreSQL 19 はまだベータ版であり、開発サイクルの途中でカラム名が変更された箇所もすでに存在します。一般提供 (GA) までに変更や差し戻しが行われる可能性があります。最新の正確な情報はリリースノートをご確認ください。

pg_stat_lock {#pg_stat_lock}

ロックは私が特に関心を持っている分野です 😀 2025 年には、「Anatomy of Table-Level Locks in PostgreSQL」というタイトルで 16 のカンファレンスで登壇しました。興味のある方は、その一部の録画が YouTube で公開されています。ですので、PostgreSQL 19 で新しいロック関連ビューが登場したのを見たときの私の興奮を想像していただけるでしょう。

これまで、ロック競合の状況を把握するための手段は pg_locks(現時点のスナップショットであり、履歴は保持されない)と log_lock_waits の出力(履歴は残るが、集計するにはログを自分で解析する必要がある。ただし、PostgreSQL 19 ではデフォルトで有効化されました。この変更は以前のモニタリングに関する記事で取り上げました)に限られていました。

PostgreSQL 19 では pg_stat_lock(Bertrand Drouvot 氏によるパッチ §)が追加されました。これは、ロックタイプ(locktype)ごとに 1 行で表される、クラスター全体を対象とした累積のロック統計情報です。locktype という名称は少し紛らわしいかもしれません。これはロックモード(ACCESS EXCLUSIVE や ROW SHARE など)ではなく、ロック対象のオブジェクト種別(何がロックされていたか)を表しており、次の 12 種類があります: relation、transactionid、tuple、extend、page、object、advisory、virtualxid、spectoken、applytransaction、frozenid、userlock。

このビュー自体は、新しい関数 pg_stat_get_lock() の薄いラッパーです。

新しく導入された pg_stat_get_lock() 関数にはドキュメントページがありません。他のほとんどの pg_stat_get_* 関数と同様に、ビューの基盤としてのみ存在するためですが、直接クエリを実行することも可能です。

表 1: pg_stat_lock ビュー

カラム型説明
locktypetextロック可能なオブジェクトのタイプ。詳細は pg_locks を参照してください。
waitsbigint競合するロックが原因で、このタイプのロックが待機を余儀なくされた回数。deadlock_timeout を超えて待機した後に正常にロックを取得できた場合のみ加算されます。
wait_timedouble precisionこのタイプのロックの待機に費やされた合計時間(ミリ秒)。deadlock_timeout を超えて待機した後に正常にロックを取得できた場合のみ加算されます。
fastpath_exceededbigintファストパスのスロット上限を超えたために、ファストパス経由で取得できなかったこのタイプのロックの回数。max_locks_per_transaction を増やすことで、この数値を抑えられます。
stats_resettimestamp with time zoneこれらの統計情報が最後にリセットされた日時。

重要な注意点が 1 つあります。waits と wait_time は、deadlock_timeout(デフォルトは 1 秒)を超えて待機した後に「正常に取得できた」ロックのみをカウントします。そのため、pg_stat_lock はすべてのロック待機をカウントするカウンターではありません。

実際にクエリを実行してみましょう。ロック競合を再現するために 2 つの psql セッションを用意しました。最初のセッションでデモ用テーブルのロックを取得し、トランザクションを開いたまま保持します:

-- session 1
BEGIN;
LOCK TABLE demo;
SELECT pg_sleep(2.5);  -- hold the lock for 2.5 seconds
COMMIT;

2 つ目のセッションで同じテーブルをロックしようとしますが、(セッション 1 がコミットするまで)ロックを取得できません:

-- session 2
LOCK TABLE demo;   -- waits here until session 1 commits

2.5 秒後、セッション 1 がコミットし、セッション 2 はようやくロックを取得します。この待機はデフォルトの deadlock_timeout である 1 秒を超えて継続し、最終的に取得に成功したため、カウント対象になります。これを確認するために、ビューにクエリを実行してみましょう:

デモを始める前に pg_stat_reset_shared('lock') でロック統計情報をリセットしたため、以下の数値はこのシナリオのみに由来するものです。

pg_stat_reset_shared() は、'wal'、'io'、'bgwriter' などの指定したターゲットに関するクラスター全体の統計情報をリセットします。'lock' ターゲットは PostgreSQL 19 で新設され、pg_stat_lock ビューと共に追加されました。

SELECT waits, wait_time
FROM pg_stat_lock
WHERE locktype = 'relation';
waits | wait_time
-------+-----------
    1 |  2201.783
(1 row)

この結果は、セッション 2 がロックを取得するまでに 2.2 秒(セッション 1 がテーブルを占有していた 2.5 秒のうち)待機しなければならなかったことを示しています。0.3 秒の差は、セッション 1 がロックを取得してからセッション 2 がロックを要求するまでのわずかなタイムラグによるものです。

本番システムでは単一の待機だけを追跡することはないため、待機回数と合わせてロックタイプを問い合わせることになります:

SELECT locktype, waits, wait_time,
      round(wait_time::numeric / NULLIF(waits, 0), 1) AS avg_wait_ms
FROM pg_stat_lock
WHERE waits > 0
ORDER BY wait_time DESC;
locktype | waits | wait_time | avg_wait_ms
----------+-------+-----------+-------------
relation |     1 |  2201.783 |      2201.8
(1 row)

クエリの解説

pg_stat_lock ビューは常に全 12 種類のロックタイプを返します。WHERE waits > 0 で競合が記録されていないものを除外しています。その後、合計待機時間で並べ替え、ロックタイプごとの平均待機時間を算出しています。合計待機時間が長い場合は頻繁に競合が発生しているロックタイプを示唆し、平均値が高い場合は発生回数は少ないもののより深刻な停滞が生じていることを示します。

fastpath_exceeded

pg_stat_lock ビューの fastpath_exceeded カウンターは掘り下げる価値があると考え、独立したセクションを設けました 🙂 速度を向上させるため、各バックエンドは競合がめったに発生しない最も一般的なロック向けに、小規模なファストパススロットのセットを保持しています。クエリが必要とするロック数がそのスロットの保持量を超えると、超過分はより低速な共有ロックテーブルへとフォールバックし、このカウンターが加算されます(deadlock_timeout のしきい値判定はなく、超過のたびに毎回カウントされます)。

fastpath_exceeded は、単一のクエリが多数のリレーションをロックする必要があるパーティション多用のワークロードにおいて、特に有用な指標となり得ます。

パーティションを多用するワークロードで fastpath_exceeded カウンターが増加している場合は、max_locks_per_transaction を引き上げるべき直接のサインです。PostgreSQL 18 以降、ファストパススロットの数はこのパラメータに基づいて決定されます(それ以前は 16 に固定されていました。Christophe Pettus 氏による当時の仕様に関する素晴らしい解説記事があります)。PostgreSQL 19 では、デフォルトの max_locks_per_transaction が 64 から 128 へと 2 倍に引き上げられました(Heikki Linnakangas 氏 §)。

デモをわかりやすくするために、ファストパススロットの容量を超えるパーティション数を持つパーティションテーブルを作成してみましょう。新しいデフォルト値である 128 を上回る 140 パーティションを選択しました。(Postgres 自身のリグレッションテストでも同様の手法が用いられており、max_locks_per_transaction + 10 個のパーティションが作成されます。)

CREATE TABLE part_demo (id int) PARTITION BY RANGE (id);

DO $$
BEGIN
  FOR i IN 1..140 LOOP
    EXECUTE format(
      'CREATE TABLE part_demo_%s PARTITION OF part_demo
      FOR VALUES FROM (%s) TO (%s)',
      i, (i-1)*1000, i*1000);
  END LOOP;
END $$;

正確に計測するためにカウンターを再度リセットし、テーブルを 1 回スキャンします。単純な SELECT count(*) でも、親テーブルとすべてのパーティションをロックする必要があります:

SELECT pg_stat_reset_shared('lock');
SELECT count(*) FROM part_demo;

ここで、ビューにクエリを実行してみます:

SELECT locktype, fastpath_exceeded
FROM pg_stat_lock
WHERE fastpath_exceeded > 0;
locktype | fastpath_exceeded
----------+-------------------
relation |               422
(1 row)

カウントが 140 を超えているのは、Postgres が上限を超えたロック取得の試行をすべてカウントしており、プランニング時と実行時の両方でパーティションがロックされる可能性があるためです。具体的な数値は実行ごとに変動することがありますが、重要なのはゼロではないという点であり、このワークロードがファストパスからあふれていることを示しています。

pg_stat_recovery {#pg_stat_recovery}

スタンバイのヘルスチェックを構築したことがある方なら、スタンバイの状態を確認するために pg_is_in_recovery()、pg_last_wal_replay_lsn()、pg_last_xact_replay_timestamp()、pg_get_wal_replay_pause_state() の一部またはすべてを使ったことがあるはずです。これらの関数はそれぞれ独自のロック下で、わずかに異なるタイミングで共有リカバリ状態を読み取ります。私たち DBA が以前よく行っていたように、これらを 1 つのビューにまとめたとしても、呼び出しの合間にもリプレイが進み続けるため、それぞれの値の間で整合性が保たれている保証はありません。

pg_stat_recovery(Xuneng Zhou 氏 §、加藤真也氏による修正 §)は、これらの情報を単一のアトミックなスナップショットとして読み出し、1 行に集約することで、すべてのフィールドの整合性を担保します。

表 2: pg_stat_recovery ビュー

カラム型説明
promote_triggeredbooleanプロモーションがトリガーされた場合は true。
last_replayed_read_lsnpg_lsn正常にリプレイされた直近の WAL レコードの開始ログ位置(WAL location)。
last_replayed_end_lsnpg_lsn正常にリプレイされた直近の WAL レコードの終了ログ位置(終了位置 + 1)。
last_replayed_tliinteger正常にリプレイされた直近の WAL レコードのタイムライン。
replay_end_lsnpg_lsn現在リプレイ中のレコードのログ位置(終了位置 + 1)。アクティブにリプレイされているレコードがない場合は last_replayed_end_lsn と等しくなります。
replay_end_tliinteger現在リプレイ中の WAL レコードのタイムライン。アクティブにリプレイされているレコードがない場合は last_replayed_tli と等しくなります。
recovery_last_xact_timetimestamptzリカバリ中にリプレイされた直近のトランザクションコミットまたはアボートレコードのタイムスタンプ。これはプライマリ上でそのトランザクションのコミットまたはアボート WAL レコードが生成された時刻です。
current_chunk_start_timetimestamptzストリーミングレプリケーションから受信した最新の WAL チャンクにリプレイが追いついたことを startup プロセスが検知した時刻。リカバリ競合のタイミングやリプレイ/適用ラグの診断に使用されます。ストリーミング WAL がまだ受信されていないか、時刻が利用できない場合は NULL になります。
pause_statetextリカバリの一時停止状態。取り得る値: not paused、pause requested、paused。
SELECT last_replayed_end_lsn, last_replayed_tli,
      recovery_last_xact_time, pause_state, promote_triggered
FROM pg_stat_recovery;
last_replayed_end_lsn | last_replayed_tli |    recovery_last_xact_time    | pause_state | promote_triggered
-----------------------+-------------------+-------------------------------+-------------+-------------------
0/03001E20            |                 1 | 2026-08-31 15:43:27.762621+02 | not paused  | f
(1 row)

💡

pg_stat_recovery ビューは、これまで SQL インターフェースが存在しなかった情報も公開します。直近にリプレイされたレコードの開始 LSN、リプレイタイムライン、現在リプレイ中のレコードの終了位置、およびプロモーションがトリガーされたかどうかです。従来、SQL では pg_is_in_recovery() が false を返すことでプロモーションが完了したことしか把握できず、プロモーションがすでに進行中であることは検知できませんでした。

実践における注意点をいくつか挙げます:

  • このビューはプライマリ上では行を返さないため、スタンバイ上でクエリを実行する必要があります。
  • データを閲覧するには pg_read_all_stats 権限が必要です。
  • 従来の関数も引き続き利用可能であり、本機能は純粋な追加要素です。

HA(高可用性)ツールをメンテナンスしている方にとって、今回はアップデートを行う良い機会です。数秒ごとにヘルスチェックを実行している場合、複数のクエリを実行する代わりに 1 つのクエリでリカバリ状態の整合したビューを取得できることは、地味ながらも嬉しい改善です。

pg_stat_autovacuum_scores {#pg_stat_autovacuum_scores}

このセクションでは、autovacuum の新しい動作と、それを監視するためのビュー(pg_stat_autovacuum_scores)の 2 つの変更点を取り上げます。

まず、動作の変更についてです。PostgreSQL 19 では、autovacuum がどのテーブルから優先して処理するかを決定する方法が変わりました。PostgreSQL 19 より前では、autovacuum は pg_class 内で見つかった順序でテーブルを処理していました。今回の変更により、各ワーカーはテーブルごとに、XID 年齢、multixact 年齢、不要タプル(dead tuples)、挿入、analyze の鮮度といった autovacuum の各しきい値にどれだけ近づいているか(あるいはどれだけ超過しているか)に基づいてスコアを算出するようになりました。最も高いスコアを持つテーブルが優先されるため、最も対処が必要なテーブルから順に処理されます。

5 つの新しい autovacuum_*_score_weight パラメータ(すべてデフォルトは 1.0)により、各要素の重み付けを設定できます。これらをすべて 0.0 に設定すると、PostgreSQL 19 より前の順序に戻ります。Nathan Bossart 氏のコミットでは、これを「よりスマートな autovacuum ワーカーに向けた第一歩」と呼んでいます。

2 つ目の変更点は、これらの統計情報を可視化する機能です。pg_stat_autovacuum_scores(Sami Imseih 氏 §)は、現在のデータベース内のテーブルごと(TOAST テーブルやシステムカタログを含む)にこれらのスコアを公開し、autovacuum がどのテーブルを優先しているかを明らかにします。

表 3: pg_stat_autovacuum_scores ビュー

カラム型説明
relidoidテーブルの OID。
schemanamenameテーブルが属するスキーマの名前。
relnamenameテーブルの名前。
scoredouble precisionすべての構成要素スコアの中の最大値。autovacuum が処理対象テーブルのリストをソートするために使用する値です。
xid_scoredouble precisionトランザクション ID 年齢の構成要素スコア。autovacuum_freeze_score_weight 以上のスコアは、トランザクション ID の周回防止のために autovacuum がテーブルを vacuum することを示します。
mxid_scoredouble precisionマルチザクト ID 年齢の構成要素スコア。autovacuum_multixact_freeze_score_weight 以上のスコアは、マルチザクト ID の周回防止のために autovacuum がテーブルを vacuum することを示します。
vacuum_scoredouble precisionvacuum の構成要素スコア。autovacuum_vacuum_score_weight 以上のスコアは、autovacuum がテーブルを vacuum することを示します(autovacuum が無効化されている場合を除く)。
vacuum_insert_scoredouble precision挿入に対する vacuum の構成要素スコア。autovacuum_vacuum_insert_score_weight 以上のスコアは、autovacuum がテーブルを vacuum することを示します(autovacuum が無効化されている場合を除く)。
analyze_scoredouble precisionanalyze の構成要素スコア。autovacuum_analyze_score_weight 以上のスコアは、autovacuum がテーブルを analyze することを示します(autovacuum が無効化されている場合を除く)。
do_vacuumbooleanautovacuum がテーブルを vacuum するかどうか。各要素のスコアが vacuum を行う基準を満たしていても、autovacuum が無効になっている場合は false になることがあります。
do_analyzebooleanautovacuum がテーブルを analyze するかどうか。各要素のスコアが analyze を行う基準を満たしていても、autovacuum が無効になっている場合は false になることがあります。
for_wraparoundboolean周回防止のために autovacuum がテーブルを vacuum するかどうか。

動作を確認するために、いくつかクエリを実行してみましょう。1,000 行のテーブルを作成して analyze を実行し、400 行を削除しました:

CREATE TABLE av_demo AS
  SELECT g AS id, md5(g::text) AS payload FROM generate_series(1, 1000) g;
ANALYZE av_demo;
DELETE FROM av_demo WHERE id <= 400;

次に、pg_stat_autovacuum_scores ビューに対して、autovacuum がこのテーブルをどのように評価しているかを問い合わせます:

SELECT relname, round(score::numeric,2) AS score,
      do_vacuum, do_analyze, for_wraparound
FROM pg_stat_autovacuum_scores
WHERE schemaname = 'public' AND (do_vacuum OR do_analyze)
ORDER BY score DESC;
relname | score | do_vacuum | do_analyze | for_wraparound
---------+-------+-----------+------------+----------------
av_demo |  2.67 | t         | t          | f
(1 row)

2.67 という数値はどこから来たのでしょうか? autovacuum はテーブルの行数から算出されるしきい値を用いて実行タイミングを判断します。analyze の場合、デフォルトは 50 + 0.1 × 行数となるため、この 1,000 行のテーブルでは 150 です。今回は 400 行を変更したため、analyze スコアは 400 / 150 = 2.67 になります。

削除処理によって vacuum のしきい値も超えています: 50 + 0.2 × 1000 = 250 となり、400 / 250 = 1.60 になります。総合スコアは各要素の中で最も高い値が採用されるため、av_demo のスコアは 2.67 となり、vacuum と analyze の両方の対象となります。

for_wraparound は行の変更数ではなく、まったく別の計算、つまり年齢に基づいています。具体的には、テーブルの最も古い未フリーズのトランザクション ID が、autovacuum_freeze_max_age(デフォルトは 2 億トランザクション)に向けてどの程度進んでいるかを示します。新しく作成したテーブルはそのしきい値から遠く離れているため、false になります。

pg_stat_autovacuum_scores は現時点の統計情報からスコアを計算しますが、autovacuum ワーカーは起動するたびにテーブルのスコアリングを行います。この 2 つのタイミングには差が生じる可能性があるため、このビューは autovacuum が優先する対象を把握するための確度の高い目安として扱い、絶対的な保証ではない点にご留意ください。

pg_dsm_registry_allocations {#pg_dsm_registry_allocations}

規模は小さめですが便利な機能を紹介します。拡張機能による共有メモリの割り当てにおいて DSM レジストリの利用が増えており、共有メモリを確保するためだけに shared_preload_libraries を設定(および再起動)する必要がなくなってきています。しかしこれまで、そのメモリは SQL から確認できませんでした。

DSM レジストリとは?

DSM は dynamic shared memory(動的共有メモリ)の略称です。サーバー起動時に一度だけ割り当てられるメインの共有メモリエリアとは異なり、実行時に作成される共有メモリを指します(そのため従来は、共有状態を必要とする拡張機能には shared_preload_libraries と再起動が必須でした)。PostgreSQL 17 で追加された DSM レジストリ(Nathan Bossart 氏 §)により、バックエンドは名前を指定してこれらの共有メモリセグメントを作成、検出、アタッチできるようになりました。これにより、拡張機能は再起動なしで、単純な CREATE EXTENSION だけで共有状態を扱えるようになります。詳細はドキュメントの Requesting Shared Memory After Startup を参照してください。

この新しい pg_dsm_registry_allocations ビュー(Florents Tselai 氏 §、Nathan Bossart 氏による機能拡張 §)は、各レジストリエントリの名前、タイプ(segment、area、hash)、およびサイズを一覧表示します。サイズが NULL の場合は、そのエントリの初期化に失敗したことを意味します。

表 4: pg_dsm_registry_allocations ビュー

カラム型説明
nametextDSM レジストリ内の割り当て名。
typetext割り当てのタイプ。取り得る値は segment、area、hash であり、それぞれ動的共有メモリセグメント、エリア、ハッシュテーブルに対応します。
sizeint8割り当てのサイズ(バイト単位)。初期化に失敗したエントリの場合は NULL。

まとめ {#outro}

最後までお読みいただきありがとうございました! 新しいシステムビューの登場を、私と同じように楽しみにしていただければ幸いです。PostgreSQL 19 のリリースが待ち遠しいですね。皆さんの今後のスムーズなアップグレードを応援しています!

10 月の PostgreSQL Conference Europe では、PostgreSQL 19 のオブザーバビリティについて講演する予定であり、本記事で取り上げたトピックもいくつか登場します。バレンシアにいらっしゃる予定の方は、ぜひ気軽にお声がけください! 👋

今すぐ ClickHouse Managed Postgres を始める

自社のデータで ClickHouse Managed Postgres の動作を試してみませんか?ClickHouse Cloud は数分で利用開始でき、300 ドル分の無料クレジットも受け取れます。

サインアップ

この記事をシェア

  • Y Combinator icon
  • X icon
  • Bluesky icon
  • Facebook icon
  • LinkedIn icon

Subscribe to our newsletter

Stay informed on feature releases, product roadmap, support, and cloud offerings!

Follow us

XBlueskySlackGithubTelegramMeetupRSS