サーバーは必ず壊れます。ディスクは飛び、AZは落ち、カーネルはパニックする。単一のPostgreSQLに全てを賭けるのは、いつか必ず来る障害に「ダウンタイムで払う」契約をしているのと同じです。
高可用性(HA)の基本はレプリケーション——常に最新に近い複製を別サーバーに用意し、いざというとき切り替える。この記事は、PostgreSQL の物理ストリーミングレプリケーションでHAを組む方法と、最も誤解される「自動フェイルオーバー」の現実を、公式ドキュメントに忠実に解説します。本番運用ガイド §3の深掘りです。
この記事のルール:レプリケーションの仕様・パラメータ既定値・PostgreSQL 18 の変更点は PostgreSQL 18 公式ドキュメント(2026年6月時点) に基づきます。フェイルオーバー自動化ツール(Patroni 等)は、公式が「外部ツール」と総称する範囲で、具体名はコミュニティ知識として注記します。
1. HAの選択肢:物理 vs 論理 vs 共有ディスク
PostgreSQL のHAには複数のアプローチがあります(公式の比較)。
| 方式 | 仕組み | 向く場面 | 制約 |
|---|---|---|---|
| 物理ストリーミング | WALをバイト単位でスタンバイへ | クラスタ全体のHA/DR、リードレプリカ | 同一メジャー版・全体複製のみ |
| 論理レプリケーション | 行変更を publish/subscribe で | テーブル単位・版跨ぎ・双方向 | 競合解決が必要・DDL非複製(別記事) |
| 共有ディスク | 単一ストレージを共有 | — | ストレージ自体がSPOF・複製はされない |
本記事の主役は物理ストリーミングレプリケーション。公式の説明:
ストリーミングレプリケーションは、ファイルベースのログシッピングより最新に保てる。スタンバイがプライマリに接続し、プライマリはWALレコードを生成され次第ストリームする。WALファイルが満杯になるのを待たない。
「ほぼリアルタイムのバイト単位の複製」です。物理なのでスタンバイはプライマリとブロック単位で同一——同一メジャーバージョンが前提です。
2. 物理ストリーミングレプリケーションの構築
最小構成の流れです。
2.1 プライマリ側の設定
# primary postgresql.conf
wal_level = replica # replica 以上(既定)
max_wal_senders = 10 # 同時WAL送信プロセス数(既定 10)
max_replication_slots = 10 # レプリケーションスロット数(既定 10)
-- 専用のレプリケーションロール(公式推奨:REPLICATION + LOGIN の最小権限)
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '***';
-- スロットを作っておくと、スタンバイが遅れてもWALを保持してくれる(§5)
SELECT pg_create_physical_replication_slot('standby1');
# primary pg_hba.conf(replication は専用キーワード。TLS強制+SCRAM)
hostssl replication replicator 10.0.0.0/24 scram-sha-256
2.2 スタンバイ側の構築
# プライマリからベースバックアップを取得し、復旧設定まで書き出す(-R)
pg_basebackup -h primary.internal -U replicator -D /var/lib/postgresql/18/main \
-Ft -z -X stream -c fast -R -S standby1
# -R: standby.signal と primary_conninfo を自動生成 / -S: 上で作ったスロットを使う
-R が生成する postgresql.auto.conf には、こう書かれます。
primary_conninfo = 'host=primary.internal user=replicator passfile=... sslmode=verify-full'
primary_slot_name = 'standby1'
そして standby.signal ファイルがあるため、起動するとスタンバイモードで立ち上がり、プライマリのWALを受信し続けます。hot_standby(既定 on)により、スタンバイで読み取りクエリを実行できます(§4)。
pg_ctl -D /var/lib/postgresql/18/main start
3. 同期 vs 非同期:耐久性とレイテンシのトレードオフ
「コミットを返す前に、スタンバイの複製を待つか」が同期/非同期です。既定は非同期——公式曰く「synchronous_standby_names に同期スタンバイ名が指定されていなければ、同期レプリケーションは無効で、コミットは複製を待たない。これが既定」。
synchronous_commit の値で耐久性の段階を選びます(既定 on)。下にいくほど強く・遅い。
| 値 | コミットが待つもの | 守られるもの |
|---|---|---|
off | 何も待たない(ローカルflushすら) | 最速。クラッシュで直近の数件を失い得る(破損はしない) |
local | ローカルのディスクflushのみ | 単一ノードの耐久性 |
remote_write | スタンバイがWALを受信・OS書き込み | スタンバイのPostgreSQLクラッシュには耐える(OSクラッシュは別) |
on(既定) | スタンバイがWALをディスクflush | プライマリ+全同期スタンバイが同時破損しない限り失わない |
remote_apply | スタンバイがWALを適用(可視化) | スタンバイのクエリに即反映。最も遅い |
# 「1件も失えない」要件:同期レプリケーション
synchronous_standby_names = 'standby1' # このスタンバイの確認を待つ
synchronous_commit = on # 受信+flush を待つ
設計判断:金融・決済のように「コミット=絶対に消えない」が要件なら同期(
on以上)。一般的なWebアプリは非同期で十分なことが多い(レイテンシ優先、わずかなデータ損失リスクを許容)。remote_applyはリードレプリカに即反映したいときだけ——commit遅延が大きくなります。
4. リードレプリカ:読み取りを逃がす(と、その代償)
ホットスタンバイは読み取り専用クエリを受けられます。公式:「hot_standby が真のとき、復旧が一貫状態に達すれば接続を受け付ける。すべての接続は厳密に読み取り専用(一時テーブルへの書き込みすら不可)」。重い集計・レポートをレプリカに逃がし、プライマリを書き込みに専念させる定番構成です。
ただしレプリケーション競合という固有の問題があります。公式:「スタンバイのクエリとWAL再生の競合の最も一般的な原因は『早期クリーンアップ』。…プライマリ側のクリーンアップ(VACUUM)が、スタンバイのトランザクションがまだ見ている行バージョンを削除してしまう可能性がある」。
スタンバイは「WAL再生を遅らせる」か「競合クエリをキャンセルする」を迫られます。2つのノブで調整します。
# ノブ1:競合時、どれだけWAL適用を遅らせるか(超えたらクエリをキャンセル)
max_standby_streaming_delay = 30s
# ノブ2:スタンバイの実行中クエリをプライマリにフィードバックし、VACUUMの早期クリーンを抑止
hot_standby_feedback = on # 既定 off
公式の警告:hot_standby_feedback = on は競合を消せるが「プライマリ側で不要行のクリーンアップが遅れ、テーブル肥大化を招き得る」。トレードオフです——長い分析クエリをレプリカで回すなら hot_standby_feedback、プライマリの肥大化を避けたいなら max_standby_streaming_delay で許容遅延を設計します。
5. WAL保持の安全装置:スロット vs wal_keep_size
スタンバイが遅れた/落ちた間に、プライマリが必要なWALを消すと、複製が壊れます。これを防ぐ2つの仕組み。
wal_keep_size(既定 0):pg_walに保持する最小サイズ。これを超えて遅れたスタンバイは、必要なWALを失い接続が切れる。固定バッファに過ぎない。- レプリケーションスロット:スタンバイが消費するまでWALを確実に保持する。より安全。
スロットの方が安全ですが、致命的な副作用があります。公式(max_slot_wal_keep_size、既定 -1):「-1 なら、レプリケーションスロットは無制限のWALを保持し得る」。つまりスタンバイが落ちたまま放置すると、プライマリの pg_wal が無限に膨れてディスクを食い潰す。
# スロットは使うが、保持上限を設けて pg_wal の暴走を防ぐ
max_slot_wal_keep_size = 64GB # これを超えたら、遅れたスロットは無効化(複製は切れるがDBは守る)
運用の鉄則:スロットは「便利だが危険」。必ず
max_slot_wal_keep_sizeで上限を設け、pg_replication_slotsの遅延を監視します。PostgreSQL 18 はidle_replication_slot_timeoutを追加し、長時間アイドルなスロットを自動無効化できるようになりました。
6. フェイルオーバー:PostgreSQLは自動でやってくれない
ここが最も誤解される点です。スタンバイを「昇格」させる操作自体は簡単。
-- スタンバイをプライマリに昇格(手動フェイルオーバー)
SELECT pg_promote();
-- または: pg_ctl promote -D <datadir>
しかし**「いつ昇格するか」を誰が決めるのか**。公式が明確に述べます。
PostgreSQL は、プライマリの障害を検知してスタンバイに通知するために必要なシステムソフトウェアを提供しない。
つまり自動フェイルオーバーはコアに無い。さらにスプリットブレイン(両方がプライマリだと思い込む)の危険:
古いプライマリに「お前はもうプライマリではない」と知らせる仕組みが必要だ。これは STONITH(Shoot The Other Node In The Head) として知られ、両系がプライマリと思い込んでデータ損失に至る事態を避けるために必要である。
公式は「そうしたツールは多数存在し、OS機能(IPアドレス移行など)とよく統合されている」と述べるにとどめます。実際には次の外部ツールで組みます(コミュニティ標準):
- Patroni(etcd/Consul と連携、デファクト)
- repmgr、pg_auto_failover
これがマネージドの最大の価値(運用ガイド §8)。RDS/Aurora の Multi-AZ は、この「障害検知+自動昇格+エンドポイント切替+STONITH相当」を代行します。自前運用なら、レプリケーション構築よりもこの自動フェイルオーバー基盤の構築・運用が本当のコストです。
7. 監視:遅延とスロットを見る
-- プライマリ側:各スタンバイの遅延(write/flush/replay lag)
SELECT application_name, state, sync_state,
write_lag, flush_lag, replay_lag
FROM pg_stat_replication;
-- スロットの滞留(restart_lsn が進まない=WALが溜まる)
SELECT slot_name, active, wal_status,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots;
replay_lag が継続的に増える=スタンバイが追いつけていない(I/O不足やレプリケーション競合)。retained_wal が増え続ける=スロットが滞留し pg_wal が膨張中——上限とアラートを必ず設定します。
8. まとめ
- 物理ストリーミングレプリケーションでHAを組む:
wal_level=replica、専用ロール、pg_basebackup -R、スロット。 - 同期/非同期は耐久性とレイテンシのトレードオフ。既定は非同期。
synchronous_commitを要件で選ぶ。 - リードレプリカは読み取りを逃がせるが、レプリケーション競合に注意(
max_standby_streaming_delay/hot_standby_feedback)。 - コアは自動フェイルオーバーを持たない(公式明記)。検知・昇格・STONITHは Patroni 等の外部ツールかマネージドで。
- スロットは安全だが暴走する——
max_slot_wal_keep_sizeで上限を設け、遅延を監視。
「障害で止めない」が固まったら、次は「変更でも止めない」——無停止のスキーマ変更、そして版跨ぎの論理レプリケーション&ゼロダウンタイム・アップグレード へ。