はじめに#
PostgreSQLを本番環境で安定稼働させるためには、適切な監視とメンテナンスが不可欠です。「気づいたらディスク容量が逼迫していた」「クエリが突然遅くなった」「接続数が上限に達してアプリケーションがエラーを返した」といったトラブルは、日常的な監視とメンテナンスを怠ることで発生します。
本記事では、PostgreSQLの健全性を監視し、適切なメンテナンスを実施するために必要な知識と実践的なタスクを解説します。
本記事で扱うトピックは以下の通りです。
- pg_stat_statementsによるクエリ監視
- 接続数・メモリ・ディスク使用量の監視
- VACUUM/ANALYZEの仕組みと実行
- autovacuumのチューニング
- ログ管理と分析
この記事を読むことで、以下のことができるようになります。
- pg_stat_statementsを使って遅いクエリを特定できる
- システムビューを使ってPostgreSQLの状態を監視できる
- VACUUMとANALYZEの役割を理解し、適切に実行できる
- autovacuumのパラメータを調整して最適化できる
- ログを適切に設定・管理できる
前提条件#
- PostgreSQL 14以降がインストールされていること
- psqlまたはGUIツールでデータベースに接続できること
- PostgreSQLの基本操作(SELECT、設定ファイルの編集)ができること
- Linuxの基本的なコマンド操作ができること
pg_stat_statementsによるクエリ監視#
pg_stat_statementsは、PostgreSQLで実行されるすべてのSQL文の計画と実行に関する統計を追跡するための拡張機能です。本番環境でパフォーマンス問題を特定する際に最も重要なツールの一つです。
pg_stat_statementsの有効化#
pg_stat_statementsを使用するには、postgresql.confに設定を追加してサーバーを再起動する必要があります。
1
2
|
# postgresql.confの場所を確認
sudo -u postgres psql -c "SHOW config_file;"
|
postgresql.confに以下の設定を追加します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 共有ライブラリにpg_stat_statementsを追加
shared_preload_libraries = 'pg_stat_statements'
# クエリIDの計算を有効化
compute_query_id = on
# 追跡するステートメントの最大数
pg_stat_statements.max = 10000
# すべてのステートメントを追跡(関数内のクエリも含む)
pg_stat_statements.track = all
# ユーティリティコマンドも追跡
pg_stat_statements.track_utility = on
|
設定後、PostgreSQLを再起動します。
1
|
sudo systemctl restart postgresql
|
対象のデータベースで拡張機能を有効化します。
1
|
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
遅いクエリの特定#
pg_stat_statementsを使って、パフォーマンス問題の原因となっているクエリを特定できます。
1
2
3
4
5
6
7
8
9
10
11
|
-- 総実行時間が長いクエリTOP10
SELECT
substring(query, 1, 100) AS short_query,
calls,
round(total_exec_time::numeric, 2) AS total_time_ms,
round(mean_exec_time::numeric, 2) AS mean_time_ms,
rows,
round((100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0))::numeric, 2) AS hit_ratio
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
|
このクエリでは、以下の情報を取得できます。
| カラム |
説明 |
| calls |
クエリの実行回数 |
| total_time_ms |
総実行時間(ミリ秒) |
| mean_time_ms |
平均実行時間(ミリ秒) |
| rows |
取得または影響を受けた行数 |
| hit_ratio |
バッファキャッシュヒット率 |
実行回数が多いクエリの分析#
頻繁に実行されるクエリは、わずかな改善でも全体のパフォーマンスに大きな影響を与えます。
1
2
3
4
5
6
7
8
9
10
|
-- 実行回数が多いクエリTOP10
SELECT
substring(query, 1, 100) AS short_query,
calls,
round(mean_exec_time::numeric, 2) AS mean_time_ms,
round(total_exec_time::numeric, 2) AS total_time_ms,
rows / nullif(calls, 0) AS avg_rows
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 10;
|
I/O負荷の高いクエリの特定#
ディスクI/Oはデータベースのボトルネックになりやすいポイントです。
1
2
3
4
5
6
7
8
9
10
11
|
-- 共有ブロックの読み取りが多いクエリ
SELECT
substring(query, 1, 100) AS short_query,
calls,
shared_blks_read,
shared_blks_hit,
round((100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0))::numeric, 2) AS hit_ratio
FROM pg_stat_statements
WHERE shared_blks_read > 0
ORDER BY shared_blks_read DESC
LIMIT 10;
|
統計情報のリセット#
定期的に統計情報をリセットして、最新の傾向を把握することが重要です。
1
2
3
4
5
|
-- すべての統計をリセット
SELECT pg_stat_statements_reset();
-- 特定のクエリの統計のみリセット(queryidを指定)
SELECT pg_stat_statements_reset(0, 0, 12345678901234);
|
接続数・メモリ・ディスク使用量の監視#
PostgreSQLの健全性を維持するためには、リソース使用状況を継続的に監視する必要があります。
接続数の監視#
接続数が上限に達すると、新規接続ができなくなりアプリケーションがエラーを返します。
1
2
3
4
5
6
|
-- 現在の接続数と最大接続数
SELECT
(SELECT count(*) FROM pg_stat_activity) AS current_connections,
(SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_connections,
round(100.0 * (SELECT count(*) FROM pg_stat_activity) /
(SELECT setting::int FROM pg_settings WHERE name = 'max_connections'), 2) AS usage_percent;
|
接続状況の詳細を確認します。
1
2
3
4
5
6
7
8
9
10
11
|
-- データベース別の接続数
SELECT
datname,
count(*) AS connections,
count(*) FILTER (WHERE state = 'active') AS active,
count(*) FILTER (WHERE state = 'idle') AS idle,
count(*) FILTER (WHERE state = 'idle in transaction') AS idle_in_transaction
FROM pg_stat_activity
WHERE datname IS NOT NULL
GROUP BY datname
ORDER BY connections DESC;
|
長時間アイドル状態のトランザクションは、ロックやテーブル膨張の原因となります。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-- 1時間以上アイドル状態のトランザクション
SELECT
pid,
usename,
datname,
state,
now() - xact_start AS transaction_duration,
now() - query_start AS query_duration,
substring(query, 1, 100) AS short_query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND xact_start < now() - interval '1 hour'
ORDER BY xact_start;
|
メモリ使用量の監視#
PostgreSQLの主要なメモリ設定を確認します。
1
2
3
4
5
6
7
8
9
10
11
|
-- メモリ関連の設定値
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name IN (
'shared_buffers',
'work_mem',
'maintenance_work_mem',
'effective_cache_size',
'wal_buffers'
)
ORDER BY name;
|
shared_buffersの使用効率を確認するには、pg_buffercache拡張を使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-- pg_buffercache拡張のインストール
CREATE EXTENSION IF NOT EXISTS pg_buffercache;
-- バッファの使用状況
SELECT
c.relname,
pg_size_pretty(count(*) * 8192) AS buffered,
round(100.0 * count(*) / (SELECT count(*) FROM pg_buffercache), 2) AS buffer_percent,
round(100.0 * count(*) FILTER (WHERE b.usagecount > 0) / count(*), 2) AS usage_percent
FROM pg_buffercache b
JOIN pg_class c ON b.relfilenode = pg_relation_filenode(c.oid)
WHERE b.reldatabase = (SELECT oid FROM pg_database WHERE datname = current_database())
GROUP BY c.relname
ORDER BY count(*) DESC
LIMIT 20;
|
ディスク使用量の監視#
データベースとテーブルのサイズを確認します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-- データベースサイズ
SELECT
datname,
pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database
ORDER BY pg_database_size(datname) DESC;
-- テーブルサイズ(TOP20)
SELECT
schemaname,
relname AS table_name,
pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
pg_size_pretty(pg_relation_size(relid)) AS table_size,
pg_size_pretty(pg_indexes_size(relid)) AS indexes_size
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 20;
|
テーブルの膨張(Bloat)を確認します。膨張が進むとパフォーマンスが低下し、ディスク容量を圧迫します。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-- テーブル膨張の確認
SELECT
schemaname,
relname,
n_live_tup,
n_dead_tup,
round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_ratio,
last_vacuum,
last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC
LIMIT 20;
|
監視ダッシュボードの構築#
これらの監視クエリを定期的に実行し、結果を記録することで傾向を把握できます。以下は、主要なメトリクスをまとめて取得するクエリです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
-- PostgreSQL健全性サマリー
SELECT
'Connections' AS metric,
(SELECT count(*) FROM pg_stat_activity)::text || ' / ' ||
(SELECT setting FROM pg_settings WHERE name = 'max_connections') AS value
UNION ALL
SELECT
'Database Size',
pg_size_pretty(pg_database_size(current_database()))
UNION ALL
SELECT
'Dead Tuples',
sum(n_dead_tup)::text
FROM pg_stat_user_tables
UNION ALL
SELECT
'Cache Hit Ratio',
round(100.0 * sum(blks_hit) / nullif(sum(blks_hit + blks_read), 0), 2)::text || '%'
FROM pg_stat_database
WHERE datname = current_database();
|
VACUUM/ANALYZEの仕組みと実行#
VACUUMとANALYZEは、PostgreSQLの健全性を維持するために不可欠なメンテナンス操作です。
VACUUMの目的と仕組み#
PostgreSQLでは、UPDATEやDELETEを実行しても、古い行バージョンはすぐには削除されません。これはMVCC(多版型同時実行制御)の仕組みによるものです。VACUUMは以下の役割を果たします。
flowchart LR
A[VACUUM] --> B[不要行の回収]
A --> C[可視性マップ更新]
A --> D[XID凍結]
B --> B1[ディスク領域の再利用]
C --> C1[Index Only Scan高速化]
D --> D1[トランザクションID周回防止]
| 役割 |
説明 |
| 不要行の回収 |
削除・更新された古い行バージョンを回収し、領域を再利用可能にする |
| 可視性マップ更新 |
すべてのトランザクションから可視な行のみを含むページを記録し、Index Only Scanを高速化 |
| XID凍結 |
トランザクションID周回問題を防止するため、古いXIDを凍結 |
| 統計情報更新 |
VACUUM ANALYZEとして実行した場合、クエリプランナー用の統計を更新 |
VACUUMの種類#
1
2
3
4
5
6
7
8
9
10
11
12
|
-- 通常のVACUUM(他の操作と並行実行可能)
VACUUM tablename;
-- VACUUM ANALYZE(統計情報も更新)
VACUUM ANALYZE tablename;
-- VACUUM VERBOSE(詳細出力)
VACUUM VERBOSE tablename;
-- VACUUM FULL(テーブルを完全に書き換え、排他ロックが必要)
-- 注意:通常運用では避ける
VACUUM FULL tablename;
|
VACUUM FULLは、テーブルを完全に書き換えてディスク領域をOSに返却しますが、処理中はテーブルに排他ロックがかかり、他の操作がブロックされます。通常の運用では標準的なVACUUMを使用し、VACUUM FULLは必要な場合のみメンテナンスウィンドウで実行してください。
VACUUMの実行状況確認#
現在実行中のVACUUMを確認します。
1
2
3
4
5
6
7
8
9
10
11
12
|
-- VACUUM進捗状況
SELECT
p.pid,
p.datname,
p.relid::regclass AS table_name,
p.phase,
p.heap_blks_total,
p.heap_blks_scanned,
round(100.0 * p.heap_blks_scanned / nullif(p.heap_blks_total, 0), 2) AS scan_percent,
p.heap_blks_vacuumed,
round(100.0 * p.heap_blks_vacuumed / nullif(p.heap_blks_total, 0), 2) AS vacuum_percent
FROM pg_stat_progress_vacuum p;
|
ANALYZEの重要性#
ANALYZEは、テーブルのデータ分布に関する統計情報を収集し、クエリプランナーが最適な実行計画を選択できるようにします。
1
2
3
4
5
6
7
8
9
10
11
|
-- 特定のテーブルを分析
ANALYZE tablename;
-- 特定のカラムのみ分析
ANALYZE tablename (column1, column2);
-- データベース全体を分析
ANALYZE;
-- 詳細出力付き
ANALYZE VERBOSE tablename;
|
統計情報が古いと、クエリプランナーが非効率な実行計画を選択し、パフォーマンスが低下します。
1
2
3
4
5
6
7
8
9
10
|
-- 最後にANALYZEが実行された日時
SELECT
schemaname,
relname,
last_analyze,
last_autoanalyze,
n_mod_since_analyze
FROM pg_stat_user_tables
ORDER BY n_mod_since_analyze DESC
LIMIT 20;
|
手動VACUUMが必要なケース#
autovacuumが有効な環境でも、以下のケースでは手動VACUUMが必要になることがあります。
1
2
3
4
5
6
7
8
9
10
11
|
-- 大量データ削除後のVACUUM
DELETE FROM large_table WHERE created_at < now() - interval '1 year';
VACUUM ANALYZE large_table;
-- 大量データロード後のANALYZE
COPY large_table FROM '/path/to/data.csv' WITH CSV;
ANALYZE large_table;
-- テーブル構造変更後
ALTER TABLE tablename ADD COLUMN new_column INTEGER;
ANALYZE tablename;
|
autovacuumのチューニング#
autovacuumは、VACUUMとANALYZEを自動的に実行するバックグラウンドプロセスです。適切にチューニングすることで、手動メンテナンスの負担を大幅に軽減できます。
autovacuumの動作確認#
1
2
3
4
5
6
7
8
|
-- autovacuumが有効か確認
SHOW autovacuum;
-- autovacuum関連の設定
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name LIKE 'autovacuum%'
ORDER BY name;
|
autovacuumのトリガー条件#
autovacuumは、以下の条件でテーブルをVACUUM対象として選択します。
VACUUMトリガー条件:
vacuum_threshold = autovacuum_vacuum_threshold
+ autovacuum_vacuum_scale_factor × テーブル行数
デフォルトでは、50 + 0.2 × テーブル行数の不要タプルが蓄積するとVACUUMが実行されます。
ANALYZEトリガー条件:
analyze_threshold = autovacuum_analyze_threshold
+ autovacuum_analyze_scale_factor × テーブル行数
デフォルトでは、50 + 0.1 × テーブル行数の変更があるとANALYZEが実行されます。
autovacuumの主要パラメータ#
| パラメータ |
デフォルト値 |
説明 |
| autovacuum |
on |
autovacuumの有効/無効 |
| autovacuum_max_workers |
3 |
同時に実行できるautovacuumワーカー数 |
| autovacuum_naptime |
1min |
autovacuumラウンド間の待機時間 |
| autovacuum_vacuum_threshold |
50 |
VACUUM実行の基本閾値 |
| autovacuum_vacuum_scale_factor |
0.2 |
VACUUM実行のスケールファクター |
| autovacuum_analyze_threshold |
50 |
ANALYZE実行の基本閾値 |
| autovacuum_analyze_scale_factor |
0.1 |
ANALYZE実行のスケールファクター |
| autovacuum_vacuum_cost_delay |
2ms |
コストベースの遅延時間 |
| autovacuum_vacuum_cost_limit |
-1 |
コスト上限(-1はvacuum_cost_limitを使用) |
大規模テーブル向けのチューニング#
大規模テーブルでは、デフォルトのスケールファクターだと不要タプルが大量に蓄積してからVACUUMが実行されます。テーブル単位でパラメータを調整できます。
1
2
3
4
5
6
7
|
-- 1億行のテーブルに対してより積極的なVACUUMを設定
ALTER TABLE large_table SET (
autovacuum_vacuum_scale_factor = 0.01, -- 1%で実行
autovacuum_vacuum_threshold = 1000, -- 最低1000行
autovacuum_analyze_scale_factor = 0.005, -- 0.5%で実行
autovacuum_analyze_threshold = 500 -- 最低500行
);
|
書き込みが多いテーブルの設定#
頻繁に更新されるテーブルには、より積極的な設定が有効です。
1
2
3
4
5
6
7
|
-- 高頻度更新テーブルの設定
ALTER TABLE hot_table SET (
autovacuum_vacuum_scale_factor = 0.02,
autovacuum_vacuum_threshold = 100,
autovacuum_vacuum_cost_delay = 0, -- 遅延なし
autovacuum_vacuum_cost_limit = 2000 -- より高い上限
);
|
autovacuumのモニタリング#
autovacuumの実行状況を監視します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-- autovacuum実行履歴
SELECT
schemaname,
relname,
last_vacuum,
last_autovacuum,
vacuum_count,
autovacuum_count,
last_analyze,
last_autoanalyze
FROM pg_stat_user_tables
WHERE autovacuum_count > 0
ORDER BY last_autovacuum DESC NULLS LAST
LIMIT 20;
|
autovacuumが長時間ブロックされていないか確認します。
1
2
3
4
5
6
7
|
-- autovacuumプロセスの状態
SELECT
pid,
now() - xact_start AS duration,
query
FROM pg_stat_activity
WHERE backend_type = 'autovacuum worker';
|
autovacuumのログ有効化#
autovacuumの動作を詳細に把握するため、ログを有効化します。
1
2
|
# postgresql.conf
log_autovacuum_min_duration = 1000 # 1秒以上かかるautovacuumをログ出力
|
ログ管理#
PostgreSQLのログは、トラブルシューティングやパフォーマンス分析に不可欠です。適切に設定・管理することで、問題の早期発見と解決が可能になります。
ログの基本設定#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# postgresql.conf - ログの基本設定
# ログの出力先
logging_collector = on # ファイルへのログ出力を有効化
log_directory = 'log' # ログディレクトリ(データディレクトリ相対)
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # ログファイル名パターン
log_rotation_age = 1d # ログローテーション間隔
log_rotation_size = 100MB # ローテーションサイズ
# ログレベル
log_min_messages = warning # 一般的なメッセージの最小レベル
log_min_error_statement = error # エラー時にSQL文を出力する最小レベル
# 接続・切断のログ
log_connections = on
log_disconnections = on
|
遅いクエリのログ出力#
本番環境でのパフォーマンス問題を特定するため、遅いクエリをログに記録します。
1
2
3
4
5
6
7
8
9
10
|
# postgresql.conf - スロークエリログ
# 指定時間以上かかるクエリをログ出力(ミリ秒)
log_min_duration_statement = 1000 # 1秒以上かかるクエリ
# または、すべてのクエリをログ出力(開発環境向け)
# log_statement = 'all'
# DDL文のみログ出力(本番環境向け)
# log_statement = 'ddl'
|
詳細なログ情報の出力#
問題調査に必要な情報を追加します。
1
2
3
4
5
6
7
8
9
10
11
12
|
# postgresql.conf - 詳細ログ設定
# ログフォーマット
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
# 実行計画関連
log_checkpoints = on # チェックポイントをログ出力
log_lock_waits = on # ロック待ちをログ出力
deadlock_timeout = 1s # デッドロック検出タイムアウト
# 一時ファイル
log_temp_files = 0 # すべての一時ファイル作成をログ出力
|
ログの分析#
ログファイルから遅いクエリを抽出します。
1
2
3
4
|
# 1秒以上かかるクエリを抽出
grep "duration:" /var/lib/postgresql/14/main/log/postgresql-*.log | \
awk -F'duration: ' '{print $2}' | \
sort -rn | head -20
|
pgBadgerを使用した詳細分析も推奨されます。
1
2
3
4
5
|
# pgBadgerのインストール(Debian/Ubuntu)
sudo apt install pgbadger
# レポート生成
pgbadger /var/lib/postgresql/14/main/log/postgresql-*.log -o report.html
|
ログローテーションの設定#
ディスク容量を圧迫しないよう、適切なローテーションを設定します。
1
2
3
4
5
6
7
8
9
10
|
# postgresql.conf
# 時間ベースのローテーション
log_rotation_age = 1d
# サイズベースのローテーション
log_rotation_size = 100MB
# 既存ファイルへの追記(offで新規作成)
log_truncate_on_rotation = off
|
外部のログローテーションツール(logrotate)を使用する場合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# /etc/logrotate.d/postgresql
/var/lib/postgresql/14/main/log/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 postgres postgres
sharedscripts
postrotate
/usr/bin/pg_ctlcluster 14 main reload > /dev/null 2>&1 || true
endscript
}
|
運用に役立つログクエリ#
現在のログ設定を確認します。
1
2
3
4
5
|
-- ログ関連の設定一覧
SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name LIKE 'log%'
ORDER BY name;
|
運用チェックリスト#
日常的な監視・メンテナンスタスクをまとめたチェックリストです。
日次チェック項目#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-- 1. 接続数の確認
SELECT count(*) AS connections,
(SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_conn
FROM pg_stat_activity;
-- 2. 長時間実行中のクエリ
SELECT pid, now() - query_start AS duration, substring(query, 1, 100)
FROM pg_stat_activity
WHERE state = 'active' AND query_start < now() - interval '5 minutes';
-- 3. テーブル膨張の確認
SELECT relname, n_dead_tup
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
ORDER BY n_dead_tup DESC
LIMIT 10;
|
週次チェック項目#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-- 1. ディスク使用量の確認
SELECT pg_size_pretty(pg_database_size(current_database())) AS db_size;
-- 2. autovacuumの実行状況
SELECT relname, last_autovacuum, autovacuum_count
FROM pg_stat_user_tables
WHERE last_autovacuum < now() - interval '1 week'
OR last_autovacuum IS NULL
ORDER BY n_dead_tup DESC;
-- 3. 未使用インデックスの確認
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
AND indexrelname NOT LIKE '%_pkey';
|
月次メンテナンス項目#
- pg_stat_statementsの統計リセット
- 大規模テーブルの膨張確認と必要に応じたVACUUM FULL実行
- ログファイルのアーカイブと削除
- autovacuumパラメータの見直し
- バックアップのリストアテスト
まとめ#
PostgreSQLの安定運用には、継続的な監視とメンテナンスが不可欠です。本記事で解説した内容を実践することで、パフォーマンス問題の早期発見と予防が可能になります。
主なポイントを振り返ります。
- pg_stat_statementsを活用して、問題のあるクエリを特定する
- pg_stat_activityやpg_stat_user_tablesを使って、接続数やテーブルの状態を監視する
- VACUUMとANALYZEの役割を理解し、autovacuumを適切にチューニングする
- ログを適切に設定し、問題発生時に分析できるようにする
これらのタスクを自動化するスクリプトやモニタリングツール(Prometheus + pg_exporter、Zabbix、Datadog等)の導入も検討してください。
参考リンク#