PostgreSQL マルチマスタ（BDR）を試してみた

# PostgreSQL BDR (Bi-Directional Replication)とは

PostgreSQL9.0 から Streaming Replication が実装されています。マスタからDBの更新情報(WAL) を複数のスレーブに対して送信・適用する事で、マスタとスレーブのデータ同期を行います。
マスタは参照/更新が可能ですが、スレーブでは参照のみで更新を行う事はできません。
また、マスタ側が障害となった場合SPOFとなる為、PGPOOL等の外部ツールにより可用性を向上させる必要があります。

9.4 からは logical replication が追加されました。BDRはこの logical replication を使うことで、1マスタNスレーブ構成だった Streaming Replication を Nマスタに拡張することができる外部ツールです。
BDR のマスタはそれぞれ、参照/更新が可能です。

![image](https://qiita-image-store.s3.amazonaws.com/0/66824/0521c18d-a819-6828-16c7-4e67dc4e0087.png)


# 環境
・Cent OS 6.7(2台)
・PostgreSQL9.4

# インストール
マニュアルを参考にインストールします。[BDR 0.10.0 Documentation](http://bdr-project.org/docs/next/installation-source.html)

```lang:事前準備_node1&node2
# chkconfig --level 2345 iptables off
# service iptables stop
# groupadd -g 301 postgres
# useradd -d /home/postgres -m -u 301 -g postgres postgres
# id postgres
uid=301(postgres) gid=301(postgres) groups=301(postgres)
```

```lang:インストール準備_node1&node2
# cd /usr/local/src
# wget http://yum.postgresql.org/9.4/redhat/rhel-6-x86_64/pgdg-centos94-9.4-2.noarch.rpm
# rpm -ihv pgdg-centos94-9.4-2.noarch.rpm
# yum check-update
# yum groupinstall "Development Tools"
# yum install yum-utils openjade docbook-dtds docbook-style-dsssl docbook-style-xsl
# yum-builddep postgresql94
```

```lang:PostgreSQL9.4をインストール_node1&node2
# git clone -b bdr-pg/REL9_4_STABLE git://git.postgresql.org/git/2ndquadrant_bdr.git postgresql-bdr
# mkdir /usr/local/pgsql
# chown -R postgres:postgres /usr/local/pgsql
# chown -R postgres:postgres /usr/local/src/postgresql-bdr
# su - postgres
$ cd /usr/local/src/postgresql-bdr
$ ./configure --prefix=/usr/local/pgsql --enable-debug --with-openssl
$ make -j4 -s install-world
$ exit
#
```

```lang:BDRをインストール_node1&node2
# id
uid=0(root) gid=0(root) groups=0(root)
# cd /usr/local/src
# git clone -b bdr-plugin/REL0_9_STABLE git://git.postgresql.org/git/2ndquadrant_bdr.git bdr-plugin
# chown -R postgres:postgres /usr/local/src/bdr-plugin
# su - postgres
$ cd /usr/local/src/bdr-plugin
$ PATH=/usr/local/pgsql/bin:"$PATH" ./configure
$ make -j4 -s all
$ make -s install
$ ll /usr/local/pgsql/bin/ | grep bdr
-rwxr-xr-x 1 postgres postgres   928885 Apr 18 06:29 bdr_dump
-rwxr-xr-x 1 postgres postgres   169288 Apr 18 06:29 bdr_init_copy
-rwxr-xr-x 1 postgres postgres     2399 Apr 18 06:29 bdr_initial_load
-rwxr-xr-x 1 postgres postgres    89440 Apr 18 06:29 bdr_resetxlog
⇒上記の4ファイルが存在する事を確認
```

# PostgreSQL設定

```lang:PostgreSQLを起動させる準備_node1
$ PATH=/usr/local/pgsql/bin:"$PATH"
$ mkdir /usr/local/pgsql/bdr
$ initdb -D /usr/local/pgsql/bdr/node1 -A trust -U postgres
```

```lang:PostgreSQLを起動させる準備_node2
$ PATH=/usr/local/pgsql/bin:"$PATH"
$ mkdir /usr/local/pgsql/bdr
$ initdb -D /usr/local/pgsql/bdr/node2 -A trust -U postgres
```


```lang:PostgreSQL設定_node1
$ cd /usr/local/pgsql/bdr
$ cat >> ./node1/postgresql.conf <<EOF
listen_addresses = '*'
shared_preload_libraries = 'bdr'
wal_level = 'logical'
track_commit_timestamp = on
max_connections = 100
max_wal_senders = 10
max_replication_slots = 10
max_worker_processes = 10
log_error_verbosity = verbose
log_min_messages = debug1
log_line_prefix = 'd=%d p=%p a=%a%q '
bdr.default_apply_delay=2000   # milliseconds
bdr.log_conflicts_to_table=on
EOF

$ cat >> ./node1/pg_hba.conf <<EOF
host    all             all           XX.XX.0.0/16    trust
local   replication     postgres                      trust
host    replication     postgres      XX.XX.0.0/16    trust
host    replication     postgres      ::1/128         trust
EOF
※XX.XX.XX.XX…node1とnode2が通信可能となるように設定
```

```lang:PostgreSQL設定_node2
$ cd /usr/local/pgsql/bdr
$ cat >> ./node2/postgresql.conf <<EOF
listen_addresses = '*'
shared_preload_libraries = 'bdr'
wal_level = 'logical'
track_commit_timestamp = on
max_connections = 100
max_wal_senders = 10
max_replication_slots = 10
max_worker_processes = 10
log_error_verbosity = verbose
log_min_messages = debug1
log_line_prefix = 'd=%d p=%p a=%a%q '
bdr.default_apply_delay=2000   # milliseconds
bdr.log_conflicts_to_table=on
EOF

$ cat >> ./node2/pg_hba.conf <<EOF
host    all             all           XX.XX.0.0/16    trust
local   replication     postgres                      trust
host    replication     postgres      XX.XX.0.0/16    trust
host    replication     postgres      ::1/128         trust
EOF
※XX.XX.XX.XX…node1とnode2が通信可能となるように設定
```


```lang:PostgreSQL起動・DB作成_node1
$ pg_ctl -l /usr/local/pgsql/bdr/node1/postgres.log -D /usr/local/pgsql/bdr/node1 -w start
$ createdb -U postgres bdrdemo
```

```lang:PostgreSQL起動・DB作成_node2
$ pg_ctl -l /usr/local/pgsql/bdr/node2/postgres.log -D /usr/local/pgsql/bdr/node2 -w start
$ createdb -U postgres bdrdemo
```

```lang:CREATE_EXTENSION実行_node1
$ psql -U postgres bdrdemo
CREATE EXTENSION btree_gist;
CREATE EXTENSION bdr;
SELECT bdr.bdr_group_create(local_node_name := 'node1',node_external_dsn := 'host=node1_ip port=5432 dbname=bdrdemo');

SELECT bdr.bdr_node_join_wait_for_ready();
\q
※node1_ipのIP
```

```lang:CREATE_EXTENSION実行_node2
$ psql -U postgres bdrdemo
CREATE EXTENSION btree_gist;
CREATE EXTENSION bdr;
SELECT bdr.bdr_group_join(local_node_name := 'node2',node_external_dsn := 'host=node2_ip port=5432 dbname=bdrdemo', join_using_dsn := 'host=node1_ip port=5432 dbname=bdrdemo');

SELECT bdr.bdr_node_join_wait_for_ready();
\q
※node1_ipのIP
※node2_ipのIP
```

```lang:BDR状態確認_node1,node2
$ psql -U postgres bdrdemo
select * from bdr.bdr_nodes;

node_sysid   |node_timeline|node_dboid|node_status|node_name|              node_local_dsn          |           node_init_from_dsn
-------------+-------------+----------+-----------+---------+--------------------------------------+-------------------------------------------
XXXXXXXXXXXXX|            1|     16385|r          |node1    |host=node1_ip port=5432 dbname=bdrdemo|
XXXXXXXXXXXXX|            1|     16385|r          |node2    |host=node2_ip port=5432 dbname=bdrdemo|host=node1_ip port=5432 dbname=bdrdemo
\q
※node1_ipのIP
※node2_ipのIP
```


# PostgreSQL BDR動作確認

```lang:node1_テーブルを作成してインサート
$ psql -U postgres bdrdemo
CREATE TABLE bdr_test (c1 INT, c2 text, PRIMARY KEY (c1));
INSERT INTO bdr_test VALUES (1, 'test1');
INSERT INTO bdr_test VALUES (2, 'test2');
SELECT * FROM bdr_test;
 c1 |  c2
----+-------
  1 | test1
  2 | test2
(2 row)
```

```lang:node2に反映されているか確認
$ psql -U postgres bdrdemo
SELECT * FROM bdr_test;
 c1 |  c2
----+-------
  1 | test1
  2 | test2
(2 row)
```

```lang:node2でデリート
$ psql -U postgres bdrdemo
DELETE FROM bdr_test WHERE c1 = 2;
SELECT * FROM bdr_test;
 c1 |  c2
----+-------
  1 | test1
(1 row)
```

```lang:node1で確認
$ psql -U postgres bdrdemo
SELECT * FROM bdr_test;
 c1 |  c2
----+-------
  1 | test1
(1 row)
```

# 更新の重複
・マルチマスタにすると、PrimaryKeyとなっている一意の項目に対し、node1とnode2で同時に更新しようとした場合に競合が発生します。
それを回避する為、BDRは複数のnodeで重複しない値を生成するGlobal Sequencesを提供しています。Global Sequencesを使うと各nodeで1000個ずつの値を割り振り、node1は1～1000、node2は1001～2000の値が使われるようになります。


# 最後に
・あっさりとPostgreSQLのマルチマスタが組めました。今までは、可用性を高める方法として、streaming replicationかPGPOOL等でやってましたが、PostgreSQL BDRも十分に使えると思いました。