pgpool-II + HS/SRクラスタ構成

pgpool-II + HS/SRクラスタ構成

SRA OSS, inc.
北川 俊広

 

はじめに

本稿では、pgpool-II 3.0 と PostgreSQL 9.0 の Streaming Replication/Hot Standby(以降SR/HSと表記します)機能を組み合わせたクラスタ構成について解説します。

pgpool-II 3.0

pgpool-II は、2010年9月にバージョン 2.3 からバージョン 3.0 へ大幅なメジャーバージョンアップが行われました。3.0 における大きな変更点は SR/HS への対応で、主にクエリ振り分けの処理に変更が加えられています。 pgpool-II で次のとおりパラメータ設定をすると、SR/HSクラスタ構成に適した動作をします。master_slave_sub_mode は、pgpool-II 3.0で新設されたパラメータです。

master_slave_mode = true
master_slave_sub_mode = 'stream'

pgpool-II 3.0のクエリ振り分けルール

pgpool-II 3.0は、以下の点を考慮してクエリを振り分けます。見出しの括弧は各機能が有効となるpgpool-IIのモードを表しています。

Hot Standby のクエリ制約 (マスタースレーブモード)

PostgreSQL 9.0 の Hot Standby 機能を利用して構築したスタンバイサーバでは、実行できないクエリが複数存在します。そのため、pgpool-II 3.0 では、それらをスタンバイサーバに送信しないようになっています。スタンバイサーバで実行できないクエリについては、マニュアルのページに記述があります。

スタンバイサーバのデータ同期遅延 (マスタースレーブモード)

PostgreSQL 9.0 の Streaming Replication 機能は非同期でデータレプリケーションを行うため、スタンバイサーバのデータ更新には若干タイムラグが発生します。データ更新のタイムラグは非常に短く抑えられていますが、例えば長時間かかる更新クエリの実行や、何らかの障害でスタンバイサーバのデータがプライマリサーバと同期できなくなってしまった場合などでは、時間が経つに連れてデータの差異が徐々に大きくなることが予想されます。そのようなときに、参照クエリをスタンバイサーバに送信してしまうと古いデータが返ってきてしまい、好ましい動作とはいえません。そこで、pgpool-II 3.0 ではプライマリサーバとスタンバイサーバ間のデータ更新の遅延を監視して、設定した閾値を超えた場合は、参照クエリをロードバランスせずにプライマリサーバに送信することができるようになっています。遅延の監視はhealth_check_userで指定したDBユーザにより、ヘルスチェックのタイミングで行われ、ヘルスチェックが無効になっている場合は30秒間隔で行われます。また、遅延の閾値は delay_threshold パラメータで設定し、バイト単位で指定します。そしてさらに、適切な閾値を求めるために遅延をバイト単位でログに出力する log_standby_delay パラメータが用意されています。

[delay_thresholdを超えたときの動作]
図1. delay_thresholdを超えたときの動作

トランザクション (マスタースレーブモード)

pgpool-II 2.3 以前は、明示的に開始されたトランザクション内で参照クエリが発行された場合は、その参照クエリをロードバランスしないようになっていました。しかし、例えばAutoCommitを有効にしたJDBCを利用しているアプリケーションなどでは、ロードバランス機能が有効に働かないケースがでてきてしまいます。そのため、pgpool-II 3.0では、明示的に開始されたトランザクション内の参照クエリもロードバランスするようになりました。

しかしながら、単純にそのように変更するとトランザクション動作にいくつか問題が生じます。例えば、トランザクション中で更新クエリを実行した直後にその更新内容を確認する参照クエリを実行した場合に、更新内容がまだ反映されていないスタンバイ(スレーブ)サーバに参照クエリが送信されてしまうと、更新されていないデータが返り、エラーもなしに直前の更新内容が失われたようなおかしなトランザクション動作になります。そこで、pgpool-II 3.0 では、明示的に開始されたトランザクション内で更新クエリが発行された場合は、以降トランザクション内の参照クエリはロードバランスせずにプライマリ(マスタ)サーバに送信するようになっています。また、SERIALIZABLE レベルのトランザクション内で発行される参照クエリも、ロードバランスしてしまうと厳密なトランザクション動作が実現できないため、プライマリ(マスタ)サーバに送信するようになっています。

[delay_thresholdを超えたときの動作]
図2. オートコミットの場合の動作例
 
[delay_thresholdを超えたときの動作]
図3. 明示的にトランザクションを開始した場合の動作例

関数リスト (レプリケーションモード、マスタースレーブモード)

pgpool-II 3.0 では、white_function_list、black_function_listの2つのパラメータを用いることで、更新が発生する関数を柔軟に制御することができます。PostgreSQLは関数をSELECTで呼び出すため、従来は参照クエリなのか更新を伴うクエリなのかの判別ができませんでした。そのため、更新を行う関数呼び出しのクエリにコメントを付与するか、全てのノードにレプリケーションする必要がありました。このパラメータを活用することで、クエリの修正が不要となり、かつ余計なレプリケーションの負荷を抑制できるようになりました。パラメータは以下の様に使い分けます。

  • white_function_list:更新を行わない関数を記述します
  • black_function_list:更新を行う関数を記述します

上記パラメータは双方共に記述をすることは出来ません。どちらかのパラメータだけを使って下さい。

一時テーブル (レプリケーションモード、マスタースレーブモード)

一時テーブルは同一セッション内や同一トランザクション内でのみ有効なテーブルです。そのため、一時テーブルに対する参照クエリをロードバランスしてプライマリ(マスタ)サーバ以外に送信してしまうと、別セッションになるため正しく動作しません。そこで、pgpool-II 3.0 では、参照クエリの対象が一時テーブルかどうかチェックし、一時テーブルであればロードバランスせずにプライマリ(マスタ)サーバに送信するようになっています。

pgpool-II + SR/HSクラスタの構築

それでは、実際にpgpool-II + SR/HSクラスタを構築していきましょう。構築する際の前提として各サーバマシンの役割、ホスト名、ポート番号は次のとおりとし、オンラインリカバリ機能を利用するために3台のサーバは、postgres ユーザがパスワードなしで双方向に ssh 接続できる状態にしておきます。

サーバ ホスト名 (IPアドレス) ポート番号
PostgreSQLサーバ (マスタ) host0.example.org (192.168.1.1) 5432
PostgreSQLサーバ (スレーブ) host1.example.org (192.168.1.2) 5432
pgpool-IIサーバ host9.example.org (192.168.1.3) 9999

そして、執筆時点で最新のpgpool-II 3.0.1 と PostgreSQL 9.0.2 を各サーバマシンにインストールします。PostgreSQLのインストールについてはこちらの記事を参考にして下さい。pgpool-II のインストール方法については第2回の記事を参考にしてください。 以降、pgpool-IIとPostgreSQLは「/usr/local」にインストールされており、データベースクラスタは「/var/pgsql/pgdata」にあるものとします。また、postgresユーザ(DBのROLE)はスーパーユーザとして下さい(おおよその環境ではデフォルトです)。

pgpool-IIの基本設定

前回の記事を参考にして pgpool.conf を書き換えます。 今回はテンプレートとして pgpool.conf.sample-streamを 使用します。

[root@host9]# cd /usr/local/etc
[root@host9]# cp pgpool.conf.sample-stream pgpool.conf
[root@host9]# vi pgpool.conf

前回と異なる設定は次のパラメータです。下記のパラメータ設定はhealth_check_user以外は、pgpool.conf.sample-streamファイルのデフォルトとなっています。

replication_mode = false
master_slave_mode = true
master_slave_sub_mode = 'stream'
health_check_user = 'postgres'

SR+HSの構成では、プライマリサーバとスタンバイサーバ間の同期遅延を、health_check_userで指定したユーザによって監視します。 パスワードは設定できませんので、pgpool-IIのサーバマシンからpostgres データベースにパスワードなしでログインできるユーザを指定してください。 ここでは、スーパーユーザのpostgresを指定します。加えて、 master_slave_mode が true 、master_slave_sub_mode が 'stream' になっているかも確認をしておきましょう。

pgpool-II のオンラインリカバリ設定

pgpool-II を SR/HS クラスタで利用する場合も、スタンバイサーバのリカバリに限ってオンラインリカバリ機能が利用できます。そこで、引き続き前回の記事を参考にしてリカバリ用関数のインストールを行い、pgpool.conf を書き換えます。オンラインリカバリに関する各種設定は同じです。ただし、recovery_1st_stage.sh を下記の SR/HS クラスタ用のものに変更します。

[root@host9]# vi /usr/local/etc/pgpool.conf

recovery_1st_stage_command = 'recovery_1st_stage.sh'

[postgres@host0]$ vi /var/pgsql/pgdata/recovery_1st_stage.sh

recovery_1st_stage.sh
----------------------------------------------------------------------
#!/bin/sh
psql -p 5432 -U $USER -d template1 \
     -c "select pg_start_backup('pgpool-recovery')"
cat > $1/recovery.conf <<EOF
standby_mode = 'on'
primary_conninfo = 'host=$HOSTNAME port=5432 user=postgres'
trigger_file = '/var/log/pgpool/trigger_file'
EOF
rsync -C -az --delete -e ssh --exclude postmaster.pid \
      --exclude postmaster.opts \
      --exclude pg_log --exclude pg_xlog $1/ $2:$3/
psql -p 5432 -U $USER -d template1 -c "select pg_stop_backup()"
rm -rf $1/recovery.conf
ssh -T $2 "rm -rf /var/pgsql/archive/*" \
    2>/dev/null 1>/dev/null < /dev/null
ssh -T $2 "mkdir -p $3/pg_xlog/archive_status" \
    2>/dev/null 1>/dev/null < /dev/null
----------------------------------------------------------------------

続いて、SR/HS構成ではrecovery_2nd_stage.shは不要なので、次のように空文字を指定します。

recovery_2nd_stage_command = ''

そしてさらに、failover_commandを次のように設定します。

failover_command = '/usr/local/bin/failover.sh %d %H /var/log/pgpool/trigger_file'

引数の%dと%Hは、それぞれ実行時にpgpool-IIによって「切り離されたノード番号」と「新しいマスターのホスト名」に置き換えられます。他にも以下の表のパターンが使用できます。

文字 意味
%d 切り離されたノード番号
%h 切り離されたノードのホスト名
%H 新しいマスターのホスト名
%p 切り離されたノードのポート番号
%D 切り離されたノードのデータベースクラスタパス
%M 古いマスターのノード番号
%m 新しいマスターのノード番号
%% '%'文字

また、引数のトリガーファイルは、recovery_1st_stage.sh 内にある recovery.conf の trigger_file パラメータの値に合わせます。そして、スタンバイサーバにトリガーファイルを配置するディレクトリをあらかじめ作成し、所有者を postgres ユーザに変更しておきます。

[root@host1]# mkdir -p /var/log/pgpool
[root@host1]# chown postgres:postgres /var/log/pgpool

また、/usr/local/bin/failover.sh を作成し、実行権限を与えておきます。

[root@host9]# vi /usr/local/bin/failover.sh
[root@host9]# chmod 755 /usr/local/bin/failover.sh

failover.sh
----------------------------------------------------------------------
#! /bin/sh
# Failover command for streming replication.
# This script assumes that DB node 0 is primary, and 1 is standby.
# 
# If standby goes down, does nothing. If primary goes down, create a
# trigger file so that standby take over primary node.
#
# Arguments: $1: failed node id. $2: new master hostname. $3: path to
# trigger file.

failed_node=$1
new_master=$2
trigger_file=$3

# Do nothing if standby goes down.
if [ $failed_node = 1 ]; then
	exit 0;
fi

# Create trigger file.
su -l postgres -c "/usr/bin/ssh -T $new_master /bin/touch $trigger_file"

exit 0;
----------------------------------------------------------------------

以上で、pgpool-II の設定は完了です。

PostgreSQL の設定

今回は、オンラインリカバリ機能を利用してスタンバイサーバを構築してみようと思います。そこで、プライマリサーバのみ設定を行います。 まず、Streaming Replication 用の接続と pgpool-II からの接続を許可するため、pg_hba.conf ファイルに次の2行を追加します。今回は簡易的にパスワード認証を使用せず、trust認証を使用します。

 

host    replication     all             192.168.1.0/24          trust
host    all             all             192.168.1.0/24          trust

そして、postgresql.conf ファイルの最低限必要な以下のパラメータを変更します。

listen_addresses = '*'
hot_standby = on
wal_level = hot_standby
max_wal_senders = 1

以上で、PostgreSQL の設定は完了です。プライマリサーバとなるPostgreSQLを起動もしくは再起動します。

オンラインリカバリを利用したスタンバイサーバの構築

スタンバイサーバが起動していないことを確認して、pgpool-II を起動します。 起動したら、スタンバイサーバが停止していることを pgpool-II に認識させるため、pgpool-II 経由で一旦データベースに接続します。

[postgres@host9]$ psql -h localhost -p 9999
psql: server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.

そして、次のコマンドを実行してノード1をリカバリします。 ここで指定するユーザ名とパスワードは、pcp.conf ファイルにて設定したものです。

[root@host9]# pcp_recovery_node 100 localhost 9898 ユーザ名 パスワード 1

リカバリが完了すると、pgpool-II + SR/HS クラスタ構成の完成です。

動作確認

正常に SR/HS が機能しているかどうか確認するため、pgpool-II 経由で適当なデータベースを作成します。

[postgres@host9]$ createdb -h localhost -p 9999 testdb2

プライマリサーバのデータベース一覧を表示します。

[postgres@host9]$ psql -h host0.example.org -p 5432 -l
                              List of databases
   Name    |  Owner   | Encoding | Collation | Ctype |   Access privileges
-----------+----------+----------+-----------+-------+-----------------------
 postgres  | postgres | UTF8     | C         | C     |
 template0 | postgres | UTF8     | C         | C     | =c/postgres
                                                     : postgres=CTc/postgres
 template1 | postgres | UTF8     | C         | C     | =c/postgres
                                                     : postgres=CTc/postgres
 testdb2   | postgres | UTF8     | C         | C     |
(4 rows)

スタンバイサーバのデータベース一覧を表示します。

[postgres@host9]$ psql -h host1.example.org -p 5432 -l
                              List of databases
   Name    |  Owner   | Encoding | Collation | Ctype |   Access privileges
-----------+----------+----------+-----------+-------+-----------------------
 postgres  | postgres | UTF8     | C         | C     |
 template0 | postgres | UTF8     | C         | C     | =c/postgres
                                                     : postgres=CTc/postgres
 template1 | postgres | UTF8     | C         | C     | =c/postgres
                                                     : postgres=CTc/postgres
 testdb2   | postgres | UTF8     | C         | C     |
(4 rows)

スタンバイサーバにもtestdb2が作成されていれば、正常にSR/HSが機能しています。

縮退動作

スタンバイサーバを停止したときに、縮退運転に移行するかどうか確認します。 まず、スタンバイサーバの PostgreSQL を停止します。pg_ctl コマンドで immediate モードを指定して停止すると、終了処理をせずに直ちに停止するので、障害によって停止したのと似た状態になります。

[postgres@host1]$ pg_ctl stop -m immediate -D /var/pgsql/pgdata

host1 の PostgreSQL を停止後、pgpool-II からアクセスすると接続エラーが発生します。

[postgres@host9]$ psql -h localhost -p 9999 testdb2
psql: server closed the connection unexpectedly
       This probably means the server terminated abnormally
       before or while processing the request.

ここで障害が検知され縮退運転に移行します。再度アクセスすると接続可能になります。SHOW pool_status で確認すると末尾の項目 backend status1 が、稼働中を表す 2 でなく、停止中を表す 3 になっていることが確認できます。

[postgres@host9]$ psql -h localhost -p 9999 testdb2
psql (9.0.2)
Type "help" for help.

testdb2=# SHOW pool_status;
        item       |       value       |        description
-------------------+-------------------+----------------------------
(中略) 
 backend_hostname1 | host1.example.org | backend #1 hostname
 backend_port1     | 5432              | backend #1 port number
 backend_weight1   | 0.500000          | weight of backend #1
 backend status1   | 3                 | status of backend #1
 standby_delay1    | 0                 | standby delay of backend #1
(75 rows)

スタンバイサーバのマスタサーバへの昇格

プライマリサーバが停止したときにフェイルオーバが行われ、スタンバイサーバがマスタサーバに昇格するかどうか確認します。 縮退動作の確認でスタンバイサーバを停止した場合は、再起動します。

[postgres@host1]$ pg_ctl start -w -D /var/pgsql/pgdata

pgpool-IIが切り離しているスタンバイサーバを再度追加します。

[root@host9]# pcp_attach_node 100 localhost 9898 ユーザ名 パスワード 1

プライマリサーバの PostgreSQL を停止します。

[postgres@host0]$ pg_ctl stop -m immediate -D /var/pgsql/pgdata

host0 の PostgreSQL を停止後、pgpool-II からアクセスすると接続エラーが発生します。

[postgres@host9]$ psql -h localhost -p 9999 testdb2
psql: server closed the connection unexpectedly
       This probably means the server terminated abnormally
       before or while processing the request.

ここで障害が検知されフェイルオーバが発生し、再度アクセスすると接続可能になります。SHOW pool_status で確認すると末尾の項目 backend status0 が、稼働中を表す 2 でなく、停止中を表す 3 になっていることが確認できます。

[postgres@host9]$ psql -h localhost -p 9999 testdb2
psql (9.0.2)
Type "help" for help.

testdb2=# SHOW pool_status;
        item       |       value       |        description
-------------------+-------------------+----------------------------
(中略) 
 backend_hostname0 | host0.example.org | backend #0 hostname
 backend_port0     | 5432              | backend #0 port number
 backend_weight0   | 0.500000          | weight of backend #0
 backend status0   | 3                 | status of backend #0
 standby_delay0    | 0                 | standby delay of backend #0

スタンバイサーバがマスタサーバに昇格し、更新クエリを受け付けるようになっているかどうか確認するため、適当なテーブルを作成します。

[postgres@host9]$ psql -h localhost -p 9999 testdb2
psql (9.0.2)
Type "help" for help.

testdb2=# CREATE TABLE t1(id INT);
CREATE TABLE

テーブルが作成できれば、正常にフェイルオーバが行われています。

おわりに

今回は、pgpool-II + HS/SR クラスタ構成について解説しました。最後に解説したプライマリサーバのダウンによるフェイルオーバですが、実際にそれが起こった場合は、当然復旧作業が必要になります。pgpool-II 3.0 は、今までの pgpool-II と同じくノード番号の若いノードをマスタとして扱うため、ノード 0 をスタンバイサーバ、ノード 1 をプライマリサーバとする構成で復旧することができません。したがって、最終的には元の構成に戻して復旧する必要があり、復旧作業は非常に面倒です。ただし、それは pgpool-II 3.1 で解消される見込みです。すでにpgpool-II 3.1 の alpha 版とともにここのWebページに解説が用意されています。pgpool-II 3.1 では、プライマリサーバがダウンしても、どのノードが新しいプライマリサーバかを認識する仕組みを入れてオンラインリカバリで復旧できるようになっています。


(2011年1月25日 公開)