はじめに

本番環境でデータベースを運用する際、単一障害点(Single Point of Failure)の排除は避けて通れない課題です。プライマリサーバーに障害が発生した場合、サービスが完全に停止してしまうリスクを最小化するために、PostgreSQLではレプリケーション機能を使った高可用性構成を構築できます。

本記事では、PostgreSQLのストリーミングレプリケーションを中心に、プライマリ・スタンバイ構成の基本概念から実際の構築手順、同期・非同期レプリケーションの違い、フェイルオーバーの考え方まで体系的に解説します。

この記事を読むことで、以下のことができるようになります。

  • PostgreSQLにおけるレプリケーションの種類と特徴を説明できる
  • ストリーミングレプリケーションの仕組みを理解できる
  • プライマリ・スタンバイ構成を構築できる
  • 同期・非同期レプリケーションの使い分けを判断できる
  • フェイルオーバーの基本的な流れを理解できる

前提条件

  • PostgreSQL 14以降がインストールされていること
  • Linux(Ubuntu/CentOS)の基本的なコマンド操作ができること
  • PostgreSQLの基本操作(psqlでの接続、設定ファイルの編集)ができること
  • ネットワークの基礎知識があること

レプリケーションとは

レプリケーション(Replication)とは、データベースの内容を複数のサーバー間で複製・同期する仕組みです。レプリケーションを構成することで、以下のメリットを得られます。

メリット 説明
高可用性(HA) プライマリサーバー障害時にスタンバイへ切り替えてサービス継続
負荷分散 読み取りクエリをスタンバイサーバーに分散
データ保護 複数拠点にデータを保持し災害対策
メンテナンス ローリングアップデートによる無停止メンテナンス

PostgreSQLにおけるレプリケーションの種類

PostgreSQLでは複数のレプリケーション方式が提供されています。

flowchart TD
    A[PostgreSQL レプリケーション] --> B[物理レプリケーション]
    A --> C[論理レプリケーション]
    B --> D[ファイルベース<br/>ログシッピング]
    B --> E[ストリーミング<br/>レプリケーション]
    C --> F[パブリケーション/<br/>サブスクリプション]
方式 概要 ユースケース
ファイルベースログシッピング WALファイル単位で転送 シンプルな災害対策
ストリーミングレプリケーション WALレコードをリアルタイム転送 高可用性構成
論理レプリケーション 論理的な変更をパブリッシュ/サブスクライブ 部分的なデータ同期、異バージョン間移行

本記事では、最も一般的に使用されるストリーミングレプリケーションに焦点を当てて解説します。

プライマリとスタンバイの役割

レプリケーション構成における各サーバーの役割を整理します。

プライマリサーバー(Primary Server)

書き込み・読み取りの両方を処理するメインサーバーです。すべてのデータ変更はプライマリで行われ、その変更がスタンバイに伝播されます。

スタンバイサーバー(Standby Server)

プライマリからWAL(Write-Ahead Log)を受信し、継続的に適用するサーバーです。スタンバイは以下の2種類に分類されます。

種類 説明 読み取りクエリ
ウォームスタンバイ 昇格するまで接続を受け付けない 不可
ホットスタンバイ 読み取り専用クエリを受け付ける 可能

現在のPostgreSQLでは、ホットスタンバイがデフォルトで有効になっており、スタンバイサーバーを読み取り専用のレプリカとして活用できます。

ストリーミングレプリケーションの仕組み

ストリーミングレプリケーションは、プライマリサーバーで生成されたWALレコードを、ファイル単位ではなくレコード単位でスタンバイに転送する方式です。

WAL(Write-Ahead Log)とは

WALは、データベースへの変更を永続化する前にログファイルに書き出す仕組みです。PostgreSQLでは、すべてのデータ変更がまずWALに記録され、その後実際のデータファイルに反映されます。

sequenceDiagram
    participant Client as クライアント
    participant Primary as プライマリ
    participant WAL as WALバッファ
    participant Disk as ディスク
    participant Standby as スタンバイ

    Client->>Primary: INSERT/UPDATE/DELETE
    Primary->>WAL: WALレコード書き込み
    WAL->>Disk: WAL永続化
    Primary-->>Client: COMMIT応答
    WAL->>Standby: WALレコード転送
    Standby->>Standby: WAL適用

ストリーミングレプリケーションの通信

ストリーミングレプリケーションでは、スタンバイサーバーがプライマリサーバーに対してTCP接続を確立し、WALレコードをリアルタイムで受信します。

プライマリ側ではwalsenderプロセスが、スタンバイ側ではwalreceiverプロセスが動作し、これらがWALの送受信を担当します。

flowchart LR
    subgraph Primary["プライマリサーバー"]
        A[PostgreSQL] --> B[walsender]
        C[(WALファイル)]
    end
    subgraph Standby["スタンバイサーバー"]
        D[walreceiver] --> E[PostgreSQL]
        F[(WALファイル)]
    end
    B -->|"WALストリーム<br/>(TCP接続)"| D

ファイルベースとの比較

ストリーミングレプリケーションとファイルベースログシッピングの違いを整理します。

項目 ファイルベース ストリーミング
転送単位 WALファイル(16MB) WALレコード
遅延 ファイルが満たされるまで待機 ほぼリアルタイム(通常1秒未満)
データ損失リスク 最大1ファイル分の損失可能性 最小限
設定の複雑さ シンプル やや複雑
ネットワーク要件 断続的な接続で可 常時接続が必要

ストリーミングレプリケーションは、ファイルベースと組み合わせて使用することも可能です。ファイルベースをフォールバックとして設定しておくことで、ネットワーク断時にもWALアーカイブからリカバリできます。

プライマリ・スタンバイ構成の構築

実際にストリーミングレプリケーション環境を構築する手順を解説します。

環境の前提

本記事では以下の構成を想定します。

サーバー ホスト名 IPアドレス 役割
プライマリ primary 192.168.1.10 書き込み/読み取り
スタンバイ standby 192.168.1.20 読み取り専用

PostgreSQL 17を使用し、両サーバーにPostgreSQLがインストール済みの状態から始めます。

プライマリサーバーの設定

レプリケーション用ユーザーの作成

まず、スタンバイサーバーがプライマリに接続するための専用ユーザーを作成します。

1
2
-- プライマリサーバーで実行
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'your_secure_password';

REPLICATION権限は、WALストリームを読み取るために必要な特別な権限です。セキュリティの観点から、スーパーユーザーではなく専用のレプリケーションユーザーを使用することを推奨します。

postgresql.confの設定

プライマリサーバーのpostgresql.confを編集し、レプリケーションに必要な設定を行います。

1
2
# postgresql.confの場所を確認
sudo -u postgres psql -c "SHOW config_file;"

以下の設定を追加・変更します。

# 接続設定
listen_addresses = '*'                  # すべてのインターフェースでリッスン

# WAL設定
wal_level = replica                     # レプリケーションに必要なWALレベル
max_wal_senders = 5                     # 同時接続可能なwalsenderプロセス数
wal_keep_size = 1GB                     # 保持するWALサイズ(スタンバイ切断時のバッファ)

# レプリケーションスロット(推奨)
max_replication_slots = 5               # レプリケーションスロットの最大数

# アーカイブ設定(オプション、推奨)
archive_mode = on
archive_command = 'cp %p /var/lib/postgresql/wal_archive/%f'

各パラメータの意味を解説します。

パラメータ 説明
wal_level replica以上でレプリケーションに必要な情報がWALに記録される
max_wal_senders 同時に接続可能なスタンバイの数 + バックアップ用に余裕を持たせる
wal_keep_size スタンバイ切断時に保持するWALサイズ。再接続時のキャッチアップに使用
max_replication_slots レプリケーションスロットを使用する場合に必要

pg_hba.confの設定

スタンバイサーバーからのレプリケーション接続を許可します。

# pg_hba.confに追加
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     replicator      192.168.1.20/32         scram-sha-256

DATABASEフィールドにreplicationを指定することで、レプリケーション接続専用のルールとなります。

設定を反映するためにPostgreSQLを再起動します。

1
sudo systemctl restart postgresql

レプリケーションスロットの作成

レプリケーションスロットは、スタンバイが必要とするWALセグメントをプライマリが自動的に保持する仕組みです。スタンバイが長時間切断されても、必要なWALが削除されることを防ぎます。

1
2
-- プライマリサーバーで実行
SELECT * FROM pg_create_physical_replication_slot('standby_slot');
  slot_name   | lsn
--------------+-----
 standby_slot |
(1 row)

スタンバイサーバーの設定

ベースバックアップの取得

スタンバイサーバーを初期化するために、pg_basebackupを使用してプライマリのベースバックアップを取得します。

まず、スタンバイサーバーの既存データディレクトリをクリアします(新規構築の場合)。

1
2
3
# スタンバイサーバーで実行
sudo systemctl stop postgresql
sudo -u postgres rm -rf /var/lib/postgresql/17/main/*

ベースバックアップを取得します。

1
2
3
4
5
6
7
8
sudo -u postgres pg_basebackup \
    -h 192.168.1.10 \
    -D /var/lib/postgresql/17/main \
    -U replicator \
    -P \
    -R \
    -X stream \
    -S standby_slot

各オプションの意味は以下の通りです。

オプション 説明
-h プライマリサーバーのホスト名/IP
-D バックアップ先ディレクトリ
-U レプリケーションユーザー
-P 進捗表示
-R standby.signalファイルと接続情報を自動作成
-X stream バックアップ中のWALもストリーミングで取得
-S 使用するレプリケーションスロット名

-Rオプションを指定することで、以下のファイルが自動的に作成されます。

  • standby.signal: スタンバイモードで起動することを示すファイル
  • postgresql.auto.conf: primary_conninfoなどの接続設定

postgresql.auto.confの確認

-Rオプションで生成された設定を確認します。

1
cat /var/lib/postgresql/17/main/postgresql.auto.conf
# 自動生成される内容
primary_conninfo = 'user=replicator password=your_secure_password host=192.168.1.10 port=5432 sslmode=prefer'
primary_slot_name = 'standby_slot'

必要に応じてpostgresql.confに追加設定を行います。

# ホットスタンバイを有効化(読み取りクエリを受け付ける)
hot_standby = on

スタンバイサーバーの起動

1
sudo systemctl start postgresql

レプリケーション状態の確認

レプリケーションが正常に動作しているか確認します。

プライマリ側での確認

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-- プライマリサーバーで実行
SELECT 
    pid,
    usename,
    application_name,
    client_addr,
    state,
    sync_state,
    sent_lsn,
    write_lsn,
    flush_lsn,
    replay_lsn
FROM pg_stat_replication;

出力例:

  pid  | usename    | application_name | client_addr   | state     | sync_state |   sent_lsn    |  write_lsn    |  flush_lsn    |  replay_lsn
-------+------------+------------------+---------------+-----------+------------+---------------+---------------+---------------+---------------
 12345 | replicator | walreceiver      | 192.168.1.20  | streaming | async      | 0/5000060     | 0/5000060     | 0/5000060     | 0/5000060

各フィールドの意味:

フィールド 説明
state streamingなら正常に動作中
sync_state async(非同期)またはsync(同期)
sent_lsn 送信済みのWAL位置
replay_lsn スタンバイで適用済みのWAL位置

スタンバイ側での確認

1
2
3
4
5
6
-- スタンバイサーバーで実行
SELECT 
    pg_is_in_recovery() AS is_standby,
    pg_last_wal_receive_lsn() AS receive_lsn,
    pg_last_wal_replay_lsn() AS replay_lsn,
    pg_last_xact_replay_timestamp() AS last_replay_time;
 is_standby |  receive_lsn  |  replay_lsn   |      last_replay_time
------------+---------------+---------------+-------------------------------
 t          | 0/5000060     | 0/5000060     | 2026-01-03 18:30:45.123456+09

pg_is_in_recovery()trueを返せば、スタンバイモードで動作しています。

レプリケーション遅延の確認

1
2
3
4
5
6
-- プライマリサーバーで実行
SELECT 
    client_addr,
    pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lag_bytes,
    pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) / 1024 / 1024 AS replay_lag_mb
FROM pg_stat_replication;

同期・非同期レプリケーション

ストリーミングレプリケーションには、同期モードと非同期モードの2種類があります。それぞれの特徴を理解し、要件に応じて使い分けることが重要です。

非同期レプリケーション

デフォルトの動作モードです。プライマリはトランザクションをコミットした後、スタンバイへのWAL転送完了を待たずにクライアントに応答を返します。

sequenceDiagram
    participant C as クライアント
    participant P as プライマリ
    participant S as スタンバイ

    C->>P: COMMIT
    P->>P: WAL書き込み
    P-->>C: COMMIT完了
    P->>S: WAL転送(非同期)
    S->>S: WAL適用

メリット

  • 低レイテンシ: スタンバイの応答を待たないため高速
  • スタンバイ障害に強い: スタンバイが停止してもプライマリの性能に影響しない

デメリット

  • データ損失の可能性: プライマリ障害時、未転送のWALが失われる可能性がある

同期レプリケーション

プライマリがクライアントにCOMMIT応答を返す前に、少なくとも1台の同期スタンバイからWAL受信の確認を待ちます。

sequenceDiagram
    participant C as クライアント
    participant P as プライマリ
    participant S as スタンバイ

    C->>P: COMMIT
    P->>P: WAL書き込み
    P->>S: WAL転送
    S->>S: WAL書き込み
    S-->>P: ACK(確認応答)
    P-->>C: COMMIT完了
    S->>S: WAL適用

メリット

  • データ損失ゼロ: コミット済みのトランザクションは必ずスタンバイにも存在する
  • 高い耐久性: プライマリとスタンバイの同時障害でない限りデータを保護

デメリット

  • レイテンシ増加: スタンバイの応答を待つため、ネットワーク遅延が性能に直結
  • 可用性への影響: 同期スタンバイがすべて停止するとプライマリのコミットもブロック

同期レプリケーションの設定

プライマリサーバーのpostgresql.confsynchronous_standby_namesを設定します。

# スタンバイのapplication_nameを指定
synchronous_standby_names = 'standby1'

スタンバイ側ではprimary_conninfoapplication_nameを追加します。

primary_conninfo = 'user=replicator password=xxx host=192.168.1.10 port=5432 application_name=standby1'

synchronous_commitの設定

synchronous_commitパラメータで、同期レプリケーションの強度を調整できます。

設定値 動作 データ損失リスク
off WAL書き込みを待たない 高(プライマリクラッシュで損失)
local ローカルWAL書き込みのみ待機 中(プライマリ障害時に損失)
remote_write スタンバイのOS層での受信を待機 低(スタンバイOSクラッシュで損失)
on スタンバイのディスク書き込みを待機 最小(同時障害でのみ損失)
remote_apply スタンバイでのWAL適用完了を待機 最小 + 即座に読み取り可能

トランザクション単位で変更することも可能です。

1
2
3
4
5
6
7
8
9
-- 重要なトランザクションは同期で
SET synchronous_commit = on;
INSERT INTO critical_data VALUES (...);
COMMIT;

-- 一時データは非同期で高速化
SET synchronous_commit = local;
INSERT INTO temp_logs VALUES (...);
COMMIT;

複数スタンバイの同期設定

複数のスタンバイサーバーがある場合、優先順位ベースまたはクォーラムベースで同期スタンバイを指定できます。

優先順位ベース(FIRST)

# s1, s2のうち最初の2台を同期スタンバイとして使用
synchronous_standby_names = 'FIRST 2 (s1, s2, s3)'

クォーラムベース(ANY)

# s1, s2, s3のうち任意の2台からの確認を待機
synchronous_standby_names = 'ANY 2 (s1, s2, s3)'

クォーラムベースでは、特定のスタンバイに依存せず、指定した数のスタンバイから確認が得られればコミットが完了します。地理的に分散したスタンバイ構成で有効です。

フェイルオーバーの考え方

フェイルオーバーとは、プライマリサーバーに障害が発生した際にスタンバイサーバーを新しいプライマリに昇格させ、サービスを継続する操作です。

フェイルオーバーの基本フロー

flowchart TD
    A[プライマリ障害発生] --> B{障害検知}
    B --> C[スタンバイを昇格]
    C --> D[アプリケーション接続先変更]
    D --> E[サービス継続]
    E --> F[旧プライマリの処理]
    F --> G{復旧可能?}
    G -->|Yes| H[スタンバイとして再構築]
    G -->|No| I[新規スタンバイ構築]

スタンバイの昇格方法

PostgreSQLでは、以下の方法でスタンバイをプライマリに昇格できます。

pg_ctl promoteコマンド

1
2
# スタンバイサーバーで実行
sudo -u postgres pg_ctl promote -D /var/lib/postgresql/17/main

pg_promote()関数

1
2
-- スタンバイサーバーで実行
SELECT pg_promote();

昇格が完了すると、standby.signalファイルが削除され、サーバーは読み書き可能なプライマリとして動作を開始します。

昇格後の確認

1
2
-- 昇格後のサーバーで実行
SELECT pg_is_in_recovery();  -- falseが返れば昇格成功

スプリットブレインの防止

フェイルオーバーで最も注意すべき問題が「スプリットブレイン」です。これは、旧プライマリと新プライマリの両方が同時に書き込みを受け付けてしまう状態を指します。

flowchart LR
    subgraph Problem["スプリットブレイン状態"]
        A[旧プライマリ] -->|"書き込み"| C[(データA)]
        B[新プライマリ] -->|"書き込み"| D[(データB)]
    end
    E[データ不整合発生]
    C --> E
    D --> E

スプリットブレインを防ぐための対策を紹介します。

STONITH(Shoot The Other Node In The Head)

旧プライマリを確実に停止させる仕組みです。フェンシング(fencing)とも呼ばれます。

  • 電源管理ユニット(PDU)による強制電源断
  • IPMIによるリモートシャットダウン
  • ストレージフェンシング

自動フェイルオーバーツールの使用

本番環境では、手動フェイルオーバーではなく自動フェイルオーバーツールの使用を推奨します。

ツール 特徴
Patroni etcd/Consul/ZooKeeperを使用した分散合意ベース
repmgr PostgreSQL専用のレプリケーション管理ツール
Pgpool-II コネクションプーリング機能も備えた統合ツール
pg_auto_failover Citus製のシンプルな自動フェイルオーバー

旧プライマリの再統合

フェイルオーバー後、旧プライマリを新しいスタンバイとして再構築する方法は2つあります。

pg_basebackupによる再構築

新プライマリからベースバックアップを取得し、スタンバイとして構築し直します。確実ですが、データ量が多いと時間がかかります。

pg_rewindによる高速再同期

pg_rewindは、旧プライマリと新プライマリの差分を巻き戻し、旧プライマリをスタンバイとして再接続可能にするツールです。

1
2
3
# 旧プライマリで実行(PostgreSQLは停止状態で)
pg_rewind --target-pgdata=/var/lib/postgresql/17/main \
          --source-server="host=192.168.1.20 user=postgres dbname=postgres"

pg_rewindを使用するには、以下の条件が必要です。

  • wal_log_hints = onまたはdata_checksumsが有効
  • 旧プライマリで未適用のWALが新プライマリに存在する

レプリケーション構成のベストプラクティス

レプリケーションスロットの活用

レプリケーションスロットを使用することで、スタンバイが必要とするWALが自動的に保持されます。ただし、スタンバイが長期間切断されると、WALが蓄積しディスクを圧迫する可能性があります。

# WAL蓄積の上限を設定
max_slot_wal_keep_size = 10GB

監視項目

レプリケーション環境では、以下の項目を継続的に監視することを推奨します。

監視項目 確認方法 警告しきい値(例)
レプリケーション遅延 pg_stat_replication 1MB以上
スタンバイ接続状態 pg_stat_replication.state streaming以外
WALディスク使用量 pg_walディレクトリサイズ 80%以上
レプリケーションスロット状態 pg_replication_slots inactive

ネットワーク設計

レプリケーション用のネットワークは、アプリケーション通信と分離することを推奨します。

flowchart TB
    subgraph Network["ネットワーク構成"]
        subgraph Primary["プライマリ"]
            P1[eth0: アプリ用<br/>192.168.1.10]
            P2[eth1: レプリ用<br/>10.0.0.10]
        end
        subgraph Standby["スタンバイ"]
            S1[eth0: アプリ用<br/>192.168.1.20]
            S2[eth1: レプリ用<br/>10.0.0.20]
        end
    end
    P2 <-->|"レプリケーション<br/>(専用セグメント)"| S2

まとめ

本記事では、PostgreSQLのストリーミングレプリケーションを中心に、高可用性構成の基礎を解説しました。

学習した内容を振り返ります。

トピック ポイント
レプリケーションの種類 物理(ストリーミング)と論理の違いを理解する
ストリーミングレプリケーション WALレコード単位のリアルタイム転送で遅延を最小化
構築手順 レプリケーションユーザー作成、設定変更、pg_basebackupで初期化
同期・非同期 データ損失リスクとレイテンシのトレードオフ
フェイルオーバー pg_ctl promote/pg_promote()で昇格、スプリットブレイン対策が重要

本番環境での運用においては、本記事で紹介した手動フェイルオーバーではなく、Patroniなどの自動フェイルオーバーツールの導入を検討してください。また、定期的なフェイルオーバー訓練を実施し、実際の障害時に確実に対応できる体制を整えることが重要です。

参考リンク