PostgreSQL 性能問題の調査に役立つパラメータ設定と拡張

PostgreSQLサポートでは「SELECTが突然遅くなった」「データベースが全体的に遅い時間帯がある」「バッチの実行時間が5分から2時間になった」などの問い合わせをいただきます。
その際、調査をするための情報として、現在の設定やログ、SQLが遅延した時に実行時間とSQL文をログに出すとか実行計画もあわせて出すとかをしておけば、遅延が発生した時や通常とは異なる状況の時に、通常とは何が違うのか、遅延時の実行計画がどうだったのかなどを調べることができます。

そこで、本記事では性能問題発生時の調査に必要な情報を取得するための設定や、性能遅延時の分析に有用な拡張を紹介します。

パラメータ設定の対象メジャーバージョンはPostgreSQL 10以上としています。なお、検証例で使用しているPostgreSQLは PostgreSQL 18.6、OSはRocky Linux 9.5です。

OSについて

現在、PostgreSQLを使う時のOSの選択肢としてよくLinuxにするかWindowsにするかの選択があります。
新規構築でOS選択の自由度がある場合は、Linuxを推奨します。

主な理由は下記の三つです。

  • 同一スペックのサーバであればLinuxのほうが性能がよい
  • Linuxでは利用可能な拡張や周辺ツールの選択肢がWindowsより豊富である(今回紹介するpg_statsinfoもWindowsでは非対応)
  • トラブル発生時の調査がLinuxの方が容易/Windowsでは困難なことが多い

Linuxでは性能面・運用面のメリットが多く、利用可能な拡張も豊富であるため、トラブル調査も進めやすくなります。

PostgreSQLログ関連パラメータ

PostgreSQLログに関してお勧めのパラメータ設定を紹介します。

ログ出力先

ログの出力先(log_destination)は、stderrがお勧めです。syslog を使う必要がある場合でも、stderrも含めて’stderr, syslog’に設定しましょう。syslog のみ設定するのは絶対に避けましょう。
stderrを含める時は、logging_collectorもonにしておく必要があります。

log_destination = 'stderr'
logging_collector = on

stderr をお勧めする理由は、archive_commandパラメータで設定したcpコマンドが実行される際の標準エラー出力もPostgreSQLログに含まれるからです。例えばコピー先のディスクの空き容量が無くcpコマンドが失敗した時のエラーメッセージが該当します。syslog の場合は、このメッセージはsyslogに出力されずに、log_destinationで設定されたファイルに出力されてしまいます。

また、syslogでは高負荷時などにPostgreSQLからのメッセージを取りこぼす可能性があるのもsyslogを避ける理由です。PostgreSQLマニュアルにも「syslogは、もし書き込みができなかったときメッセージの廃棄を選びます。」との記述があります。またsyslog側の機能としてレート制限があり、制限値以上のメッセージが到着した場合に"NNN messages lost due to rate-limiting"というメッセージでNNN行失われたことが伝えられます。高負荷時にこそPostgreSQLが出力するログを確認したいのに、syslogではメッセージが大量に出力される高負荷時にsyslog側でメッセージが省略されることが多々ありますので、syslogを使う必要がある場合は必ずstderrでもログを取得するようにしましょう。

チェックポイントの実行と自動VACUUM/ANALYZEの実行

PostgreSQLなどのデータベースでは一定間隔ごとにチェックポイントが実行されます。チェックポイントはshared_buffersで設定された共有バッファ上で変更されたがファイルシステム上のデータベースファイルにはまだ書き込まれていない、いわゆるダーティーバッファをすべてデータベースファイルに書き出すことで、チェックポイント終了時点のすべてのデータベースファイルで一貫性が取れている状態にすることが目的の処理です。
ダーティーバッファをすべてファイルに書き出す処理ですので、Disk I/Oを使用します。SQL実行などのクライアント側の処理への影響は程度の差はあれあります。SQL実行で何らかの影響を受けたかどうかの調査で、そのタイミングでチェックポイントが実行されていたかどうかを確認するために、チェックポイント実行(log_checkpoints)をPostgreSQLログに出力することは有用です。

log_checkpoints = on
# PostgreSQL 14までデフォルトoff、PostgreSQL 15からデフォルトon

自動VACUUMや自動ANALYZEの実行もチェックポイントと同様に、Disk I/Oを使用します。PostgreSQLログに出力する目的はチェックポイントと同様に、SQL実行時に自動VACUUMや自動ANALYZEが実行されていたかどうかを調査する観点もありますが、もう一つの観点としてSQLの実行計画が突然変わった時に、その直前で実行計画の策定に影響するオプティマイザ統計情報を更新する自動ANALYZEが実行されていたかどうかを確認する観点もあります。

log_autovacuum_min_duration = 0
# PostgreSQL 14までデフォルト-1(無効)、PostgreSQL 15からデフォルト10min(10分)

ロック待ち

突然SQLの実行が遅くなった時に、ロック待ちをしていたかどうかを確認したい場合があります。デフォルトではoff(無効)となっているlog_lock_waitsをon(有効)にしておくと、deadlock_timeoutで設定した時間(デフォルト1秒)ロック待ちをするとPostgreSQLログに出力されます。ただしdeadlock_timeoutの設定値が少なすぎるとマニュアルにも説明があるようにチェックの負荷がかかるため、本当にデッドロックが発生した場合に検知が遅くなるデメリットはありますが、チェックの負荷が軽減されるメリットもあるのでデフォルト(1秒)よりも長い時間、例えば5秒とか10秒とかを設定しましょう。下記例では10秒を設定しています。

log_lock_waits = on # デフォルト off
deadlock_timeout = 10s # デフォルト 1s

遅延SQL

log_min_duration_statement(デフォルト:-1(無効))に設定した時間以上実行にかかったSQLはPostgreSQLログに出力されます。
下記設定例では、10秒を設定していますので、10秒以上かかったSQLはPostgreSQLログに出力されます。

log_min_duration_statement = 10s # デフォルト-1(無効)

上記の設定をすべて実行した場合

上記の設定をすべて実行した場合、SQLの遅延が発生するとSQL文が実行時間と共にPostgreSQLログに出力されます。例えばSQL Aが11秒かかったことが分かり、その際、一緒にロック待ちのログが出ていれば、ロック待ちの影響で遅延が発生したことがわかります。
また、遅延が発生したタイミングで自動VACUUMもしくはチェックポイントが実行されていたとか、遅延が発生する直前に自動ANALYZEが実行されていたかもわかりますので、同じタイミングで実行されていた自動VACUUMやチェックポイントの可能性があるかないか、また自動ANALYZEにより実行計画を作成する元となるオプティマイザ統計情報が変更されて実行計画が変更された可能性があるかないかが判断可能となります。
しかし、SQLが遅延した場合に実際に実行計画が変化したのかどうかまではわかりません。SQLが遅延した時に実行計画の情報をPostgreSQLログに出力するauto_explainモジュールについて後で紹介します。

メモリ設定関連パラメータ

共有バッファ

PostgreSQLマニュアルには1GB以上物理メモリがあるサーバでのshared_buffersの妥当な初期値は物理メモリの25%という説明が記載されていますので、共有バッファはサーバの物理メモリの4分の1に設定しましょう。例えば、物理メモリが64GBならば16GBを設定します。下記設定例は、物理メモリが64GBの場合です。

shared_buffers = 16GB

サーバのメモリがどのように使用されるか、使用される比率について説明します。
PostgreSQLはOSのファイルシステム上にデータベースのファイルを作成しますので、PostgreSQLの共有メモリで全体の25%、OS側でファイルのキャッシュとして全体の25%、合わせると物理メモリ全体の半分(50%)がPostgreSQLのデータベースファイルで使われ、残りの半分(50%)でOS自体が使用するメモリとOSが起動するプロセス、そしてPostgreSQLのプロセスがメモリを使用します。

ワークメモリ

SQLを実行する際にソートやハッシュ検索などの処理ではPostgreSQLプロセスがwork_memで設定された値を上限としてメモリを使用しようとしますが、設定された値では足りない場合にディスクを使って読み書きをして処理をします。メモリ上の読み書きに比べてディスク上の読み書きは桁違いに遅いので、SQLの実行に影響しますので、サーバのメモリが不足しない程度にwork_memに大きな値を設定します。下記の例では、16MBを設定しています。

work_mem = 16MB

work_memで設定した値でメモリ上で処理が済んでいるかどうかを確認するには、ディスク上の処理が発生した場合にPostgreSQLログに出力するlog_temp_filesを0に設定して、SQLを実行します。ディスクを使用したら、ファイル削除時にPostgreSQLログに使用した一時ファイルが出力されます。

log_temp_files = 0 # デフォルト -1(無効)

postgresql.confで上記の設定をすると、ディスク上の処理がすべてPostgreSQLログに出てしまいますので、一時的にlog_temp_files=0に変更してpg_ctl reloadで設定を反映、ディスク上の使用有無をチェックするSQL文を実行、log_temp_files=-1に戻してpg_ctl reloadで設定を反映することで、調査期間の間だけPostgreSQLログに出力することが可能です。

また、特定のSQLを取り出してpsqlで実行可能な場合は、下記の例のようにセッション単位でlog_temp_files=0に設定してSQLを実行することも可能です。psqlの画面上に表示するためにclient_min_messagesも設定することで、LOG: temporary file: path "base/pgsql_tmp/pgsql_tmp13483.0", size 10731520が表示されて一時ファイルが使用されていることが分かります。

db1=# SET log_temp_files = 0;
SET
db1=# SHOW log_temp_files;
log_temp_files
----------------
0
(1 row)

db1=# SET client_min_messages = log;
SET
db1=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pgbench_accounts ORDER BY abalance DESC;
LOG: temporary file: path "base/pgsql_tmp/pgsql_tmp13483.0", size 10731520
QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------
----
Sort (cost=16476.32..16726.32 rows=100000 width=97) (actual time=65.871..80.323 rows=100000.00 loops=1)
Sort Key: abalance DESC
Sort Method: external merge Disk: 10480kB
Buffers: shared hit=1704, temp read=1310 written=1313
-> Seq Scan on pgbench_accounts (cost=0.00..2701.00 rows=100000 width=97) (actual time=0.022..14.887 rows=100000.00 loops
=1)
Buffers: shared hit=1701
Planning:
Buffers: shared hit=73
Planning Time: 0.571 ms
Execution Time: 89.080 ms
(10 rows)

上記のSQLで一時ファイルのDisk I/Oを使用しないwork_memの値を調べるには、実行計画でDiskが出なくなるwork_memの値を、セッション単位で一時的に必要最小限の大きな値に設定して、実行計画を表示させます。下記例では、16MBに設定した時に、external merge Diskがquicksort Memoryに変わりましたので、このSQLの実行直前にセッション単位のSET文でSET work_mem ='16MB';に設定すればメモリ上のソートで性能向上が見込めます。

db1=# SHOW work_mem;
work_mem
----------
4MB
(1 row)

db1=# SET work_mem = '16MB';
SET
db1=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pgbench_accounts ORDER BY abalance DESC;
QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------
----
Sort (cost=11005.82..11255.82 rows=100000 width=97) (actual time=38.479..51.692 rows=100000.00 loops=1)
Sort Key: abalance DESC
Sort Method: quicksort Memory: 14791kB
Buffers: shared hit=1701
-> Seq Scan on pgbench_accounts (cost=0.00..2701.00 rows=100000 width=97) (actual time=0.020..12.699 rows=100000.00 loops
=1)
Buffers: shared hit=1701
Planning Time: 0.084 ms
Execution Time: 56.287 ms
(8 rows)

SQL遅延時の対応について

auto_explain拡張を紹介する前に、SQLが遅延した時にどのような対応を取るのかについて説明します。

オプティマイザ統計情報の最新化と不要領域の整理

SQLの遅延が発生した時にまず実行してほしいのが、SQLの実行計画の基となっているオプティマイザ統計情報の最新化と、SQLの検索対象テーブルの不要領域の整理です。
SQLの検索対象となっているテーブル全て、特に対象テーブルのみに限定する必要が無い場合はデータベースに対してVACUUM (ANALYZE)を実行します。

下記実行例では、どのような実行状況なのかを表示するためにVERBOSEオプションを付けています。

VACUUM (ANALYZE,VERBOSE) 検索対象テーブル;

もしくは、対象データベースに接続した状態で、引数を省略するとデータベース全体に対してVACUUM (ANALYZE)が実行されます。

VACUUM (ANALYZE,VERBOSE);

上記コマンドの実行で、現在テーブルに入っているデータで実行計画を作成するベースとなるオプティマイザ統計情報を最新化するとともに、削除済みだがまだ回収されていない不要領域が回収されます。そのため、実行計画を策定するためのオプティマイザ統計情報がテーブルの最新状況を反映していない乖離した古い状態で、効率の悪い不適切な実行計画によりSQLが遅延していた場合は、統計情報が最新化された後の再実行では遅延が解消されます。

実行計画の取得

上記のVACUUM (ANALYZE)をSQLの対象テーブルすべてに対して実行しても性能が改善しない場合、例えば普段0.5秒程度で実行されているSQLが急に10秒とかかかるようになった時に取得が必要な情報がEXPLAIN (ANALYZE, BUFFERS) 遅延SQL;で出力される実行計画です。

EXPLAIN (ANALYZE, BUFFERS) 遅延SQL;

EXPLAINコマンドは引数のSQLをどのように実行するかの詳細ステップを書いた実行計画を出力するコマンドで、ANALYZEオプションは実際に実行した時間を出力、BUFFERSオプションはファイルアクセス時に共有バッファから読み込んだのかファイルから読み込んだのかの情報とアクセスしたバッファ数を出力します。

実行例を下記に示します。1回目はDB起動直後、2回目は連続して実行しました。2回の違いとして、shared read(Diskからの読み込み)がshared hit(共有バッファからの読み込み)になっていること、一度読み込まれて共有バッファからの読み込みになったので2回目の時間がPlanningもExecutionも短くなっていることが分かります。

$ psql db1
psql (18.6)
Type "help" for help.

db1=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pgbench_accounts;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Seq Scan on pgbench_accounts (cost=0.00..2640.00 rows=100000 width=97) (actual time=2.176..28.884 rows=100000.00 loops=1)
Buffers: shared read=1640
Planning:
Buffers: shared hit=44 read=11
Planning Time: 0.846 ms
Execution Time: 33.923 ms
(6 rows)

db1=# EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM pgbench_accounts;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Seq Scan on pgbench_accounts (cost=0.00..2640.00 rows=100000 width=97) (actual time=0.069..9.271 rows=100000.00 loops=1)
Buffers: shared hit=1640
Planning Time: 0.070 ms
Execution Time: 17.789 ms
(4 rows)

db1=#

上記の実行結果を、通常の速い時に取っておき、遅延が発生した遅い時にも取れば、実行計画そのものが変わったのか実行計画は同一なのか、遅い時はどのステップでどの程度遅くなっているのかの比較ができて、通常の速い時とどう違って遅いのかの理由が分かります。

なお、どの実行計画のどの段階でどの列の結果を出力しているのかのオプションであるVERBOSEオプションもあわせて指定すると、実行計画がどのように動作しているのか分かりやすくなります。下記実行例では、Outputの行がVERBOSEオプションによって追加された情報になります。

db1=# EXPLAIN (ANALYZE, BUFFERS, VERBOSE) SELECT * FROM pgbench_accounts;
QUERY PLAN

------------------------------------------------------------------------------------------------------------------------------
-----
Seq Scan on public.pgbench_accounts (cost=0.00..2640.00 rows=100000 width=97) (actual time=0.018..11.627 rows=100000.00 loop
s=1)
Output: aid, bid, abalance, filler
Buffers: shared hit=1640
Planning:
Buffers: shared hit=60
Planning Time: 1.456 ms
Execution Time: 19.776 ms
(7 rows)

db1=#

拡張(EXTENSION)

PostgreSQLの⼤きな特徴として、様々な機能を追加できる拡張(EXTENSION)があります。
ここでは性能問題の調査や分析に役立つ代表的な拡張を紹介します。

拡張を使用するためには、拡張が入ったパッケージをPostgreSQLインストール時にあらかじめインストールしておき、CREATE EXTENSIONやshared_preload_libraries変更などの設定が必要ですが、そもそも拡張をインストールしていない場合は、拡張を使用する設定をする前に後から追加でインストールが必要となります。
しかし、既にシステムが本番稼働をした状況で、後から拡張をインストールをするのはハードルが高いので、後から使いたい時に使えるよう、PostgreSQLインストール時に一緒にインストールしておくのが望ましいです。

拡張の種類

PostgreSQL本体と一緒に同梱されているcontribモジュールと、contribモジュール以外(サードパーティー)モジュールがあります。

contribモジュールは、PostgreSQL本体と合わせてリリースされていますので、PostgreSQLサーバのインストール時に一緒にインストールしておきましょう。contribモジュールをインストールしておけば、後からcontribモジュールに含まれる拡張を導入することが可能です。contribモジュール以外の拡張は、サードパーティーモジュールと呼ばれ、現在はGitHubサイトで公開されていることが多いです。ダウンロード可能な形式はソースコードがメインで、リリース時点で公開されているRHELのrpmファイルもダウンロード可能な場合もあります。

auto_explain(contrib)

設定した時間以上かかったSQLの実行計画をPostgreSQLログに出力します。今回のこの記事で一番紹介したかったのはこのauto_explain拡張モジュールです。

サポートでよく「SQLが突然遅くなった。」という問い合わせをいただきますが、遅くなった時の情報が何もないため、なぜ遅くなったのかの原因調査が出来ません。問い合わせをいただいた時の返答はいつも「次回遅延が発生している間にEXPLAIN (ANALYZE, BUFFERS) SQL;を実行して実行計画を取得してください。」とお伝えしています。遅延している状況がずっと続いている、または定期的に遅延していてその時間帯が日中の場合はEXPLAIN (ANALYZE, BUFFERS) SQL;を手動で実行して実行結果を取得することは可能ですが、遅延の発生が真夜中であるとか、遅延がいつ発生するのかが不明な場合は人手でEXPLAIN (ANALYZE, BUFFERS) SQL;を実行することは困難です。そのような状況で一番役立つのがこのauto_explainです。

auto_explainの使い方について説明します。postgresql.confに下記の設定を追加して、PostgreSQLを再起動するとauto_explainが有効になって、例えば10秒以上かかるSQLが実行されると自動的にSQL実行時のEXPLAIN(ANALYZE, BUFFERS) SQL;の情報がPostgreSQLログに出力されます。

PostgreSQLログにauto_explainでの実行結果が出力されれば、性能悪化時と通常時の実行計画を比較できます。
下記はauto_explainを使うために必要な設定で、設定追加後PostgreSQLを再起動すると10秒以上かかったSQL文の実行計画がPostgreSQLログに出力されます。

shared_preload_libraries = 'auto_explain'
auto_explain.log_min_duration = 10s
auto_explain.log_analyze = on
auto_explain.log_buffers = on
auto_explain.log_timing = off # ノードごとの実行時間を取得する場合はon

なお、auto_explainの有効化はデータベースサーバ全体だけではなく、セッション単位でも下記の方法で有効化可能です。下記の例は、psqlで接続した後、

db1=# LOAD 'auto_explain';
LOAD
db1=# set auto_explain.log_min_duration = 0;
SET
db1=# set auto_explain.log_analyze = on;
SET
db1=# SELECT COUNT(*) FROM pgbench_accounts;
count
--------
100000
(1 row)

db1=#

上記の実行でのPostgreSQLログ出力は下記となります。

2026-10-06 20:51:58.504 JST [5786] LOG: duration: 7.090 ms plan:
Query Text: SELECT COUNT(*) FROM pgbench_accounts;
Aggregate (cost=2924.00..2924.01 rows=1 width=8) (actual time=7.080..7.081 rows=1.00 loops=1)
-> Seq Scan on pgbench_accounts (cost=0.00..2674.00 rows=100000 width=0) (actual time=0.008..4.326 rows=100000.00 loops=1)

※注:auto_explain.log_analyzeを有効(on)にすると、すべてのSQL実行で性能上のマイナスが起こり得ます。auto_explain.log_timing = offを追加することでノードごとの時間情報が無効になりますが行数の情報は取得でき性能面での損失が改善されますので、本番環境では通常はauto_explain.log_timingを無効にしておくことが望ましいと考えられます。
実際に遅延が発生し、ノードごとの時間情報も必要と判断した場合のみ、性能面でのデメリットを認識した上でauto_explain.log_timing = onに変更し、情報が取得でき次第速やかに無効(off)にする運用がよいでしょう。

auto_explain(F.3. auto_explain — 低速な問い合わせ実行計画のログ)
https://www.sraoss.co.jp/PostgreSQL/Manual/document/18/auto-explain.html

pg_hint_plan(サードパーティー)

なお、今回紹介している性能調査とは異なりますが、auto_explainでPostgreSQLログに出力した実行計画が性能悪化の原因であった場合、SQLに埋め込んだヒント句で強制的に実行計画を変更するpg_hint_planという拡張も紹介しておきます。
オプティマイザ統計情報を最新化しても実行計画の形が変わってしまってSQLの性能が悪化した場合、例えば結合方式が速い時はHash Joinなのに遅延時はNested Loopに変更されてしまったとか、複数テーブルある結合で速い時と遅い時でテーブルを結合する順番が異なるなどの場合に、強制的に速い時の実行計画にあわせるためにSQLにヒント句を埋め込んでヒント句の通りに実行計画を強制変更します。SQLにヒント句を埋め込まない指定も利用可能です。弊社ブログに解説がありますのでご参照ください。

pg_hint_plan (PostgreSQL 実行計画制御ツール)
https://www.sraoss.co.jp/tech-blog/pgsql/pg_hint_plan/

pg_stat_statements(contrib)

pg_stat_statementsは日常的にどのSQLが時間を使っているのかを把握するための拡張です。短い実行時間のSQLでも実行回数が多ければ合計実行時間は大きくなります。pg_stat_statementsはサーバで実行されたすべてのSQLのプラン生成時と実行時の統計情報を記録します。このモジュールはPostgreSQLログへの出力は無く、情報の参照は情報を取得しているデータベースに接続してpg_stat_statementsビューをSELECTする方法となります。

下記はpg_stat_statementsを使うために必要な設定で、設定追加後PostgreSQLを再起動した後、利用するデータベースに接続してCREATE EXTENSION pg_stat_statements;を実行すると、pg_stat_statementsビューが参照可能となります。

shared_preload_libraries = 'pg_stat_statements'

DB再起動後、下記を実行。

db1=# CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION
db1=# \dx
List of installed extensions
Name | Version | Default version | Schema | Description

--------------------+---------+-----------------+------------+----------------------------------------------------------------
--------
pg_stat_statements | 1.12 | 1.12 | public | track planning and execution statistics of all SQL statements e
xecuted
plpgsql | 1.0 | 1.0 | pg_catalog | PL/pgSQL procedural language
(2 rows)

db1=#

PostgreSQLマニュアルの実行例を実行した例。合計実行時間(total_exec_time)の多い順から、SQL、呼び出し回数、合計実行時間、行数、算出したヒット率が表示されています。

db1=# \x
Expanded display is on.
db1=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+-----------------------------------------------------------------------------------------------------
query | UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2
calls | 3000
total_exec_time | 17180.62709200005
rows | 3000
hit_percent | 99.9982951155059245
-[ RECORD 2 ]---+-----------------------------------------------------------------------------------------------------
query | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls | 3000
total_exec_time | 14783.155679999958
rows | 3000
hit_percent | 99.9957805907172996
-[ RECORD 3 ]---+-----------------------------------------------------------------------------------------------------
query | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls | 3000
total_exec_time | 111.09148400000022
rows | 3000
hit_percent | 92.5940860215053763
-[ RECORD 4 ]---+-----------------------------------------------------------------------------------------------------
query | SELECT abalance FROM pgbench_accounts WHERE aid = $1
calls | 3000
total_exec_time | 28.88099300000003
rows | 3000
hit_percent | 100.0000000000000000
-[ RECORD 5 ]---+-----------------------------------------------------------------------------------------------------
query | INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
calls | 3000
total_exec_time | 21.792536000000027
rows | 3000
hit_percent | 100.0000000000000000

db1=#

pg_stat_statements(F.32. pg_stat_statements — SQL文のプラン生成時と実行時の統計情報を記録する)
https://www.sraoss.co.jp/PostgreSQL/Manual/document/18/pgstatstatements.html

性能問題の調査に役立つ情報の継続収集

性能問題の調査では、問題発生時の状況だけでなく、過去との比較も重要です。
そのためには PostgreSQL の統計情報や OS のリソース情報を継続的に収集しておくことが有効です。

収集方法としては、

  • sysstat(sar)
  • 各種監視ツール
  • PostgreSQL向け収集ツール

などがあります。
その中でも PostgreSQL の統計情報と OS リソース情報をまとめて時系列で取得できるツールとしてpg_statsinfo があります。

pg_statsinfo(サードパーティー)

pg_statsinfo は PostgreSQL の統計情報やOSリソース情報を時系列で取得・保存するツールです。PostgreSQL 統計情報やOSリソース情報を時系列で取得するスナップショットを保存するためのリポジトリDBが必要となりますが、pg_statsinfoを使った運用をしていると何か性能問題が発生した時に過去の情報を参照できるのでとても便利です。上で紹介したpg_stat_statementsと、ヒント句を取得できるpg_store_plansと併用することでクエリの統計情報と実行計画も一緒に保存できるため、どのSQLが性能に影響していた可能性があるのか、また実行計画の変化によって性能が悪化した時も後から確認できます。
弊社ブログに解説がありますのでご参照ください。

pg_statsinfo(統計・OSリソース情報時系列取得保存ツール)
https://www.sraoss.co.jp/tech-blog/pgsql/pg_statsinfo/

pg_statsinfoは現在も有用なツールですが、監視製品やクラウド環境の監視機能などを利用して継続収集を行う方法も合わせてご検討ください。

まとめ

PostgreSQLの性能問題は、「遅くなった」という事実だけでは原因を特定できません。重要なのは、問題発生時に何が起きていたのかを後から分析できる情報を残しておくことです。

本記事では、チェックポイント、自動VACUUM、自動ANALYZE、ロック待ち、遅延SQLを記録するためのログ設定と、遅延時の実行計画を自動取得できるauto_explain、調査結果を踏まえて実行計画を制御できるpg_hint_plan、SQL実行統計を蓄積するpg_stat_statements、PostgreSQLの統計情報やOSリソース情報を時系列で取得・保存するpg_statsinfoを紹介しました。

特に、SQL性能問題の原因究明においては、遅延発生時のEXPLAIN (ANALYZE, BUFFERS)の取得が最も重要です。auto_explainを導入しておけば、人が操作できない時間帯に発生した遅延であっても、実行計画を記録できます。遅延時の実行計画が通常時の実行計画と異なる場合は、pg_hint_planにより強制的に通常時の実行計画に合わせることが可能となります。ただし、実行計画変更の根本原因が統計情報やデータ分布の変化にある場合もあり、pg_hint_planは原因調査や恒久対策を行った上で必要に応じて利用します。

PostgreSQLの性能問題への対応は、問題発生後に慌てて調査方法を考えるのではなく、平常時から必要な情報を取得できる状態を作っておくことが重要です。本記事がそのための設定見直しの参考になれば幸いです。