PostgreSQLでフェイルオーバーを試してみる

PostgreSQLで主系と待機系の2つのDBをつくる必要が近々あるので、フェイルオーバーの環境をつくってテストしてみた。

テストしたツールは以下の通り

  • CentOS5
  • PostgreSQL8.3.4
  • pgpool2 2.1
  • Ruby-1.8.7 p72
  • Rails2.1.1
  • ruby-pg-0.7.9.2008.01.28

テスト環境としては以下を想定。本当はWebサーバーが複数台あるはずだけどここは1台に簡略化しておく(コネクション数周りの確認は後日)。またデータ同期はここでは無視するので図ではSlony-Iがあるけど実際には動かしてない。

主系がダウンしたときは、以下の用に切り替えてくれることを期待してテストしてみる。

各サーバーに割り当てたアドレスは以下の通り。

まずはpgpool側の設定をつくる。pgpoolには幾つかのモードがあるのだけれど(http://pgpool.projects.postgresql.org/pgpool-ja.html)、今回の目的からいくと一番単純なRawモードが良さそうなのでそれを定義する。

pg_poolをインストールして以下の個所を変更(/usr/local/etc/pgpool.confがディフォルト位置)

93c90
< health_check_period = 60
---
> health_check_period = 0
171,176d167
< backend_hostname0 = '192.168.0.131'
< backend_port0 = 5432
< backend_weight0 = 1
< backend_hostname1 = '192.168.0.133'
< backend_port1 = 5432
< backend_weight1 = 1

ヘルスチェックの間隔を60秒に設定することでフェイルオーバーの機能が動作し始める。後は、接続先のDB定義を記入しておけばRawモードのテストとしての設定は終了。実際にはタイムアウト、コネクション数、ログ等の出力先を変更する必要があるので注意。

ということでpgpoolを起動

$/usr/local/bin/pgool

Webサーバーのローカル9999ポートで起動しているので、Webサーバーから接続してみる(Webサーバーにもテストの為PostgreSQL8.3.4を一通りインストールしてある)。

$ psql -p 9999 -l
         List of databases
   Name         |   Owner    | Encoding
----------------+------------+----------
 database131    | postgres83 | UTF8
 postgres       | postgres83 | UTF8
 template0      | postgres83 | UTF8
 template1      | postgres83 | UTF8
(4 rows)

database131というデータベースを主系につくっているので正常につながっている事がわかる。

つながっていることが確認できたので、主系のpostgresqlを停止してみる。

主系サーバーにログインして、以下のコマンドを実施(immediateでないとpgpoolがコネクションを張っているので停止出来ない)

$pg_ctl stop -m immediate

さて、Webサーバーで先ほどと同じコマンドを入力してどうなるか確認

$psql -p 9999 -l
psql: server closed the connection unexpectedly
 This probably means the server terminated abnormally
 before or while processing the request.

なるほど。こういうメッセージがでるのね。ここで1分ほどまって、pgpoolのヘルスチェックが走しるのを待つ。ヘルスチェックで主系がダウンしていることが確認されればフェイルオーバーが行われる筈。

$ psql -p 9999 -l
          List of databases
    Name     |   Owner    | Encoding
-------------+------------+----------
 database133 | postgres83 | UTF8
 postgres    | postgres83 | UTF8
 template0   | postgres83 | UTF8
 template1   | postgres83 | UTF8
(4 rows)

待機系の192.168.0.133(database133が表してる)が見えた。これで予定通りフェイルオーバーが実施されているのを確認できた。

なるほどこれは便利。自動復帰できるという点でいい感じね。

ここで、再び主系サーバーにログインしてpostgresqlを再起動

$pg_ctl start

これでしばらく待つともとにつながり直すのかな?と思って数分まってみた。

$ psql -p 9999 -l
          List of databases
    Name     |   Owner    | Encoding
-------------+------------+----------
 database133 | postgres83 | UTF8
 postgres    | postgres83 | UTF8
 template0   | postgres83 | UTF8
 template1   | postgres83 | UTF8
(4 rows)

やっぱり待機系につながったままでもとには戻らない。まあ、いったん切り離したノードを律儀にチェックにいって、勝手に元のサーバーに戻っても運用上は困るわけなんだけどもね。

ということで、切り離された主系サーバー(ノード)を復帰する以下のコマンドをWebサーバーで入力

#pcp_attach_node 10 localhost 9898 id pass backend_hostname0

ポート番号が9999ではなく9898になっているのに注意。これはコマンド系を受け付けるために別に起動しているポートになる(pgpool.confで定義可能だが9898がディフォルト)。IDCで運用する時には、管理系端末からこのポートへの穴を空けておかなくてはならないので覚えておこう。

さて、上記コマンドで復帰したか再度確認。

$ psql -p 9999 -l
         List of databases
   Name         |   Owner    | Encoding
----------------+------------+----------
 database131    | postgres83 | UTF8
 postgres       | postgres83 | UTF8
 template0      | postgres83 | UTF8
 template1      | postgres83 | UTF8
(4 rows)

これで無事元の主系データベースサーバーに接続完了。実際は絶え間なく問い合わせが流れているだろうからその辺の考慮は必要になるもののどうにかなりそうだな。

ということで次はRailsで接続しているときどういうエラーが出るかテスト。

webサーバーに以下の用な無限ループのスクリプトをつくってみた。

require "rubygems"
require "activerecord"

ActiveRecord::Base.establish_connection(:adapter=>"postgresql",
                                        :database=>"database131",
                                        :host=>"localhost",
                                        :port=>9999,
                                        :user=>"id",
                                        :password=>"password")

class Customer < ActiveRecord::Base
end

while(1) do
p Customer.find(:all)
sleep(5)
end

database131にはcustmersというテーブルをつくってある。これを実行している途中で主系のデータベースをダウンさせてみる。

/usr/local/lib/ruby/gems/1.8/gems/activerecord-2.1.1/
  lib/active_record/connection_adapters/abstract_adapter.rb:147:in `log': 
    PGError: server closed the connection unexpectedly (ActiveRecord::StatementInvalid)
    This probably means the server terminated abnormally
    before or while processing the request.

上記のエラーが出て停止(改行位置は見やすい用に編集してます)。フェイルオーバーまで最大1分かかることからリトライするのはちょっと現実的じゃないな。これは例外で補足して処理を中止するようにつくった方が良さそう。

ということで基本的なフェイルオーバーのテストはできたかな。

根本的な問題はWebサーバー側のpgpoolが停止したら実質的にDBにはつながらないからそこのプロセス監視をどうするかだなあ。monitで監視するというのはもちろんありなんだけど、Webサーバーを3台程度配置する事を考えると、バランサー側でpgpoolのプロセスを監視させておいてpgpoolのプロセスを見失ったらそのWebサーバーには割当をしない、というのが妥当か。

故障率の点では不利な気もするがこのへんどうやってクリアしてるんだろう。

DBクラスターをOracleとかで構築してしまえば、また違うんだろうけどオープンソースでつくっていくとなるとこの辺ノウハウが足らないなあ。もう少し掘り下げて調べておこう。

2008.10.18追記

運用上のコマンドを調べて追記してみたので、使うときはそちらも併せて参照 - 2008-10-17 - 技術メモ的なモノと気になるモノ