PostgreSQL の管理コマンド一覧(DBA)
PostgreSQL DBA コースで覚える構文の一覧です。共通 SQL コースの構文(SELECT・JOIN・GROUP BY・ウィンドウ関数など)はすべて使えます。
CREATE ROLEロールを作る(PostgreSQL)Lv1 で覚える
CREATE ROLE 名前 [LOGIN] [PASSWORD 'パスワード'] [CREATEDB] [CREATEROLE]; CREATE USER 名前 …;(LOGIN 付きの CREATE ROLE) ALTER ROLE 名前 属性; DROP ROLE 名前;
PostgreSQL では、ユーザーもグループも「ロール」です。LOGIN があるロールだけが接続できます(CREATE USER は LOGIN が最初から付いた CREATE ROLE)。属性は ALTER ROLE で後から変えられます。持ち物(表など)があるロールは DROP できません。
CREATE ROLE clerk LOGIN PASSWORD 'zou-2026'; ALTER ROLE clerk CREATEDB; DROP ROLE clerk;
\duロールの一覧を見る(psql)Lv2 で覚える
\du \du 名前 \dg
ロールと属性(Superuser・Create role・Create DB・Cannot login・Password valid until など)を一覧にします。何も書かれていないロールは、ログインできるふつうのロールです。pg_roles を SELECT しても同じことが分かります。
\du SELECT rolname, rolcanlogin FROM pg_roles WHERE rolname !~ '^pg_';
\c と psql -U別のロールで接続するLv3 で覚える
\c データベース ロール [ホスト] [ポート] \! psql -h localhost -U ロール -d quest -c "SQL" \! PGPASSWORD=パスワード psql -h ホスト -U ロール -c "SQL" \! ssh app01 psql -h db01 -U ロール -c "SQL"
\c は今の psql の接続を新しい相手に替えます(省いたものや - は前の接続と同じ)。psql -c は 1 回だけ接続して SQL を実行します。どちらも pg_hba.conf の規則で許されて、パスワードなどの認証に通ったときだけ接続できます。パスワードが要るときは Password for user …: と聞かれます。このシミュレーターでは quest データベースにだけ接続できます。
\c quest clerk localhost \! psql -h localhost -U clerk -c "SELECT current_user"
GRANT表や列の権限を与える(PostgreSQL)Lv4 で覚える
GRANT SELECT, INSERT ON 表 TO ロール; GRANT SELECT (列, 列) ON 表 TO ロール; GRANT UPDATE (列) ON 表 TO ロール; GRANT SELECT ON ALL TABLES IN SCHEMA スキーマ TO ロール; GRANT ロール TO ロール;
表の持ち主とスーパーユーザー以外は、権限がないと permission denied になります。列を書くと、その列だけの権限になります。ALL TABLES IN SCHEMA は「今ある表」だけに与えます(これから作る表は ALTER DEFAULT PRIVILEGES)。ロールをロールに GRANT すると、メンバーにできます。
GRANT SELECT ON scrolls TO clerk; GRANT UPDATE (stock) ON scrolls TO clerk;
SET ROLEほかのロールになって試すLv5 で覚える
SET ROLE ロール; RESET ROLE; SELECT current_user, session_user;
スーパーユーザーや、そのロールのメンバーは、SET ROLE でそのロールに切り替えられます。権限を正しく与えたか、本人になって試すときに便利です。current_user が今のロール、session_user は接続したロールです。RESET ROLE で戻ります。
SET ROLE clerk; SELECT * FROM scrolls; RESET ROLE;
REVOKE権限を取り消す(PostgreSQL)Lv6 で覚える
REVOKE 権限 ON 表 FROM ロール; REVOKE ALL ON 表 FROM ロール; REVOKE 権限 ON 表 FROM PUBLIC; REVOKE GRANT OPTION FOR 権限 ON 表 FROM ロール;
GRANT で与えた権限を取り消します。PUBLIC は「すべてのロール」のことで、PUBLIC に与えた権限は、そのロールから取り消しても残ります。WITH GRANT OPTION で渡した「人に渡す権利」は GRANT OPTION FOR で取り消します。
REVOKE INSERT ON scrolls FROM clerk; REVOKE ALL ON scrolls FROM PUBLIC;
スキーマの権限スキーマに入る権利と、作る権利Lv7 で覚える
GRANT USAGE ON SCHEMA スキーマ TO ロール; GRANT CREATE ON SCHEMA スキーマ TO ロール; REVOKE CREATE ON SCHEMA public FROM PUBLIC; CREATE SCHEMA スキーマ AUTHORIZATION ロール; \dn+
スキーマの USAGE がないと、中の表に権限があっても使えません。CREATE があると、そのスキーマに表を作れます。PostgreSQL 15 から public スキーマの CREATE は PUBLIC に与えられていませんが、古い版から上げたデータベースでは残っていることがあります。\dn+ の Access privileges で確かめます(U が USAGE、C が CREATE)。
REVOKE CREATE ON SCHEMA public FROM PUBLIC; GRANT USAGE ON SCHEMA archive TO clerk;
ALTER DEFAULT PRIVILEGESこれから作る表の権限を決めるLv9 で覚える
ALTER DEFAULT PRIVILEGES IN SCHEMA スキーマ GRANT SELECT ON TABLES TO ロール; ALTER DEFAULT PRIVILEGES FOR ROLE 作る人 IN SCHEMA スキーマ GRANT …; \ddp
GRANT は今ある表にだけ効きます。これから作る表にも自動で権限を付けたいときは ALTER DEFAULT PRIVILEGES を使います。決まりは「表を作るロール」ごとで、FOR ROLE を省くと自分が作る表の決まりになります。\ddp で確かめます。
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readers;
\dp と has_table_privilege誰が何をできるかを確かめるLv10 で覚える
\dp [表]
\z [表]
SELECT has_table_privilege('ロール', '表', 'SELECT');
SELECT has_column_privilege('ロール', '表', '列', 'UPDATE');\dp の Access privileges は「ロール=権限/与えた人」の形です。r=SELECT、a=INSERT、w=UPDATE、d=DELETE、D=TRUNCATE、x=REFERENCES、t=TRIGGER、m=MAINTAIN、* は人に渡せる権利。空のときは、持ち主だけが使える既定の状態です。has_…_privilege は true / false で答えます。
\dp scrolls
SELECT has_table_privilege('clerk', 'scrolls', 'INSERT');行レベルセキュリティ行ごとに見せる・見せないを決めるLv11 で覚える
ALTER TABLE 表 ENABLE ROW LEVEL SECURITY; CREATE POLICY 名前 ON 表 [FOR SELECT|INSERT|UPDATE|DELETE] [TO ロール] USING (条件) [WITH CHECK (条件)]; DROP POLICY 名前 ON 表;
RLS を有効にすると、ポリシーの USING の条件に合う行だけが見え、WITH CHECK の条件に合う行だけを書けます。ポリシーが 1 つもなければ何も見えません。表の持ち主とスーパーユーザーは RLS を素通りします(持ち主にも効かせるには FORCE ROW LEVEL SECURITY)。
ALTER TABLE letters ENABLE ROW LEVEL SECURITY; CREATE POLICY own_letters ON letters USING (owner = current_user);
pg_hba.conf誰がどこから接続できるかLv13 で覚える
TYPE DATABASE USER ADDRESS METHOD local all all peer host quest app 192.168.10.0/24 scram-sha-256 SELECT * FROM pg_hba_file_rules;
接続が来ると、上の行から順に TYPE(local はソケット、host は TCP/IP)・DATABASE・USER・ADDRESS を比べ、最初に合った行の METHOD で認証します。合う行がなければ no pg_hba.conf entry で断られます。METHOD は trust(パスワードなし)・reject(断る)・scram-sha-256・md5・peer(OS のユーザーと同じ名前か)など。書き換えたら再読み込みが要ります。pg_hba_file_rules でファイルの読み方と間違い(error)を確かめられます。
\! cat pg_hba.conf \! echo 'host quest app 192.168.10.21/32 scram-sha-256' >> pg_hba.conf
設定の再読み込み設定ファイルを読み直させるLv14 で覚える
\! pg_ctl reload SELECT pg_reload_conf();
pg_hba.conf や postgresql.conf は、書き換えただけでは効きません。サーバーに再読み込みさせると、pg_hba.conf の規則と、再起動が要らない設定が変わります。接続はそのまま続きます。pg_hba.conf に間違いがあると、前の規則のまま(ログに pg_hba.conf was not reloaded)です。
\! pg_ctl reload SELECT pg_reload_conf();
SHOW と pg_settings設定の値を見るLv16 で覚える
SHOW 名前;
SELECT current_setting('名前');
SELECT name, setting, unit, context, source FROM pg_settings WHERE name = '名前';
SET 名前 = 値;(そのセッションだけ)pg_settings の context は、変えたものがいつ効くかを表します。postmaster は再起動、sighup は再読み込み、superuser と user は SET でそのセッションだけ変えられます。source はどこで決まったか(default・configuration file など)、pending_restart は再起動を待っている設定です。
SHOW max_connections; SELECT name, setting, context FROM pg_settings WHERE name = 'work_mem';
postgresql.confサーバーの設定ファイルLv17 で覚える
名前 = 値 # コメント \! grep -n 名前 postgresql.conf \! sed -i "s/^#名前 = .*/名前 = 値/" postgresql.conf SELECT name, setting, applied, error FROM pg_file_settings;
データディレクトリ($PGDATA)にある設定ファイルです。# で始まる行はコメントで、その設定は既定値のままです。同じ名前を何度も書くと最後の行が勝ちます。書き換えたら再読み込み(再起動が要る設定は再起動)で効きます。pg_file_settings でファイルに書いた値と、効いているか(applied)を確かめられます。
\! grep -n work_mem postgresql.conf \! sed -i "s/^#work_mem = 4MB/work_mem = 8MB/" postgresql.conf
ALTER SYSTEMSQL で設定ファイルを書き換えるLv18 で覚える
ALTER SYSTEM SET 名前 = 値; ALTER SYSTEM RESET 名前; ALTER SYSTEM RESET ALL;
ALTER SYSTEM は postgresql.auto.conf に書きます。このファイルは postgresql.conf の後に読まれるので、同じ設定なら auto.conf が勝ちます。書いただけでは効かないので、再読み込み(再起動が要る設定は再起動)します。トランザクションの中では使えません。auto.conf は手で書き換えないでください。
ALTER SYSTEM SET work_mem = '16MB'; SELECT pg_reload_conf();
pg_ctlサーバーを起動・停止・再起動するLv19 で覚える
\! pg_ctl status \! pg_ctl stop [-m fast|smart|immediate] \! pg_ctl start \! pg_ctl restart [-m fast]
データディレクトリは環境変数 PGDATA で決まります(-D で書いてもよい)。stop の既定は fast(接続を切って、きれいに止める)。smart は接続がなくなるまで待ち、immediate はすぐに落とします(次の起動で回復の処理が走る)。止めると psql の接続も切れ、次の SQL で psql がつなぎ直します(Attempting reset)。
\! pg_ctl status \! pg_ctl restart
サーバーのログサーバーで起きたことを読むLv21 で覚える
\! tail -n 20 log/postgresql.log \! grep FATAL log/postgresql.log ALTER SYSTEM SET log_connections = 'on'; ALTER SYSTEM SET log_min_duration_statement = '100ms';
logging_collector が on だと、ログは $PGDATA の log_directory(log)の log_filename に書かれます。接続に失敗した本当の理由(パスワードが違う・期限切れなど)は、相手には見せずにログの DETAIL にだけ出ます。log_connections・log_disconnections で接続を、log_min_duration_statement で遅い SQL を記録できます。エラーになった SQL は STATEMENT に出ます。
\! tail -n 5 log/postgresql.log \! grep DETAIL log/postgresql.log
MVCC と不要な行UPDATE・DELETE は古い行を残すLv23 で覚える
SELECT ctid, xmin, xmax, * FROM 表; SELECT relname, n_live_tup, n_dead_tup FROM pg_stat_user_tables;
PostgreSQL は行を書き換えず、新しい版の行を足します(MVCC)。xmin はその行を作ったトランザクション、xmax は消したトランザクション、ctid は行の場所です。古い版は、見ている人がいなくなっても「不要な行(dead tuple)」として残ります。DELETE や、ROLLBACK した UPDATE も不要な行を作ります。数は pg_stat_user_tables の n_dead_tup で見られます。
SELECT ctid, xmin, xmax, id FROM treasure WHERE id = 1; SELECT relname, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;
VACUUM不要な行を掃除するLv24 で覚える
VACUUM 表; VACUUM (VERBOSE) 表; VACUUM;(すべての表)
不要な行の場所を、また使えるようにします。表のファイルは小さくなりません(末尾が空いたときだけ切り詰めます)。表を止めずに動くので、いつでも実行できます。VERBOSE を付けると、消した行の数(tuples: … removed)などが INFO に出ます。古いトランザクションが残っていると、dead but not yet removable(まだ消せない)になります。
VACUUM (VERBOSE) treasure; SELECT last_vacuum, vacuum_count FROM pg_stat_user_tables WHERE relname = 'treasure';
ANALYZE と pg_stats実行計画のための統計を集めるLv25 で覚える
ANALYZE 表; VACUUM ANALYZE 表; SELECT attname, n_distinct, most_common_vals FROM pg_stats WHERE tablename = '表'; ALTER TABLE 表 ALTER COLUMN 列 SET STATISTICS 数;
ANALYZE は表の中身を調べて、値の種類や多い値の割合を pg_stats に残します。プランナーはこれで行の数を見積もり、Seq Scan か Index Scan かを選びます。データが大きく変わったのに統計が古いと、見積もり(rows=)が外れて遅い計画を選びます。SET STATISTICS で、調べる細かさを列ごとに変えられます。
ANALYZE orders; EXPLAIN SELECT * FROM orders WHERE status = 'new';
autovacuum自動の掃除と統計Lv26 で覚える
SHOW autovacuum; SELECT name, setting FROM pg_settings WHERE name LIKE 'autovacuum%'; ALTER TABLE 表 SET (autovacuum_vacuum_scale_factor = 0.01); ALTER TABLE 表 RESET (autovacuum_enabled);
autovacuum は 1 分ごと(autovacuum_naptime)に表を見回り、不要な行が「autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × 行の数」を超えた表を VACUUM、変わった行が多い表を ANALYZE します。大きな表は割合のしきい値に届きにくいので、表ごとに小さくします。結果は pg_stat_user_tables の last_autovacuum・autovacuum_count で確かめます。このシミュレーターでは、\! sleep 60 で 1 分が経ちます。
ALTER TABLE big_log SET (autovacuum_vacuum_scale_factor = 0.01); \! sleep 60
表の大きさディスクをどれだけ使っているかLv27 で覚える
SELECT pg_size_pretty(pg_relation_size('表'));
SELECT pg_size_pretty(pg_total_relation_size('表'));
SELECT pg_size_pretty(pg_indexes_size('表'));pg_relation_size は表そのもの、pg_indexes_size はインデックスの合計、pg_total_relation_size は表・インデックス・TOAST の合計です(バイト)。pg_size_pretty で kB・MB の形にします。不要な行が多い表は、行の数のわりに大きくなります(ふくらみ)。
SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) FROM pg_stat_user_tables ORDER BY pg_total_relation_size(relid) DESC;
VACUUM FULL表を作り直して縮めるLv28 で覚える
VACUUM FULL 表; VACUUM (FULL, VERBOSE) 表; \! df -h $PGDATA
表を新しいファイルに書き直して、空いた場所を OS に返します(relfilenode が変わります)。その間は ACCESS EXCLUSIVE ロックを持つので、読むこともできません。ほかの接続がその表を使っていると、終わるまで待ちます。新しいファイルを書き終えてから古いファイルを消すので、作り直した表の大きさの分だけ空きディスクも要ります(df -h で先に確かめる。足りないと No space left on device で止まります)。止められない表では、ふつうの VACUUM をこまめに動かすほうが大事です。
VACUUM FULL treasure; SELECT relfilenode FROM pg_class WHERE relname = 'treasure';
REINDEXインデックスを作り直すLv29 で覚える
REINDEX INDEX インデックス; REINDEX TABLE 表; REINDEX INDEX CONCURRENTLY インデックス;
インデックスも、更新を重ねるとふくらみます。REINDEX は作り直して小さくします。ふつうの REINDEX は表への書き込みを止めます。CONCURRENTLY を付けると、書き込みを止めずに作り直せます(時間はかかります)。失敗した CONCURRENTLY の作業は、pg_index の indisvalid が false の INVALID なインデックスとして残ります。
REINDEX INDEX CONCURRENTLY orders_updated_idx; SELECT indexrelid::regclass FROM pg_index WHERE NOT indisvalid;
凍結と XID の年齢トランザクション ID の周回を防ぐLv30 で覚える
SELECT datname, age(datfrozenxid) FROM pg_database; SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind = 'r'; VACUUM FREEZE 表;
トランザクション ID(XID)は約 21 億で一周します。古い行は「凍結」して、どの XID よりも古いと印を付けておく必要があります。age(relfrozenxid) は、その表でまだ凍結していない一番古い XID からの年齢です。autovacuum_freeze_max_age(既定 2 億)を超えると、autovacuum を止めていても周回を防ぐ VACUUM が動きます。VACUUM FREEZE はすぐに凍結します。
VACUUM FREEZE old_ledger; SELECT age(datfrozenxid) FROM pg_database WHERE datname = 'quest';
vacuumdbデータベースをまとめて掃除するLv31 で覚える
\! vacuumdb -z quest \! vacuumdb -Z quest \! vacuumdb -t 表 -v quest \! vacuumdb -F quest
シェルから VACUUM を流すコマンドです。cron で夜中に動かすのによく使います。-z は VACUUM と ANALYZE、-Z は ANALYZE だけ、-F は FREEZE、-f は FULL、-t は表を選び、-v は詳しく表示します。
\! vacuumdb -z quest
pg_dumpデータベースを SQL の形で写す(論理バックアップ)Lv33 で覚える
\! pg_dump -f ファイル.sql quest \! pg_dump -Fc -f ファイル.dump quest \! pg_dump -t 表 -f ファイル.sql quest \! pg_dump -n スキーマ -s -f ファイル.sql quest
動いているデータベースから、表の定義(CREATE TABLE)と行(COPY)を取り出します。取っている間も、ほかの人は読み書きできます。-Fp(既定)は読める SQL のテキスト、-Fc はカスタム形式(圧縮され、pg_restore で表を選んで戻せる)。-t は表、-n はスキーマを選び、-s(--schema-only)は定義だけ、-a(--data-only)は行だけを写します。ロールは写りません(pg_dumpall -g)。
\! pg_dump -Fc -f /backup/quest.dump quest \! grep COPY /backup/quest.sql
pg_restore と psql -fダンプから戻すLv34 で覚える
\! pg_restore -l ファイル.dump \! pg_restore -d quest ファイル.dump \! pg_restore -d quest -t 表 ファイル.dump \! pg_restore --clean -d quest ファイル.dump \! psql -f ファイル.sql
カスタム形式は pg_restore で戻します。-l は中身の一覧(TOC)、-d は戻す先のデータベース、-t は表だけ(インデックス・制約・権限は戻りません)、-a は行だけ、--clean は先に DROP してから作り直します(--if-exists で、無いものの DROP をエラーにしない)。テキスト形式は psql -f で流します。どちらもエラーがあっても続け、最後に数を出します。
\! pg_restore -d quest -t treasure /backup/quest.dump \! psql -f /backup/quest.sql
pg_dumpallロール(クラスター全体のもの)も写すLv35 で覚える
\! pg_dumpall --globals-only -f ファイル.sql \! pg_dumpall -f ファイル.sql \! psql -f ファイル.sql
ロールやロールのメンバーは、データベースではなくクラスター全体のものなので、pg_dump には入りません。pg_dumpall --globals-only(-g)はロールの定義(CREATE ROLE と ALTER ROLE … PASSWORD)だけを写します。表と一緒に別のサーバーへ移すときは、先にロールを戻してから表を戻します(GRANT の相手が無いとエラーになる)。スーパーユーザーで実行します。
\! pg_dumpall -g -f /backup/globals.sql \! psql -f /backup/globals.sql
WALすべての変更の記録Lv36 で覚える
SELECT pg_current_wal_lsn(); SELECT pg_walfile_name(pg_current_wal_lsn()); SELECT pg_wal_lsn_diff(LSN, LSN); SELECT pg_switch_wal(); SELECT name, size FROM pg_ls_waldir();
PostgreSQL は、データを書き換える前に、変更を WAL(write-ahead log)に書きます。WAL の位置は LSN(0/3000148 のような形)で表し、16MB ごとのファイル(000000010000000000000003)に分かれて $PGDATA/pg_wal にたまります。pg_wal_lsn_diff は 2 つの位置の差(バイト)、pg_switch_wal は今のファイルを閉じて次のファイルへ進めます。
SELECT pg_current_wal_lsn(), pg_walfile_name(pg_current_wal_lsn()); \! ls pg_wal
WAL のアーカイブ書き終わった WAL を別の場所に保管するLv37 で覚える
ALTER SYSTEM SET archive_mode = on;(再起動が要る) ALTER SYSTEM SET archive_command = 'test ! -f /backup/wal/%f && cp %p /backup/wal/%f'; SELECT * FROM pg_stat_archiver;
archive_mode を on にすると、WAL のファイルが書き終わるたびに archive_command を実行します。%p はファイルのパス、%f はファイルの名前です。test ! -f で、同じ名前を上書きしないようにします。コマンドが失敗すると、成功するまでやり直し、その間 WAL は pg_wal にたまり続けます。pg_stat_archiver の failed_count・last_failed_wal と、ログの archive command failed で気づけます。
SELECT archived_count, last_archived_wal, failed_count FROM pg_stat_archiver;
pg_basebackupデータディレクトリを丸ごと写す(物理バックアップ)Lv38 で覚える
\! pg_basebackup -D ディレクトリ -Ft -X stream -P \! PGPASSWORD=… pg_basebackup -h localhost -U ロール -D ディレクトリ \! pg_basebackup -D ディレクトリ -Fp -R
動いているサーバーから、レプリケーションの接続でデータディレクトリを写します。-Ft は tar(base.tar と pg_wal.tar)、-Fp はそのままのディレクトリ。-X stream は取っている間の WAL も一緒に写し、これだけで戻せるバックアップになります。-P は進み具合を出し、-R はスタンバイ用の設定(standby.signal と primary_conninfo)を書きます。REPLICATION の属性と、pg_hba.conf の replication の行が要ります。
\! pg_basebackup -D /backup/base -Ft -X stream -P
REPLICATION の属性レプリケーションの接続を許すLv39 で覚える
CREATE ROLE 名前 LOGIN REPLICATION PASSWORD '…'; ALTER ROLE 名前 REPLICATION;
pg_basebackup やスタンバイは、レプリケーションの接続でサーバーにつなぎます。それにはスーパーユーザーか、REPLICATION の属性を持つロールが要ります(無いと permission denied to start WAL sender)。バックアップやスタンバイのために、スーパーユーザーではない専用のロールを作るのが安全です。
CREATE ROLE backup LOGIN REPLICATION PASSWORD 'shelf-key';
PITR(時点を指定した復旧)ベースバックアップと WAL で、ある時点に戻すLv40 で覚える
\! tar -xf /backup/base/base.tar -C $PGDATA restore_command = 'cp /backup/wal/%f %p' recovery_target_time = '2026-11-04 22:14:00' recovery_target_name = '名前'(pg_create_restore_point) \! touch $PGDATA/recovery.signal SELECT pg_wal_replay_resume();
止めたサーバーのデータディレクトリをベースバックアップで置き換え、restore_command(アーカイブから WAL を取ってくるコマンド)と目標(recovery_target_time・_name・_lsn)を書き、recovery.signal を置いて起動します。サーバーは WAL を流し直して目標で止まり(recovery_target_action の既定は pause)、読むだけの状態になります。データを確かめてから pg_wal_replay_resume() で復旧を終えると、新しいタイムラインで書けるようになります。
\! touch recovery.signal SELECT pg_is_in_recovery(); SELECT pg_wal_replay_resume();
pg_verifybackup とタイムラインバックアップが使えるか・復旧のあとを確かめるLv41 で覚える
\! pg_verifybackup /backup/base SELECT timeline_id FROM pg_control_checkpoint(); \! cat /backup/wal/00000002.history
pg_verifybackup は backup_manifest と見比べて、ファイルが欠けたり壊れたりしていないか、必要な WAL があるかを確かめます。復旧で書けるようになると、タイムラインの番号が 1 つ増え、どこで分かれたかを 0000000N.history に書きます。復旧が目標に届かないと、ログに recovery ended before configured recovery target was reached と出て起動しません。
\! pg_verifybackup /backup/base
pg_archivecleanup要らなくなった古い WAL を消すLv42 で覚える
\! pg_archivecleanup アーカイブの場所 一番古く残すファイル \! pg_archivecleanup -n /backup/wal 000000010000000000000005
ベースバックアップより前の WAL は、そのバックアップから戻すときには要りません。いちばん古く残すベースバックアップの .backup ファイル(START WAL LOCATION)を見て、それより前の WAL のファイルを消します。-n は消さずに一覧だけ出します。新しいバックアップを取って確かめてから消します。
\! cat /backup/wal/000000010000000000000005.00000028.backup
pg_stat_activityいまの接続と、何をしているかLv44 で覚える
SELECT pid, usename, state, wait_event_type, wait_event, query FROM pg_stat_activity; SELECT state, count(*) FROM pg_stat_activity GROUP BY state;
接続ごとに 1 行です。state は active(実行中)・idle(待ち)・idle in transaction(トランザクションの途中で止まっている)。query_start は今の問い合わせの開始、xact_start はトランザクションの開始、wait_event_type・wait_event は何を待っているか(Lock ならロック、Client ならアプリの返事)。backend_type が client backend の行がアプリなどの接続です。
SELECT pid, now() - xact_start AS xact_age, query FROM pg_stat_activity WHERE state = 'idle in transaction';
pg_cancel_backend と pg_terminate_backend暴走した問い合わせを止めるLv45 で覚える
SELECT pg_cancel_backend(pid); SELECT pg_terminate_backend(pid); ALTER SYSTEM SET idle_in_transaction_session_timeout = '10min';
pg_cancel_backend は今の問い合わせだけを取り消します(接続は残ります。何も実行していない接続には効きません)。pg_terminate_backend は接続ごと切ります(トランザクションは ROLLBACK されます)。idle_in_transaction_session_timeout を決めておくと、トランザクションの途中で止まった接続を自動で切ります(効くのは決めたあとに止まった接続です。いま止まっている接続は、読み直しても切られないので手で切ります)。
SELECT pg_cancel_backend(5103); SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle in transaction';
pg_locks と pg_blocking_pidsロックの待ちを調べるLv46 で覚える
SELECT pid, relation::regclass, mode, granted FROM pg_locks WHERE NOT granted; SELECT pid, pg_blocking_pids(pid) FROM pg_stat_activity WHERE wait_event_type = 'Lock'; SET lock_timeout = '3s';
pg_locks はロックの一覧です。granted が false の行は、取れずに待っているロックです。pg_blocking_pids(pid) は、その接続を待たせている接続の pid の配列を返します。待たせている元(多くは idle in transaction)を見つけて終わらせます。DDL を流すときは lock_timeout を決めておくと、長く待たずにあきらめます。
SELECT pid, state, query FROM pg_stat_activity WHERE pid = ANY(pg_blocking_pids(5202));
pg_stat_statements重い SQL を探すLv47 で覚える
ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';(再起動が要る) CREATE EXTENSION pg_stat_statements; SELECT query, calls, total_exec_time, mean_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5; SELECT pg_stat_statements_reset();
SQL の形(値は $1 に置き換えたもの)ごとに、回数(calls)・合計の時間(total_exec_time)・1 回の平均(mean_exec_time)・読んだブロックなどを集めます。サーバーの起動時に読み込む必要があり(shared_preload_libraries)、データベースに CREATE EXTENSION します。total_exec_time の大きいものから直すと、効き目が大きくなります。
SELECT query, calls, round(mean_exec_time::numeric, 2) FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 3;
EXPLAIN (ANALYZE, BUFFERS)本当に実行して計画を測るLv48 で覚える
EXPLAIN (ANALYZE, BUFFERS) SELECT …; BEGIN; EXPLAIN (ANALYZE) UPDATE …; ROLLBACK;
実際に実行して、ノードごとの見積もり(cost・rows)と実際(actual time・rows)、読んだブロック(Buffers: shared hit はキャッシュ、read はディスク)を見せます。見積もりと実際の行の数が大きく違うときは、統計が古いことが多いです。UPDATE などは本当に書き換えるので、BEGIN と ROLLBACK で包みます。
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE customer_id = 42;
pg_stat_user_indexes使われていないインデックスを探すLv49 で覚える
SELECT indexrelname, idx_scan FROM pg_stat_user_indexes ORDER BY idx_scan; DROP INDEX CONCURRENTLY インデックス;
idx_scan はそのインデックスを使った回数です。長い間 0 のインデックスは、書き込みを遅くし、ディスクを使うだけです。ただし主キーや UNIQUE のインデックスは、重複を防ぐ役目があるので消しません。統計を消した(pg_stat_reset)直後や、月に一度の処理だけが使うものにも気をつけます。
SELECT s.indexrelname FROM pg_stat_user_indexes s JOIN pg_index i ON i.indexrelid = s.indexrelid WHERE s.idx_scan = 0 AND NOT i.indisunique;
work_mem と一時ファイル並べ替えがディスクにあふれていないかLv50 で覚える
EXPLAIN (ANALYZE) SELECT … ORDER BY …; SELECT temp_files, temp_bytes FROM pg_stat_database WHERE datname = 'quest'; ALTER SYSTEM SET log_temp_files = 0; \! free -h
並べ替えやハッシュは work_mem までメモリを使い、超えると一時ファイルに書きます(Sort Method: external merge Disk: …)。pg_stat_database の temp_files・temp_bytes で数を、log_temp_files でログを確かめます。work_mem は 1 つの並べ替えごとに使うので、大きくしすぎると接続が多いときにメモリが足りなくなります。free でサーバーのメモリを見ます。
SET work_mem = '64MB'; ALTER ROLE report SET work_mem = '64MB';
キャッシュのヒット率shared_buffers が足りているかLv51 で覚える
SELECT blks_hit, blks_read, round(100.0 * blks_hit / (blks_hit + blks_read), 2) FROM pg_stat_database WHERE datname = 'quest'; SELECT relname, heap_blks_hit, heap_blks_read FROM pg_statio_user_tables;
blks_hit は shared_buffers(キャッシュ)で見つかったブロック、blks_read はキャッシュに無く読みに行ったブロックです。業務のシステムでは 99% 以上が目安です。低いときは、よく読むデータが shared_buffers に入りきっていません(メモリの 25% くらいが目安。変えるには再起動が要ります)。
SELECT relname, round(100.0 * heap_blks_hit / NULLIF(heap_blks_hit + heap_blks_read, 0), 1) FROM pg_statio_user_tables;
チェックポイント書き込みの山をならすLv52 で覚える
SELECT num_timed, num_requested FROM pg_stat_checkpointer;
ALTER SYSTEM SET max_wal_size = '16GB';
ALTER SYSTEM SET log_checkpoints = on;
SELECT pg_stat_reset_shared('checkpointer');チェックポイントは、メモリの変更をデータファイルに書き出す処理です。checkpoint_timeout(既定 5 分)ごとに動くもの(num_timed)と、WAL が max_wal_size に近づいて急いで動くもの(num_requested)があります。num_requested が多いと書き込みが集中し、ログに checkpoints are occurring too frequently と出ます。max_wal_size を大きくすると、時間で動くようになります。
SELECT num_timed, num_requested FROM pg_stat_checkpointer;
wal_level と max_wal_sendersレプリケーションの準備Lv54 で覚える
SHOW wal_level; ALTER SYSTEM SET wal_level = replica;(再起動が要る) ALTER SYSTEM SET max_wal_senders = 10;
wal_level は WAL にどれだけ書くか(minimal・replica・logical)です。スタンバイや pg_basebackup には replica 以上、論理レプリケーションには logical が要ります。max_wal_senders は、WAL を送る接続の最大の数です。どちらも再起動で変わります。
SELECT name, setting, context FROM pg_settings WHERE name IN ('wal_level', 'max_wal_senders');スタンバイWAL を受け取って同じデータを持つサーバーLv55 で覚える
\! pg_basebackup -h localhost -U replicator -D /var/lib/pgsql/18/standby -R -X stream \! pg_ctl -D /var/lib/pgsql/18/standby -o "-p 5433" start \! psql -p 5433 -c "SELECT …"
pg_basebackup -R で写すと、standby.signal(スタンバイとして起動する印)と、postgresql.auto.conf の primary_conninfo(どこから WAL を受け取るか)ができます。起動すると、プライマリから WAL を受け取って流し続けます。スタンバイでは読むことはできますが、書くと cannot execute … in a read-only transaction になります。このシミュレーターでは、スタンバイは同じマシンの別のポートで動かし、psql -p で問い合わせます。
\! psql -p 5433 -c "SELECT pg_is_in_recovery()"
pg_stat_replicationスタンバイの様子と遅れを見るLv56 で覚える
SELECT application_name, state, sent_lsn, replay_lsn, replay_lag, sync_state FROM pg_stat_replication; SELECT pg_wal_lsn_diff(sent_lsn, replay_lsn) FROM pg_stat_replication; \! psql -p 5433 -c "SELECT status FROM pg_stat_wal_receiver"
プライマリの pg_stat_replication は、つながっているスタンバイごとに 1 行です。sent_lsn は送った位置、replay_lsn はスタンバイが流し終えた位置で、その差(pg_wal_lsn_diff)が遅れです。スタンバイの側では pg_stat_wal_receiver で受け取りの様子を、pg_wal_replay_pause()・pg_wal_replay_resume() で流すのを止めたり再開したりできます。
SELECT application_name, pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes FROM pg_stat_replication;
レプリケーションスロットスタンバイが要る WAL を消さずに残すLv57 で覚える
SELECT * FROM pg_create_physical_replication_slot('名前');
primary_slot_name = '名前'(スタンバイの設定)
SELECT slot_name, active, restart_lsn, wal_status FROM pg_replication_slots;
SELECT pg_drop_replication_slot('名前');スロットを使うと、スタンバイがまだ受け取っていない WAL を、プライマリが消さずに残します。止まったスタンバイがあとで追いつけます。その代わり、使われないスロット(active が f)があると WAL がたまり続け、ディスクがあふれます。要らないスロットは消し、max_slot_wal_keep_size で残す量の上限を決めておきます(超えると wal_status が lost)。
SELECT slot_name, active, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) FROM pg_replication_slots;
同期レプリケーションスタンバイに届いてからコミットを終えるLv58 で覚える
ALTER SYSTEM SET synchronous_standby_names = '名前'; ALTER SYSTEM SET synchronous_standby_names = 'ANY 1 (a, b)'; SET synchronous_commit = local;
synchronous_standby_names に書いたスタンバイ(primary_conninfo の application_name)に WAL が届くまで、COMMIT は終わりません。プライマリが壊れても、コミットしたデータはスタンバイにあります。その代わり、同期のスタンバイが止まると、書き込みが待ったまま止まります(Ctrl+C で待つのをやめても、ローカルではコミット済み)。ANY 1 (a, b) なら、どちらか 1 台に届けばよくなります。
SELECT application_name, sync_state FROM pg_stat_replication;
昇格(promote)スタンバイをプライマリにするLv59 で覚える
\! pg_ctl -D /var/lib/pgsql/18/standby promote \! psql -p 5433 -c "SELECT pg_promote()"
プライマリが壊れたら、スタンバイを昇格させます。受け取った WAL を流し終えてから、新しいタイムラインで書けるようになります(0000000N.history ができる)。アプリの接続先を新しいプライマリに替えます。古いプライマリをそのまま起動すると、プライマリが 2 つになるので気をつけます。
\! pg_ctl -D /var/lib/pgsql/18/standby promote
pg_rewind分かれた古いプライマリを、新しいプライマリに合わせるLv60 で覚える
\! pg_rewind --target-pgdata=$PGDATA --source-server="host=localhost port=5433 user=postgres dbname=quest" -R
昇格のあと、古いプライマリには、スタンバイに届かなかった変更が残っていることがあります(歴史が分かれた)。pg_rewind は、分かれた位置から先を新しいプライマリに合わせて、スタンバイとしてつなげるようにします。古いプライマリは止めてから使います。設定ファイルも相手から写るので、ポートや primary_conninfo を確かめます。-R で standby.signal と primary_conninfo を書きます。
\! pg_rewind --target-pgdata=$PGDATA --source-server="host=localhost port=5433 user=postgres dbname=quest" -R
論理レプリケーション表ごとに、変更を別のサーバーへ写すLv61 で覚える
ALTER SYSTEM SET wal_level = logical;(再起動が要る) CREATE PUBLICATION 名前 FOR TABLE 表; CREATE SUBSCRIPTION 名前 CONNECTION 'host=… dbname=… user=… password=…' PUBLICATION 名前;(受け取る側) ALTER SUBSCRIPTION 名前 REFRESH PUBLICATION; SELECT * FROM pg_stat_subscription;
発行する側で PUBLICATION を作り、受け取る側で SUBSCRIPTION を作ると、まず表の中身を写し、そのあとは INSERT・UPDATE・DELETE を行ごとに送ります。受け取る側には同じ名前の表を先に作っておきます(定義は写りません)。UPDATE・DELETE を送るには主キー(REPLICA IDENTITY)が要ります。受け取る側で行がぶつかると、エラーで止まり、ログに出ます。
CREATE PUBLICATION shop_pub FOR TABLE orders; \! psql -p 5434 -c "SELECT subname, received_lsn FROM pg_stat_subscription"