2022年10月1日土曜日

PostgreSQLのDBを手動フェイルオーバーする手順 (ストリーミングレプリケーション構成)

以下記事にてPostgreSQLをストリーミングレプリケーションで構成する手順を記載した。

ストリーミングレプリケーション構成では、更新・参照が可能なマスターと、参照のみ可能なスタンバイの構成となるが、マスターのDBが障害により停止した場合は、スタンバイのDBを更新可能とする必要がある。このようにスタンバイのDBをマスターDBとして昇格する手順を「フェイルオーバー」と呼ぶ。

PostgreSQLは自動でフェイルオーバーする機能は提供されておらず、PacemakerやPgpool-IIなど、別製品を組み合わせて実現するパターンが多いと想定されるが、今回は手動でPostgreSQLをフェイルオーバーする手順を記載する。

環境

OSはRHEL 8を利用し、ディストリビューションでパッケージ提供されているPostgreSQLのバージョンをそのまま利用する。

  • OS : Red Hat Enterprise Linux 8.2
  • PostgreSQL : 10.6

すでに以下記事の手順にてPostgreSQLのストリーミングレプリケーションは構成されていることを前提とする。

今回は1号機マスター、2号機スタンバイとして構成したものを、1号機スタンバイ、2号機マスターとして入れ替える。作業対象がわかりやすくなるよう、プロンプトを以下のように記載する。

プロンプト 説明
[#1(Master/Standby)] 1号機で作業を実施。()はMaster/Standbyのいずれかを記載。
[#2(Master/Standby)] 2号機で作業を実施。()はMaster/Standbyのいずれかを記載。

手動フェイルオーバー手順

1. マスターのDBを停止

マスターのDBをpg_ctlコマンドで停止する。systemctlコマンドで停止も可能だが、その場合パスワード認証を求められるため、作業簡略化のためpg_ctlコマンドを用いる。

[#1(Master)]$ pg_ctl stop -D ${PGDATA}

2. スタンバイのDBを更新可能となるよう昇格

スタンバイのDBを更新可能にするため、昇格 (Promote) する。これをすると、スタンバイの設定が記載されていたrecovery.confrecovery.doneというファイル名に変更され、それによってスタンバイとしてのDB設定が無効化される。

[#2(Standby)]$ pg_ctl promote -D ${PGDATA}

3. ストリーミングレプリケーションの同期を停止

スタンバイDBを昇格しマスターDBとなったとしても、スタンバイDBが停止しているためWAL転送ができず、更新完了の応答を返すことができない。

以下はこの状態であえてDBに更新を行った際のエラーメッセージとなる。ローカルではコミットされているが、スタンバイにレプリケーションされていない旨のメッセージが表示されている。

[#2(Master)]$ psql -d testdb -c "insert into mytable (id, name) values (4, 'Shiro');"
^CCancel request sent
WARNING:  canceling wait for synchronous replication due to user request
DETAIL:  The transaction has already committed locally, but might not have been replicated to the standby.
INSERT 0 1

これはDBとしては正常な動作であり、postgresql.confsynchronous_commitの設定値がonであったりremote_writeであったりすると発生する。本事象回避のため、一時的に同期レプリケーションを停止させるため、synchronous_standby_namesの値を空白に設定する。

[#2(Master)]$ sed -ie "s/^synchronous_standby_names = '\*'/synchronous_standby_names = ''/g" ${PGDATA}/postgresql.conf
[#2(Master)]$ pg_ctl reload -D ${PGDATA}

これによって、以下の通りDBの更新が可能となる。

[#2(Master)]$ psql -d testdb -c "delete from mytable where id=4;"
DELETE 1
[#2(Master)]$ psql -d testdb -c "insert into mytable (id, name) values (4, 'Shiro');"
INSERT 0 1

4. 旧マスターのDBをスタンバイとして設定

旧マスターのDBをスタンバイとして動作させるため、recovery.donerecovery.confにリネームする。

[#1(Standby)]$ mv ${PGDATA}/recovery.done ${PGDATA}/recovery.conf

もしrecovery.doneが存在しない場合(過去一度もrecovery.confを設定したことがない場合)は、以下のように新規作成する。IPアドレスは新マスターのIPアドレスを指定すること。

[#1(Standby)]$ vi $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'application_name=t1116psgl user=dbrepl password=''P@ssw0rd'' host=192.168.11.117 port=5432'
recovery_target_timeline = 'latest'
restore_command = 'scp -o StrictHostKeyChecking=no 192.168.11.117:$PGDATA/archivedir/%f "%p"'

recovery.confを作成後、DBをリロードし設定を反映させる。

[#1(Standby)]$ pg_ctl start -D ${PGDATA}

5. ストリーミングレプリケーションの同期を再開

前の手順で変更したsynchronous_standby_namesをもとの設定値である*に戻す。

[#2(Master)]$ sed -ie "s/^synchronous_standby_names = ''/synchronous_standby_names = '\*'/g" ${PGDATA}/postgresql.conf
[#2(Master)]$ pg_ctl reload -D ${PGDATA}

6. ストリーミングレプリケーションの状態確認

最後にマスターとなったDBにて、レプリケーションの状態を確認しよう。sync_statesyncとなっていれば問題なくマスターとスタンバイでストリーミングレプリケーションが構成できている。

[#2(Master)]$ psql -x -c "SELECT * FROM pg_stat_replication;"
-[ RECORD 1 ]----+------------------------------
pid              | 6616
usesysid         | 16384
usename          | dbrepl
application_name | t1116psgl
client_addr      | 192.168.11.116
client_hostname  |
client_port      | 55756
backend_start    | 2022-06-10 10:58:02.832958+09
backend_xmin     |
state            | streaming
sent_lsn         | 0/701CA08
write_lsn        | 0/701CA08
flush_lsn        | 0/701CA08
replay_lsn       | 0/701CA08
write_lag        |
flush_lag        |
replay_lag       |
sync_priority    | 1
sync_state       | sync

以上で、手動でPostgreSQLをフェイルオーバーする手順は完了となる。

フェイルオーバー手順をスクリプト化する

フェイルオーバーの手順を毎回手作業で実施することは、手間を要するだけでなく、順序を誤ると最悪スタンバイのDB再構築 (ベースバックアップのコピーから実施) が必要となる。

そのため、本作業を自動化すべく、以下の通りスクリプト化してみた。以下にスクリプトの内容を記載する。本スクリプトはスタンバイのDBで実行する。また、互いにSSHの鍵交換を行いパスワードなしでSSHできることが前提となる。

#!/bin/bash

# PostgreSQLのストリーミングレプリケーションをフェイルオーバーするスクリプト

set -eu

# 本スクリプトはスタンバイのDBで実行すること
# スタンバイであるかをチェック
if [ $(psql -xt -c "SELECT * FROM pg_stat_replication;" | grep -c sync_state) -ne 0 ]; then
  echo "[ERROR] This DB is a master. Please run it on standby DB."
  exit 1
fi

# DBのIPアドレスを取得
myhost=$(nmcli c s ens192 | grep ipv4.addresses | sed -e 's#.* \(.*\)/.*#\1#g')
peer=$(grep -e 'primary_conninfo' ${PGDATA}/recovery.conf | sed -e 's/.*host=\(.*\) .*/\1/g')

# マスターの停止
ssh -o StrictHostKeyChecking=no $peer "pg_ctl stop -D ${PGDATA}"

# スタンバイからマスターへ昇格及びレプリケーション停止
pg_ctl promote -D ${PGDATA}
sed -ie "s/^synchronous_standby_names = '\*'/synchronous_standby_names = ''/g" ${PGDATA}/postgresql.conf
pg_ctl reload -D ${PGDATA}

# 旧マスターをスタンバイへ変更
ssh -o StrictHostKeyChecking=no $peer "mv ${PGDATA}/recovery.done ${PGDATA}/recovery.conf"
ssh -o StrictHostKeyChecking=no $peer "pg_ctl start -D ${PGDATA} -l /dev/null"

# レプリケーション再開
sed -ie "s/^synchronous_standby_names = ''/synchronous_standby_names = '\*'/g" ${PGDATA}/postgresql.conf
pg_ctl reload -D $PGDATA

exit 0

スクリプトはfailover.shという名前でpostgresユーザーのホームディレクトリなどに配置すればよい。実行権限も付与しておこう。

[Master/Standby]$ cd ~
[Master/Standby]$ vi failover.sh
[Master/Standby]$ chmod +x failover.sh

実際に本スクリプトを用いてフェイルオーバーを実施してみた結果が以下となる。

[#2(Standby)]$ ./failover.sh
サーバ停止処理の完了を待っています....完了
サーバは停止しました
サーバの昇格を待っています....完了
サーバは昇格しました
サーバにシグナルを送信しました
サーバの起動完了を待っています.....完了
サーバ起動完了
サーバにシグナルを送信しました

[#2(Master)]$ psql -x -c "SELECT * FROM pg_stat_replication;"
-[ RECORD 1 ]----+------------------------------
pid              | 11421
usesysid         | 16384
usename          | dbrepl
application_name | t1116psgl
client_addr      | 192.168.11.116
client_hostname  |
client_port      | 56022
backend_start    | 2022-06-11 15:44:35.698242+09
backend_xmin     |
state            | streaming
sent_lsn         | 0/150001A8
write_lsn        | 0/150001A8
flush_lsn        | 0/150001A8
replay_lsn       | 0/150001A8
write_lag        | 00:00:00.02531
flush_lag        | 00:00:00.02543
replay_lag       | 00:00:00.025536
sync_priority    | 1
sync_state       | sync

0 件のコメント:

コメントを投稿

人気の投稿