Personal tools

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 にバージョンアップできない場合は、有用な構成といえます。


(2011年2月22日 公開)
 

You are here: Home 読み物 技術的な記事 pgpool pgpool-II + Slony-Iクラスタ構成