pgpool-II + Slony-Iクラスタ構成
SRA OSS, inc.
北川 俊広
はじめに
本稿では、pgpool-II 3.0 と Slony-I 2.0 を組み合わせたクラスタ構成について解説します。
Slony-Iとは
Slony-I は、PGDG (PostgreSQL Global Development Group: PostgreSQLの開発チーム) が主体となって開発している非同期方式のレプリケーションソフトウェアです。システムはマスタ・スレーブ構成をとり、マスタのみ更新が可能です。スレーブは複数台用意することができます。レプリケーションの仕組みは、PostgreSQL のトリガ機能を使用しており、あらかじめレプリケーション対象のテーブルにトリガを作成しておくことで、ユーザがデータを更新するとトリガ関数によって、更新内容が Slony-I の管理テーブルに記録されるようになっています。そして、その更新内容は、定期的に Slony-I のデーモンプロセスである slon がスレーブに転送し、それをスレーブで適用することによってレプリケーションを実現します。
非同期方式というと PostgreSQL 9.0 の Streaming Replication/Hot Standby との違いが気になりますが、Slony-I には以下の利点があります。
- PostgreSQL のメジャーバージョンが異なっていてもレプリケーション可能
- 異なる CPU アーキテクチャのハードウェアや異なる OS 間でもレプリケーション可能
- レプリケーション対象とするテーブルの選択が可能
- テーブル毎にレプリケーション先のスレーブ選択が可能
- レプリケーション対象のテーブル群ごとにマスタとなるサーバを定義可能
上記は、Slony-I のマニュアルを参考にしたものですが、このように Streaming Replication/Hot Standby よりも、柔軟にレプリケーション構成を組めるのが Slony-I の利点です。
ただし、レプリケーションの仕組みにトリガを使用している関係で、以下の変更をスレーブに反映できないという制限があります。
- ラージオブジェクトの変更
- DDLコ マンドによる変更
- ロールの変更
- TRUNCATE コマンドによる変更(将来的には対応するかもしれない)
pgpool-II と Slony-I を組み合わせる利点
pgpool-II と Slony-I を組み合わせる利点は、Slony-I に不足している機能を pgpool-II で補えるという点です。
例えば、Slony-I を負荷分散目的で使用する場合、更新クエリをマスタサーバのみに送信し、参照クエリをマスタサーバかスレーブサーバに送信するように、アプリケーション側で送信先を制御する必要があります。また、Slony-I には障害を検知して自動的にフェイルオーバを行う仕組みがありません。しかし、それらの機能は pgpool-II が持っていますので、組み合わせることで補うことができます。
pgpool-II + Slony-I クラスタの構築
それでは、実際に pgpool-II + Slony-I クラスタを構築していきましょう。構築する際の前提として各サーバマシンの役割、ホスト名、ポート番号は次のとおりとします。
サーバ | ホスト名 (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 をインストールします。pgpool-II のインストール方法については第2回の記事を参考にしてください。 以降、pgpool-II と PostgreSQL は「/usr/local」にインストールされており、データベースクラスタは「/var/pgsql/pgdata」にあるものとします。 また、Slony-I のデーモン slon は、マスタサーバとスレーブサーバで動かします。
PostgreSQLの設定
まず、pgpool-II と Slony-I からの接続を許可するため、マスタサーバとスレーブサーバの pg_hba.conf ファイルに次の1行を追加します。今回は簡易的にパスワード認証を使用せず、trust 認証を使用します。
host all all 192.168.1.0/24 trust
そして、postgresql.conf ファイルの最低限必要な次のパラメータを変更します。
listen_addresses = '*'
以上で、PostgreSQL の設定は完了です。PostgreSQL を起動もしくは再起動します。
Slony-I の入手とインストール
Slony-I のソースコードはここのサイトからダウンロードできます。
現在、Slony-I は主にバージョン 2.0 と 1.2 のメンテナンスが行われています。バージョン 2.0 は PostgreSQL 8.3 以降でしか動作しませんので、実際は、PostgreSQL のバージョンに応じて Slony-I のバージョンを選択します。本稿では、執筆時点で最新の 2.0.6 を使用します。
インストールは、3台すべてのサーバにインストールします。pgpool-II サーバにはインストールが不要に思われますが、pgpool-II がフェイルオーバを行う際は Slony-I に対してもフェイルオーバを命令する必要があり、その際に Slony-I のコマンドを使用するため、インストールが必要になります。 以降、3台のマシンに同じ手順でインストールします。
slony1-2.0.6.tar.bz2 を /usr/local/src に配置したあと、一通りディレクトリを作成してその所有者を postgres に変更します。
[root@host*]# mkdir /usr/local/src/slony1-2.0.6 [root@host*]# mkdir /usr/local/slony [root@host*]# chown postgres:postgres /usr/local/src/slony1-2.0.6 [root@host*]# chown postgres:postgres /usr/local/slony
/usr/local/src/slony1-2.0.6 はソースコードの展開先、/usr/local/slony はインストール先です。
続いて、ソースコードを展開し、ビルド、インストールします。
[root@host*]# su - postgres [postgres@host*]$ tar jxf slony1-2.0.6.tar.bz2 [postgres@host*]$ cd slony1-2.0.6 [postgres@host*]$ ./configure --prefix=/usr/local/slony \ --with-pgconfigdir=/usr/local/bin [postgres@host*]$ make install
--prefix オプションでインストール先を、--with-pgconfigdir オプションで pg_config コマンドの場所を指定できます。--with-pgconfigdir は、複数の PostgreSQL をインストールしている場合や pg_config コマンドにパスが通っていない場合は指定する必要があります。
最後に、必要に応じて Slony-I のコマンドにパスを通して終了です。
[postgres@host*]$ vi ~/.bash_profile PATHに「/usr/local/slony/bin」を追加
※ 上記はシェルが bash の場合です。
レプリケーションを行うデータベースの作成
まず、マスタサーバにて動作確認用の testdb3 データベースを作成し、psql で testdb3 データベースにログインして下記の SQL を実行します。
[postgres@host0]$ createdb testdb3 [postgres@host0]$ psql testdb3 psql (9.0.2) Type "help" for help. =# CREATE TABLE t1 (id SERIAL PRIMARY KEY, comment TEXT, ins_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP);
これで次のテーブルが作成されます。
testdb3=# \d t1 Table "public.t1" Column | Type | Modifiers ----------+-----------------------------+-------------------------- id | integer | not null default nextval ('t1_id_seq'::regclass) comment | text | ins_time | timestamp without time zone | default now() Indexes: "t1_pkey" PRIMARY KEY, btree (id) ※ 幅の関係で1行のデータを改行して記述しています。
Slony-I は、PRIMARY KEY が付いていないテーブルをレプリケーションできません。そこで、レプリケーションを行うテーブルには必ず PRIMARY KEY を付けるようにします。 また、DDL コマンドによる変更も自動的にレプリケーションできないため、pg_dump と psql を使用してスレーブサーバにスキーマをコピーします。
[postgres@host0]$ pg_dump -C -s testdb3 | psql -h host1.example.org
Slony-I の設定
Slony-I の設定は slonik という管理コマンドを使用して行います。下記のスクリプトはSlony-I のマニュアルに記載されているサンプルを修正したものです。これを、マスタサーバで実行します。
[postgres@host0]$ sh setup.sh setup.sh ----------------------------------------------------------------- #!/bin/sh CLUSTERNAME=testdb3_cluster MASTERDBNAME=testdb3 SLAVEDBNAME=testdb3 MASTERHOST=host0.example.org SLAVEHOST=host1.example.org REPLICATIONUSER=postgres /usr/local/slony/bin/slonik <<_EOF_ #-- # define the namespace the replication system # uses in our example it is slony_example #-- cluster name = $CLUSTERNAME; #-- # admin conninfo's are used by slonik to connect to # the nodes one for eachnode on each side of the cluster, # the syntax is that of PQconnectdb in # the C-API # -- node 1 admin conninfo = 'dbname=$MASTERDBNAME \ host=$MASTERHOST user=$REPLICATIONUSER'; node 2 admin conninfo = 'dbname=$SLAVEDBNAME \ host=$SLAVEHOST user=$REPLICATIONUSER'; #-- # init the first node. Its id MUST be 1. This creates # the schema _$CLUSTERNAME containing all replication # system specific database objects. #-- init cluster ( id=1, comment = 'Master Node'); #-- # Slony-I organizes tables into sets. The smallest unit # a node can subscribe is a set. The master or origin of # the set is node 1. #-- create set (id=1, origin=1, comment='All testdb3 tables'); set add table (set id=1, origin=1, id=1, fully qualified name = 'public.t1', comment='t1 table'); set add sequence (set id=1, origin = 1, id = 1, fully qualified name = 'public.t1_id_seq', comment = 't1 id sequence'); #-- # Create the second node (the slave) tell the 2 nodes how # to connect to each other and how they should listen for events. #-- store node (id=2, comment = 'Slave Node', event node=1); store path (server = 1, client = 2, conninfo='dbname=$MASTERDBNAME \ host=$MASTERHOST user=$REPLICATIONUSER'); store path (server = 2, client = 1, conninfo='dbname=$SLAVEDBNAME \ host=$SLAVEHOST user=$REPLICATIONUSER'); _EOF_ -----------------------------------------------------------------
スクリプト中の各種設定コマンドの意味は、Slony-I のマニュアルを参照してください。
上記のスクリプトを実行すると、Slony-I の各種管理テーブルが testdb3 データベースの「_testdb3_cluster」というスキーマに作成され、そこに設定内容が記録されます。
slon デーモンの起動
マスタサーバとスレーブサーバで slon デーモンを起動します。
[postgres@host0]$ /usr/local/slony/bin/slon testdb3_cluster \ "dbname=testdb3 user=postgres host=localhost" & [1] 27969 2011-01-07 15:54:06 JSTCONFIG main: slon version 2.0.6 starting up … [postgres@host1]$ /usr/local/slony/bin/slon testdb3_cluster \ "dbname=testdb3 user=postgres host=localhost" & [1] 7772 2011-01-07 15:54:32 JSTCONFIG main: slon version 2.0.6 starting up …
レプリケーションの開始
マスタサーバにて、下記のスクリプトを実行するとレプリケーションが始まります。
[postgres@host0]$ sh subscribe.sh :18: NOTICE: subscribe set: omit_copy=f … subscribe.sh ---------------------------------------------------------------------- #!/bin/sh CLUSTERNAME=testdb3_cluster MASTERDBNAME=testdb3 SLAVEDBNAME=testdb3 MASTERHOST=host0.example.org SLAVEHOST=host1.example.org REPLICATIONUSER=postgres /usr/local/slony/bin/slonik <<_EOF_ # ---- # This defines which namespace the replication system uses # ---- cluster name = $CLUSTERNAME; # ---- # Admin conninfo's are used by the slonik program to connect # to the node databases. So these are the PQconnectdb arguments # that connect from the administrators workstation (where # slonik is executed). # ---- node 1 admin conninfo = 'dbname=$MASTERDBNAME host=$MASTERHOST \ user=$REPLICATIONUSER'; node 2 admin conninfo = 'dbname=$SLAVEDBNAME host=$SLAVEHOST \ user=$REPLICATIONUSER'; # ---- # Node 2 subscribes set 1 # ---- subscribe set ( id = 1, provider = 1, receiver = 2, forward = no); _EOF_ ------------------------------------------------------------------------
Slony-Iの動作確認
マスタサーバにて psql で testdb3 データベースにログインし、テストデータを挿入します。
[postgres@host0]$ psql testdb3 psql (9.0.2) Type "help" for help. testdb3=# INSERT INTO t1(comment) VALUES('replication test') INSERT 0 1
マスタサーバとスレーブサーバで t1 テーブルの内容を表示し、同じ行が表示されれば成功です。
testdb3=# SELECT * FROM t1; id | comment | ins_time ----+------------------+---------------------------- 1 | replication test | 2011-01-07 16:29:36.780824 (1 row)
pgpool-IIの基本設定
第2回の記事を参考にして pgpool.conf を書き換えます。 今回はテンプレートとして pgpool.conf.sample-master-slave を使用します。
[root@host9]# cd /usr/local/etc [root@host9]# cp pgpool.conf.sample-master-slave pgpool.conf [root@host9]# vi pgpool.conf
第2回と設定が異なるのは次のパラメータです。
replication_mode = false master_slave_mode = true master_slave_sub_mode = 'slony'
今回はSlony-Iクラスタ用の設定になりますので、master_slave_mode を true にし、master_slave_sub_mode を 'slony' にします。
pgpool-II のフェイルオーバ設定
まず、フェイルオーバ時に実行するスクリプトを作成します。
[root@host9]# vi /usr/local/bin/failover.sh failover.sh ---------------------------------------------------------------------- #! /bin/sh # Failover command for Slony-I. # This script assumes that DB node 0 is master, and 1 is slave. # # If slave goes down, does nothing. If master goes down, execute # slonik command. # # Arguments: $1: failed node id. CLUSTERNAME=testdb3_cluster MASTERDBNAME=testdb3 SLAVEDBNAME=testdb3 MASTERHOST=host0.example.org SLAVEHOST=host1.example.org REPLICATIONUSER=postgres failed_node=$1 # Do nothing if slave goes down. if [ $failed_node = 1 ]; then exit 0; fi # execute slonik command /usr/local/slony/bin/slonik <<_EOF_ cluster name = $CLUSTERNAME; node 1 admin conninfo = 'dbname=$MASTERDBNAME host=$MASTERHOST \ user=$REPLICATIONUSER'; node 2 admin conninfo = 'dbname=$SLAVEDBNAME host=$SLAVEHOST \ user=$REPLICATIONUSER'; failover (id = 1, backup node = 2); drop node (id = 1, event node = 2); _EOF_ exit 0; ----------------------------------------------------------------------
作成したスクリプトに実行権限を与えます。
[root@host9]# chmod 755 /usr/local/bin/failover.sh
pgpool.conf の failover_command を以下のように設定します。
[root@host9]# vi /usr/local/etc/pgpool.conf failover_command = '/usr/local/bin/failover.sh %d'
引数の %d は、実行時に pgpool-II によって「切り離されたノード番号」に置き換えられます。他にも以下の表のパターンが使用できます。
文字 | 意味 |
---|---|
%d | 切り離されたノード番号 |
%h | 切り離されたノードのホスト名 |
%H | 新しいマスターのホスト名 |
%p | 切り離されたノードのポート番号 |
%D | 切り離されたノードのデータベースクラスタパス |
%M | 古いマスターのノード番号 |
%m | 新しいマスターのノード番号 |
%% | '%'文字 |
以上で、pgpool-II の設定は完了です。
動作確認
pgpool-II を起動し、pgpool-II 経由でテストデータを挿入します。
[postgres@host9]$ psql -h localhost -p 9999 testdb3 psql (9.0.2) Type "help" for help. testdb3=# INSERT INTO t1(comment) VALUES('from pgpool-II'); INSERT 0 1
マスタサーバとスレーブサーバで t1 テーブルの内容を表示し、同じ行が表示されれば正常です。
testdb3=# SELECT * FROM t1; id | comment | ins_time ----+------------------+---------------------------- 1 | replication test | 2011-01-07 19:29:36.780824 2 | from pgpool-II | 2011-01-07 19:54:30.977498 (2 rows)
縮退動作
スレーブサーバを停止したときに、縮退運転に以降するかどうか確認します。 まず、スレーブサーバの PostgreSQL を停止します。pg_ctl コマンドで immediate モードを指定して停止すると、終了処理をせずに直ちに停止するので、障害によって停止した場合と似た状態になります。
[postgres@host1]$ pg_ctl stop -m immediate -D /var/pgsql/pgdata
スレーブサーバの PostgreSQL を停止後、pgpool-II からアクセスすると接続エラーが発生します。
[postgres@host9]$ psql -h localhost -p 9999 testdb3 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 testdb3 psql (9.0.2) Type "help" for help. testdb3=# 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)
次に、縮退運転の状態でテストデータを挿入し、スレーブサーバを復旧後にそのテストデータがレプリケーションされるかどうか確認します。
スレーブサーバが停止している状態でpgpool-II経由でテストデータを挿入します。
[postgres@host9]$ psql -h localhost -p 9999 testdb3 psql (9.0.2) Type "help" for help. testdb3=# INSERT INTO t1(comment) VALUES('degeneration test'); INSERT 0 1 testdb3=# SELECT * FROM t1; id | comment | ins_time ----+-------------------+---------------------------- 1 | replication test | 2011-01-07 19:29:36.780824 2 | from pgpool-II | 2011-01-07 19:54:30.977498 3 | degeneration test | 2011-01-07 20:06:04.328413 (3 rows)
スレーブサーバを起動します。
[postgres@host1]$ pg_ctl start -w -D /var/pgsql/pgdata
スレーブサーバでt1テーブルの内容を表示し、3行表示されれば正常です。
[postgres@host1]$ psql -h localhost testdb3 psql (9.0.2) Type "help" for help. testdb3=# SELECT * FROM t1; id | comment | ins_time ----+-------------------+---------------------------- 1 | replication test | 2011-01-07 19:29:36.780824 2 | from pgpool-II | 2011-01-07 19:54:30.977498 3 | degeneration test | 2011-01-07 20:06:04.328413 (3 rows)
最後に、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 testdb3 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 testdb3 psql (9.0.2) Type "help" for help. testdb3=# 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 testdb3 psql (9.0.2) Type "help" for help. testdb3=# INSERT INTO t1(comment) VALUES('failover test'); INSERT 0 1 testdb3=# SELECT * FROM t1; id | comment | ins_time ----+-------------------+---------------------------- 1 | replication test | 2011-01-07 19:29:36.780824 2 | from pgpool-II | 2011-01-07 19:54:30.977498 3 | degeneration test | 2011-01-07 20:06:04.328413 4 | failover test | 2011-01-07 20:18:44.127364 (4 rows)
テストデータが挿入できれば、正常にフェイルオーバが行われています。
おわりに
今回は、pgpool-II + Slony-I クラスタ構成について解説しました。pgpool-II と Slony-I の組み合わせは、pgpool-II のレプリケーションモードと比較すると非同期レプリケーションになってしまうという弱点があるものの、クエリの制限が少なくアプリケーション側に手を入れる必要が少ないことから、既存のシステムのデータベースをレプリケーション構成にする場合によく使われています。既存システムのデータベースを Streaming Replication/Hot Standby に対応した PostgreSQL 9.0 にバージョンアップできない場合は、有用な構成といえます。