Skip to content

PostgreSQL 9.1 ストリーミングレプリケーション対応 リソースエージェント (for pgrex branch)

t-matsuo edited this page Feb 15, 2012 · 3 revisions

(本ドキュメントは執筆途中です。)

pg-rexブランチはメンテナンスバージョンで、大きな仕様変更を行う予定はありません。 pgsql91ブランチは開発バージョンです。

バグ報告 https://github.com/t-matsuo/resource-agents/issues

概要

PostgreSQL 9.0 の新機能として、非同期レプリケーションがサポートされましたが、さらに9.1では同期レプリケーションがサポートされました。ここでは、PostgreSQL 9.1の同期および非同期レプリケーション機能をオープンソースのHAクラスタソフト"Pacemaker"を用いて、クラスタ化するためのリソースエージェント(RA)を、Heartbeatコミュニティのpgsqlリソースエージェントをベースに開発しています。

用語

まずはじめに、本ドキュメントで使用する用語について整理しておきます。

  • RA
    • リソースエージェント
  • Master, Slave
    • Pacemaker の Master/Slave リソースの各状態
  • PRI
    • PostgreSQLがPrimaryで動作している状態。PRIでは通常のPostgreSQLと同様にRead/Writeのリクエストを処理可能で、レプリケーション用のデータを送信することができます。基本的にPacemakerのMasterと状態が一致します。なお、同期レプリケーション時は、レプリケーション先のPostgreSQLからの応答がなくなると、トランザクションが停止します。
  • HS
    • PostgreSQLがHot Standbyで動作している状態。Readのリクエストしか処理できません。PRIに接続することで、レプリケーションのデータを受け取ることができます。PacemakerはMaster->Slaveと状態遷移しますが、PostgreSQLはPRIからHSに直接状態遷移できないため、PacemakerのSlaveと必ずしも状態が一致しているとは限りません。
  • 非同期モード
    • RAのパラメータ rep_mode に async を設定した場合のモードです。PostgreSQLの非同期レプリケーションを、RAでHAクラスタ化していることを示します。HSが正常に非同期レプリケーションで動作している場合のみ、Masterのフェイルオーバが可能です。
  • 同期モード
    • RAのパラメータ rep_mode に sync を設定した場合のモードです。PostgreSQLの同期レプリケーションを、RAでHAクラスタ化していることを示します。ただし、同期レプリケーションで動くのは、HSが正常に同期レプリケーション状態に入った状態の時のみで、HS故障時やレプリケーション用LAN切断時などは非同期レプリケーションで動きます(自動切替)。HSが同期レプリケーションで動いている場合のみ、Masterのフェイルオーバが可能です。
  • レプリケーションモード
    • 非同期モード、同期モードを総称してレプリケーションモードと呼びます。
  • レプリケーションモード切替
    • 同期モード時に、同期レプリケーションと非同期レプリケーションを切り替える操作を示します。
  • D-LAN
    • データレプリケーションのパケットを流すLAN。二重化したい場合はbondingを使用してください。
  • S-LAN
    • サービスを提供するためのLAN。今までのAct-Standby構成において、仮想IPを付与していたLANと同等です。二重化したい場合はbondingを使用してください。
  • IC-LAN
    • Pacemakerのハートビート通信のパケットを流すLAN(インターコネクトLAN)。二本以上用意することを推奨します。

機能

Masterのフェイルオーバ

PRIとして動作しているPostgreSQLをMaster、HSで動作しているPostgreSQLをSlaveとして管理し、PRIが故障した際、RAはこの故障を検知しPRIを停止、HSを新たなPRIに昇格(promote)させます。

初期起動時のデータ新旧の自動判別

初期起動時に同時に複数のノードのPacemakerを起動した際、お互いのデータの新旧(last xlog replay location)を比較し、新しいデータのノードをPRIとして動作させます。 1ノードでのみPacemakerを起動した場合、初めての起動の場合はそのノードがPRIになります。2回目移行の起動時は、以前停止した時のデータの状態を元に、判断します。

同期レプリケーション利用時のレプリケーションモード切替

同期モードの場合に動く機能です。PostgreSQL 9.1の同期レプリケーションは、HSが故障したり、D-LA切N断が発生したりすると、PRIのトランザクションが停止します。つまりD-LANやHS故障時にサービスの停止が発生します。そのため、初期起動時やこれらの故障発生時に、同期レプリケーションから非同期レプリケーションに動的に切り替え、サービスが停止し続けるのを防ぎます。

Readの負荷分散

HSはRead onlyのSQLを処理することができますので、Read only用の仮想IPを別途つけてあげることで、Readの負荷分散が可能です。

制限事項

  • PostgreSQLの仕様上、Master(PRI)のスイッチオーバーやPostgreSQLの再起動を実現するには、PRIのWALアーカイブを共有する必要があります。方法としては、postgresq.conf の archive_command の設定で、scpやrsyncを使ってHSにWALアーカイブを転送したり、共有ディスクやNFS等を使ってアーカイブを共有したりする方法が考えられますが、RAとしてはどのような方法で共有するかは問いません。任意の方法で共有を行ってください。
    • (注意) 2011年10月現在の最新バージョンPostgreSQL 9.1.1では、本体バグにより、正常にfast shutdownを行っても、データがロストする問題があるため、スイッチオーバーに失敗します。
      • ※アプリケーションで書き込み成功したデータがロストするわけではありません。通常fast shutdownはWALを全てHSに転送してから終了しますがこれに失敗します。アプリケーション的には書き込み失敗と見えるので問題ありませんが、PRIにはデータが存在し、HSにはデータが存在しない状態が発生します。
  • PostgreSQLはpromote時にTimelineIDがインクリメントされます。上記のようなWALアーカイブを共有をしない場合、HSのPostgreSQLはPRIのTimelineIDに追いつくことができず、レプリケーション接続できなくなります。このような場合、HSを起動する前に、PRIの起動確認後、手動でPRIのデータをHSにコピーする必要が出てきます。
  • MasterにはS-LANだけでなく、D-LANにも仮想IPを付与する必要があります。

パラメータ

  • 元のpgsqlのパラメータに加えて以下のパラメータが追加されています。なお、元のpgsqlパラメータのmonitor_sqlは、レプリケーションモードでは使用できません。
    • rep_mode
      • none/async/sync から選択します。noneがデフォルトで、noneの場合は今までのpgsql RAと同じ動作です。asyncが非同期モード、syncが同期モードです。これら二つのレプリケーションモード時は、下記のnode_list, master_ip, restore_command のパラメータが必須です。同期モード時は、postgresql.conf の include ディレクティブで、rep_mode.confファイルをインクルードする必要があります。rep_mode.confには、レプリケーションモード切替時に設定変更が必要なパラメータ"synchronous_standby_names"がRAにより自動で書き込まれ、リロードされます。そのため、postgresql.confにこのパラメータは記述してはいけません。このファイルはroot, DBの所有者(通常はpostgres)ユーザ両方が読み書きできる必要があります。
    • node_list (必須)
      • レプリケーションに参加させる全ノードの一覧です。スペース区切りで全ノード名(uname -n コマンドの結果)を記述します。
    • master_ip (必須)
      • D-LANに使用するMasterの仮想IPを指定します。
    • restore_command (必須)
      • HSで起動する際にrecovery.confに記述するrestore_commandを指定します。
    • repuser
      • SlaveがMasterに接続する際に使用するユーザを指定します。デフォルトは postgres です。
    • stop_on_demote
      • PostgreSQLの仕様によりdemote処理でPRIをHSに直接遷移させることができないため、デフォルトでの動作ではPostgreSQLを再起動しますが、"yes"に設定すると再起動ではなく停止します。これは巨大なデータベースになると、再起動時のリカバリ処理に時間がかかり、フェイルオーバに時間を要してしまうことを避けるのに有効です。さらにこのパラメータが"yes"の場合、stop処理において通常のfast shutdownではなくimmediate shutdownが使用されます。
    • primary_conninfo_opt
      • RAは、HSで動かすために必要な設定ファイルrecovery.confを自動で作成します。この時recovery.confのprimary_conninfoのhost, port, user,application_nameに値を設定します。これら以外に追加パラメータを設定したい場合はここで指定します。
    • tmpdir
      • PostgreSQLの制御に必要な一時ファイルを作成できるディレクトリを指定します。デフォルトは/var/lib/pgsql ディレクトリです。rootとDBの所有者(通常はpostgresユーザ)が読み書きできる必要があります。異常シャットダウン時にPostgreSQLの起動を抑止するための"PGSQL.lock"ファイルや、同期・非同期モードを切り替えるための"rep_mode.conf"ファイル、データの新旧を判別するための一時ファイルが作成されます。
    • pgctldata
      • pg_controldataコマンドへのパスを指定します。デフォルトは/usr/bin/pg_controldataです。
    • xlog_check_count
      • 初期起動時に自分のデータの位置を判別するためにデータの位置(xlog location)をチェックする回数(1回のmonitorで1回チェック)を指定します。デフォルトは3(回)です。

インストール

ここでは本RA特有の設定について主に記述しています。PostgreSQLやPacemakerのインストール、基本操作等は他のドキュメントを参考にしてください。

本ドキュメントでの想定構成は以下とします。

  • レプリケーションモードは同期モードとする。
  • WALアーカイブの共有には、scpを使用する。
  • ノード名はpm01,pm02とする。
  • S-LAN IPは、192.168.0.1, 192.168.0.2 とする。
  • 仮想IP(Master)は192.168.0.201とする
  • 仮想IP(Slave)は192.168.0.202とする
  • IC-LANは、192.168.1.1, 192.168.1.2 と、192.168.2.1, 192.168.2.2 とする。
  • D-LANは、192.168.3.1, 192.168.3.2 とする。
  • 仮想IP(D-LANのMaster)は、192.168.3.200とする。
  • PostgreSQLのインストール先は/usr/local/pgsql/とします。
  • PostgreSQLデータ領域は/var/lib/pgsql/9.1/data/とし、アーカイブ領域は/var/lib/pgsql/9.1/data/pg_archiveとします。
  • 説明の都合上STONITHは設定しませんが、商用利用時にはSTONITHを使うことを強くお勧めします。

構成図 図

インストール方法は以下の通りです。

PotgreSQLの設定 (重要な点のみ)

  • postgresql.conf 設定のポイント * "synchronous_standby_names"パラメータがある場合は、削除します。 * listen_addressに固定のIPを書くことはできません。 * replication_timeout は、レプリケーション用LANが切断した場合の検知時間で、wal_receiver_status_interval は、HSがPRIに接続を試みる間隔です。デフォルトでは検知に時間がかかってしまうため、検知を短くしたい場合は短く指定します。
    • デフォルトはtcpがタイムアウトするまで待つのかも(?) * postgresql.confから/var/lib/pgsql/rep_mode.confをincludeします。このファイルにRAがsynchronous_standby_namesを動的に書き込みます。 * WALアーカイブをPRIとHSで共有します。ここでは、archive_command で、PRI上にアーカイブするのと同時に、HS側へもscp送信する例を示しています。そのためには、DB実行ユーザ(postgres)がpm01とpm02でsshの鍵交換しており、パスワードなしでログインできるようにしておいてください。

以下、主な設定箇所の抜粋です。その他のパラメータの設定については、PostgreSQLのマニュアルを参考にして構築し、PostgreSQL単体で起動して、レプリケーションが可能であることまで事前に確認しておいてください。 ※PostgreSQLはあまり詳しくないので、他にも設定しておいた方がよいパラメータがある場合はLinux-HA JapanのMLまでお願いします。

レプリケーション参考

pm01のpostgresql.conf抜粋

listen_addresses = '*'
wal_level = hot_standby
synchronous_commit = on
archive_mode = on
archive_command = 'cp %p /var/lib/pgsql/9.1/data/pg_archive/%f && scp -q %p postgres@192.168.3.2:/var/lib/pgsql/9.1/data/pg_archive/%f'
max_wal_senders=5
wal_keep_segments = 32
hot_standby = on
include = '/var/lib/pgsql/rep_mode.conf'
restart_after_crash = off
replication_timeout = 5000         # mseconds
wal_receiver_status_interval = 2   # seconds

pm02のpostgresql.conf抜粋(scpのIPのみ異なる)

listen_addresses = '*'
wal_level = hot_standby
synchronous_commit = on
archive_mode = on
archive_command = 'cp %p /var/lib/pgsql/9.1/data/pg_archive/%f && scp -q %p postgres@192.168.3.1:/var/lib/pgsql/9.1/data/pg_archive/%f'
max_wal_senders=5
wal_keep_segments = 32
hot_standby = on
include = '/var/lib/pgsql/rep_mode.conf'
restart_after_crash = off
replication_timeout = 5000         # mseconds
wal_receiver_status_interval = 2   # seconds

  • pg_hba.conf * ※セキュリティは考慮していません:-)

host    all             all     127.0.0.1/32        trust
host    replication     all     192.168.3.0/24      trust
  • recovery.conf
    • 必要ありません。RAが自動で作りますので、あれば削除します。PostgreSQL単体で動作確認が必要な場合は以下の内容のファイルを作ってpm02上の/var/lib/pgsql/9.1/data/に置いてみてください。(pm02をHSで動作させる場合)

standby_mode = 'on'
primary_conninfo = 'host=192.168.3.1 port=5432 user=postgres application_name=pm02'
restore_command = 'cp /var/lib/pgsql/9.1/data/pg_archive/%f %p'
recovery_target_timeline = 'latest'

設定後、PostgreSQLを起動し、レプリケーションが正常にできることを確認してください。

Pacemaker

  • Linux-HA Japan 提供のPacemakerをインストールします。(動作確認は1.0.11で実施してます:-)
  • /usr/lib/ocf/resource.d/heartbeat/pgsql だけ、このファイルと入れ替えます。※rawをクリックするとファイルをダウンロードできます。
    • 本家コミュニティとの議論を反映中の開発版はこちら
  • Pacemaker起動後、本ドキュメントの最後に記述しているサンプルの設定CRMファイルを自分の環境にあわせて変更し、流し込みます。

状態について

RAは以下の状態をPacemakerのノード属性値として定義します。 ※属性は"crm_mon -A"で見ることができます。

pgsql-status

PRI,HSどちらのノードでも表示される属性値で、PostgreSQLの現在の状態を示します。

  • STOP
    • PostgreSQLが停止している状態です。
  • UNKNOWN
    • PostgreSQLの停止に失敗し状態不明な状態です。
  • HS:alone
    • HSとして動作しているが、PRIに接続していない状態です。
  • HS:connected
    • HSとして動作し、PRIに接続しているが、正常なレプリケーション状態になっていない(PRIにデータが追いついていない)状態です。
    • 同期モードでは、PRIでの"select state,sync_state from pg_stat_replication" の実行結果が STREAMING|SYNC, STREAMING|ASYNC STREAMING|POTENTIAL以外の場合、非同期モードではSTREAMING|ASYNC以外の場合に表示されます。
  • HS:async
    • HSとして動作し、非同期レプリケーション状態です。非同期モードでRAを使用している場合、Masterに昇格できます。
  • HS:sync
    • HSとして動作し、同期レプリケーション状態です。同期モードの場合、Masterに昇格できます。
  • HS:potential
    • 3ノード以上において、同期モードで動かした場合(Master x1, Slave x2)に表示されます。PostgreSQLの仕様で、実際に同期レプリケーションとして動かすことができるHSは1ノードのみで、残りはこのPOTENTIALというモードになります。
  • PRI
    • PRIで動作している状態です。

pgsql-data-status

データの遷移状態を示します。本状態は、次回起動時に、データが最新かどうか判断するために使用されます。

  • DISCONNECT
    • HSの故障やD-LAN故障等で、PRIからHSが認識できなくなった場合に遷移します。
  • {state}|{sync_state}
    • HSがPRIに接続している場合に遷移します。
    • PRI上での"select state,sync_state from pg_stat_replication"の結果がそのまま属性値として表示されます。
    • {state}には、INIT, CATCHUP, STREAMING が、{sync_state}には、ASYNC, POTENTIAL, SYNCが表示されます。(これで全部?)
  • LATEST
    • Master(PRI)になった場合に遷移します。

これらの状態は、最終的なデータの遷移状態を示すもので、実際のデータの状態と必ずしも一致するわけではありません。 例えばPRIで動作している時は"LATEST"という状態に遷移しますが、ノード停止やダウン時もこの"LATEST"という状態が保持されます。つまり自分で"DISCONNECT"に遷移することはありません。この後、他のノードが新しくMasterになった場合、この新しいMasterが旧Masterの状態を"DISCONNECT"に変更します。逆に誰もMasterになれない場合は、この"LATEST"が保持され続けることになります。

よって、次回起動時に自分のデータが"LATEST"ならば、自分が最も新しいデータを持っていると判断できますし、"DISCONNECT"ならば、自分のデータは古いと判断できます。これにより、古いデータでのサービス再開を防ぐことができます。

pgsql-master-baseline

Masterノードに表示される属性値で、promote 直前のTimeline IDとxlog receive locationの値です。他のノードがdemoteした場合もdemote時のPostgreSQL再起動に備えて一時的に表示されます。新しくSlave起動する際、SlaveのPostgreSQLのTimeline IDがこの値より小さい場合は起動失敗になります。Timeline IDが同じであっても、checkpointがこの値より新しいとデータ不整合と見なし起動失敗になります。Timeline IDがこの値より大きい場合は、Primaryのデータをリストアしたと見なし、起動成功になります。

pgsql-xlog-loc

起動時にMasterがどこにも存在しない場合に表示されます。自分がMasterになれるかどうかを決定するために、自分のlast xlog replay locationかlast xlog receive locationの新しい方の値を他のノードに提示するために設定されます。他のノードと自分のノードのこの値を比較し、自分が最も新しい場合にMaster(PRI)になるための権限を得ることができます。ただし上記のpgsql-data-statusの状態で、データが古いと判断できた場合は、Masterになることはできません。

crm_mon の表示例

正常に同期レプリケーションで動作している場合

============
Last updated: Wed Jul 11 11:11:11 2011
Stack: Heartbeat
Current DC: pm02 (11111111-1111-1111-1111-111111111111) - partition with quorum
Version: 1.0.11-1554a83db0d3c3e546cfd3aaff6af1184f79ee87
2 Nodes configured, unknown expected votes
5 Resources configured.
============

Online: [ pm01 pm02 ]

vip-master      (ocf::heartbeat:IPaddr2):       Started pm01
vip-rep (ocf::heartbeat:IPaddr2):       Started pm01
vip-slave       (ocf::heartbeat:IPaddr2):       Started pm02
 Master/Slave Set: msPostgresql
     Masters: [ pm01 ]
     Slaves: [ pm02 ]
 Clone Set: clnPingCheck
     Started: [ pm01 pm02 ]

Node Attributes:
* Node pm01:
    + pm02-eth1                         : up
    + pm02-eth3                         : up
    + default_ping_set                  : 100
    + master-postgresql:0               : 1000
    + pgsql-data-status                 : LATEST
    + pgsql-status                      : PRI
* Node pm02:
    + pm01-eth1                         : up
    + pm01-eth3                         : up
    + default_ping_set                  : 100
    + master-postgresql:1               : 100
    + pgsql-data-status                 : STREAMING|SYNC
    + pgsql-status                      : HS:sync

Migration summary:
* Node pm02:
* Node pm01:

設定CRMサンプル(同期モード)

Master/Slave 2台構成

property \
	no-quorum-policy="ignore" \
	stonith-enabled="false" \
    crmd-transition-delay="2s"

rsc_defaults \
	resource-stickiness="INFINITY" \
	migration-threshold="1"

ms msPostgresql postgresql \
	meta \
		master-max="1" \
		master-node-max="1" \
		clone-max="2" \
		clone-node-max="1" \
		notify="true"

clone clnPingCheck pingCheck
group master-group vip-master vip-rep

primitive vip-master ocf:heartbeat:IPaddr2 \
	params \
		ip="192.168.0.200" \
		nic="eth0" \
		cidr_netmask="24" \
	op start   timeout="60s" interval="0s"  on-fail="restart" \
	op monitor timeout="60s" interval="10s" on-fail="restart" \
	op stop    timeout="60s" interval="0s"  on-fail="block"

primitive vip-rep ocf:heartbeat:IPaddr2 \
	params \
		ip="192.168.3.200" \
		nic="eth3" \
		cidr_netmask="24" \
	op start   timeout="60s" interval="0s"  on-fail="restart" \
	op monitor timeout="60s" interval="10s" on-fail="restart" \
	op stop    timeout="60s" interval="0s"  on-fail="block"

primitive vip-slave ocf:heartbeat:IPaddr2 \
	params \
		ip="192.168.0.202" \
		nic="eth0" \
		cidr_netmask="24" \
    meta \
        resource-stickiness="1" \
	op start   timeout="60s" interval="0s"  on-fail="restart" \
	op monitor timeout="60s" interval="10s" on-fail="restart" \
	op stop    timeout="60s" interval="0s"  on-fail="block"

primitive postgresql ocf:heartbeat:pgsql \
	params \
		pgctl="/usr/local/pgsql/bin/pg_ctl" \
		psql="/usr/local/pgsql/bin/psql" \
		pgctldata="/usr/local/pgsql/bin/pg_controldata" \
		pgdata="/var/lib/pgsql/9.1/data/" \
		start_opt="-p 5432" \
		rep_mode="sync" \
        node_list="pm01 pm02" \
        restore_command="cp /var/lib/pgsql/9.1/data/pg_archive/%f %p" \
        master_ip="192.168.3.200" \
	op start   timeout="60s" interval="0s"  on-fail="restart" \
	op monitor timeout="60s" interval="7s" on-fail="restart" \
	op monitor timeout="60s" interval="2s"  on-fail="restart" role="Master" \
	op promote timeout="60s" interval="0s"  on-fail="restart" \
	op demote  timeout="60s" interval="0s"  on-fail="block" \
	op stop    timeout="60s" interval="0s"  on-fail="block" \
	op notify  timeout="60s" interval="0s"

primitive pingCheck ocf:pacemaker:pingd \
	params \
		name="default_ping_set" \
		host_list="192.168.0.254" \
		multiplier="100" \
	op start   timeout="60s" interval="0s"  on-fail="restart" \
	op monitor timeout="60s" interval="10s" on-fail="restart" \
	op stop    timeout="60s" interval="0s"  on-fail="ignore"

location rsc_location-1 vip-slave \
	rule  200: pgsql-status eq "HS:sync" \
	rule  100: pgsql-status eq "PRI" \
	rule  -inf: not_defined pgsql-status \
	rule  -inf: pgsql-status ne "HS:sync" and pgsql-status ne "PRI"

location rsc_location-2 msPostgresql \
	rule role=master  200: #uname eq pm01 \
	rule role=master  100: #uname eq pm02 \
	rule role=master -inf: defined fail-count-vip-master \
	rule role=master -inf: defined fail-count-vip-rep \
	rule -inf: not_defined default_ping_set or default_ping_set lt 100

colocation rsc_colocation-1 inf: msPostgresql        clnPingCheck
colocation rsc_colocation-2 inf: master-group        msPostgresql:Master

order rsc_order-1 0: clnPingCheck          msPostgresql
order rsc_order-2 0: msPostgresql:promote  master-group:start   symmetrical=false
order rsc_order-3 0: msPostgresql:demote   master-group:stop    symmetrical=false

運用時のQ&A

  • 両ノードのpgsql-data-statusが"DISCONNECT"状態になって、Masterになれない場合は?
    • ログ等を確認し、古いデータの方のPacemakerを停止します。
    • 停止後、新しいデータの方のPacemakerのpgsql-data-statusを"LATEST"に変更します。
      • crm_attribute -l forever -N ノード名 -n "pgsql-data-status" -v "LATEST"

  • rsyncでデータ同期する場合のオプションは?
    • --delete オプションを必ずつけましょう。このオプションをつけないと不要なファイルが削除されず、データベースが異常な状態になってしまいます。
      • (例) rsync -avr --delete 192.168.3.2:/var/lib/pgsql/9.1/data/ /var/lib/pgsql/9.1/data/
      • パスはディレクトリを指定せずに * (アスタリスク) でファイルを指定すると、--delete が意味をなさないので注意。
  • arvhive_command に scp を設定したくないのだけど・・・
    • WALアーカイブを共有しない場合は、両ノードの同時起動はできません。PRI起動確認後、データを手動で同期しHSを起動します。