PostgreSQLで自動フェイルオーバーする方法

以前投稿した[bgwokerで超簡易クラスタ管理](http://qiita.com/sawada_masahiko/items/43849016a78ff8a9c6be)を進化させた[pg_keeper](https://github.com/MasahikoSawada/pg_keeper)について投稿。

# コンセプト
このツールのコンセプトは**「PostgreSQLの自動フェイルオーバーを簡単に設定する」**です。
Pacemaker/corosyncやrepmgrを使えばより細かく、柔軟に設定することが出来ますが、その一方設定が面倒だったり、多くのケースではそこまで柔軟な設定は必要ないと思ったので、**「マスタ、スレーブ2台構成でもっと簡単に可用性を向上させたい」**と思い作りました。

監視プロセスはPostgreSQLのプロセスの一つとして動作するので、高機能なクラスタリングミドルウェアによくある監視プロセス自体の起動・停止・監視等の作業は発生しません。

ただし、pg_keeperが対応しているのは**マスタ1台、スタンバイ1台で同期レプリケーションを使用した場合**のみです。
スタンバイを2台以上使用するケースには対応していません。

# 使い方
## インストール
github上の[pg_keeper](https://github.com/MasahikoSawada/pg_keeper)というリポジトリからダウンロードしてください。

他のEXTENSIONと同じようにインストール。
マスタ、スタンバイサーバの両方でインストール、設定します。

```
$ cd pg_keeper
$ make USE_PGXS=1
$ su
# make USE_PGXS=1 install
```
## 設定
設定するのは以下の5つのパラメータ。上の2つだけ指定しても動きます。

|設定パラメータ|内容|設定例|備考|
|:----------:|:---:|:--:|:---:|
|pg_keeper.node1_conninfo|片方のサーバへの接続情報を設定（**最初マスタサーバになる方**）|`pg_keeper.node1_conninfo = 'host=pgserver1 port=5432 dbname=postgres'`|必須|
|pg_keeper.node2_conninfo|もう片方のサーバへの接続情報を設定（**最初スタンバイサーバになる方**）|`pg_keeper.node1_conninfo = 'host=pgserver2 port=5432 dbname=postgres'`|必須
|pg_keeper.keepalive_time|Hearbeatの間隔を指定(秒)|`pg_keeper.keepalive_time=5`|デフォルト5秒
|pg_keeper.keepalive_count|何回Heatbeatが失敗したらフェイルオーバーするかを指定|`pg_keeper.keepalive_count=3`|デフォルト1回
|pg_keeper.after_command|フェイルオーバー後に実行するコマンドを指定|pg_keeper.after_command='sh /path/to/shell'|デフォルトはNULL。例えばVIPを切り替えるシェル等を指定します。|

サーバダウンからフェイルオーバーまでの時間は`(pg_keeper.keepalive_time * pg_keeper.keepalive_count)秒`となります。

# 実行例
マスタサーバ、スタンバイサーバの2台構成で、同期レプリケーション+pg_keeperで高可用構成を組んだ時の例を紹介します。
 
```bash:設定値
pg_keeper.keepalives_time = 5
pg_keeper.keepalives_count = 4
```

# 1. インストール後、マスタを起動
他のクラスタリングソフトを使用した場合と同様に、マスタサーバから起動します。
PostgreSQLが起動するのと同時にpg_keeperプロセスが自動的にマスタモードで起動します。

```bash:マスタサーバで実行
$ pg_ctl start -D master
$ ps x | grep pg_keeper | grep -v grep
33525 ?        Ss     0:00 postgres: bgworker: pg_keeper   (master mode)
```

# 2. スタンバイを起動
pg_keeperプロセスがスタンバイモードで起動します。

```bash:スタンバイサーバで実行
$ pg_ctl start -D standby
$ ps x | grep pg_keeper | grep -v grep
33613 ?        Ss     0:00 postgres: bgworker: pg_keeper   (standby mode:connected)
```

また、マスタサーバ側では、マスタで起動しているpg_keeperプロセスがスタンバイサーバに接続したログが出て、プロセス名が、`(master mode)`から`(master mode:connected)`に変わっていることがわかります。

```bash:マスタサーバで実行
$ tail master.log
LOG:  pg_keeper connects to standby server
$ ps x | grep pg_keeper | grep -v grep
33525 ?        Ss     0:00 postgres: bgworker: pg_keeper   (master mode:connected)
```

# 3-1. スタンバイサーバを落としてみる(同期→非同期の自動切り替え)
同期レプリケーションを組んでいるため、スタンバイサーバが落ちるとマスタへの更新処理が停止します。
pg_keeperではスタンバイサーバのクラッシュを検知し、自動的に非同期レプリケーションに切り替えます。

まずは、スタンバイサーバを停止

```bash:スタンバイサーバで実行
$ pg_ctl stop -D standby
```

マスタサーバの様子を見てみると、5秒おきに4回Heatbeat通信をした後に、設定ファイルを再読込して、非同期レプリケーション切り替えていることがわかります。

```bash:マスタサーバで実行
$ tail master.log
<2016-07-20 09:10:09.855 AST> LOG:  could not get tuple from server : "port=5551 dbname=postgres"
<2016-07-20 09:10:09.855 AST> LOG:  pg_keeper failed to execute pooling 1 time(s)
<2016-07-20 09:10:14.859 AST> LOG:  could not get tuple from server : "port=5551 dbname=postgres"
<2016-07-20 09:10:14.859 AST> LOG:  pg_keeper failed to execute pooling 2 time(s)
<2016-07-20 09:10:19.863 AST> LOG:  could not get tuple from server : "port=5551 dbname=postgres"
<2016-07-20 09:10:19.863 AST> LOG:  pg_keeper failed to execute pooling 3 time(s)
<2016-07-20 09:10:24.867 AST> LOG:  could not get tuple from server : "port=5551 dbname=postgres"
<2016-07-20 09:10:24.867 AST> LOG:  pg_keeper failed to execute pooling 4 time(s)
<2016-07-20 09:10:24.867 AST> LOG:  pg_keeper changes replication mode to asynchronous replication
<2016-07-20 09:10:24.884 AST> LOG:  received SIGHUP, reloading configuration files
<2016-07-20 09:10:24.885 AST> LOG:  parameter "synchronous_standby_names" changed to ""
```

# 3-2. マスタサーバを落としてみる(フェイルオーバー)
pg_keeperではマスタサーバのクラッシュを検知し、自動的にスタンバイサーバへフェイルオーバーします。

まずは、マスタサーバを停止。

```bash:マスタサーバで実行
$ pg_ctl stop -D master
```

スタンバイサーバの様子を見てみると、5秒おきに4回Heatbeat通信をした後に、設定ファイルを自動的に再読込して、フェイルオーバーしていることがわかります。

```bash:スタンバイサーバで実行
$ tail standby.log
<2016-07-20 09:14:30.622 AST>LOG:  could not get tuple from server : "port=5550 dbname=postgres"
<2016-07-20 09:14:30.622 AST>LOG:  pg_keeper failed to execute pooling 1 time(s)
<2016-07-20 09:14:35.628 AST>LOG:  could not get tuple from server : "port=5550 dbname=postgres"
<2016-07-20 09:14:35.628 AST>LOG:  pg_keeper failed to execute pooling 2 time(s)
<2016-07-20 09:14:40.634 AST>LOG:  could not get tuple from server : "port=5550 dbname=postgres"
<2016-07-20 09:14:40.634 AST>LOG:  pg_keeper failed to execute pooling 3 time(s)
<2016-07-20 09:14:45.641 AST>LOG:  could not get tuple from server : "port=5550 dbname=postgres"
<2016-07-20 09:14:45.641 AST>LOG:  pg_keeper failed to execute pooling 4 time(s)
<2016-07-20 09:14:45.641 AST>LOG:  promote standby server to primary server
<2016-07-20 09:14:45.641 AST>LOG:  swtiched master and standby informations
<2016-07-20 09:14:45.641 AST>DETAIL:  "port=5551 dbname=postgres" is regarded as master server, "port=5550 dbname=postgres" is regarded as standby server
<2016-07-20 09:14:45.641 AST>LOG:  received promote request
<2016-07-20 09:14:45.641 AST>LOG:  redo done at 0/3000060
<2016-07-20 09:14:45.646 AST>LOG:  selected new timeline ID: 2
<2016-07-20 09:14:45.690 AST>LOG:  archive recovery complete
<2016-07-20 09:14:45.692 AST>LOG:  MultiXact member wraparound protections are now enabled
<2016-07-20 09:14:45.693 AST>LOG:  database system is ready to accept connections
```

さらにスタンバイサーバが昇格した後に、pg_keeperプロセスはマスタモードに自動的に切り替わります。
このようにすることで、クラッシュした旧マスタが再度スタンバイとして接続した時にも、スムーズに高可用構成を組むことが出来ます。

```bash:新マスタサーバで実行
$ ps x | grep pg_keeper | grep -v grep
34116 ?        Ss     0:00 postgres: bgworker: pg_keeper   (master mode)
```

# 使用上のTIPS
通常の運用でクラスタを組んでいるときはマスタ、スタンバイのVIPを使って通信していると思います。

pg_keeperでは、`pg_keeper.after_command`に設定されたコマンドがスタンバイサーバが昇格した後にシェルを実行されるようにしているので、VIPを付け替えるようなシェルを実行するように定義しておくことで、VIPの付け替えも対応可能です。

また、レプリケーション通信が不通になった場合、スプリットブレインが発生します。`pg_keeper.after_command`にて、旧マスタをSTONITHで強制的に落とすようにすることで対応できると思います。

使って問題があったら、お気軽にIssueまたはPR投げてください。
https://github.com/MasahikoSawada/pg_keeper