5分で試すPostgreSQLのLogical Replication

PostgreSQL 10 Beta3もリリースされ、正式リリースも近づいてきたのでPostgreSQL 10の目玉機能の一つであるLogical Replicationを使ってみます。
ソースは[ここ](https://www.postgresql.org/download/)から取得できます。

# 特徴

Logical Replicationとは何か理解するときに以下の2つの特徴を知っておくと便利です。

* Logical Decoding
* Publication/Subscription

## Logical Decoding
Logical DecodingはPostgreSQL 9.3から導入された機能で、「WAL(トランザクションログ)をデコードする」機能です。これを使うことで、PostgreSQLが出力WALを任意の形式にデコードすることができます。また、デコード部分はプラグイン形式になっているので色々なプラグインが存在します。(最近Amazon PostgreSQL RDSでもwal2jsonが[サポートされましたね](https://aws.amazon.com/about-aws/whats-new/2017/07/amazon-rds-for-postgresql-supports-new-minor-versions-9-6-3-and-9-5-7-and-9-4-12-and-9-3-17/)。)

* [test_decoding](https://www.postgresql.jp/document/current/html/test-decoding.html)
* [wal2json](https://github.com/eulerto/wal2json)
* [decoder_raw](https://github.com/michaelpq/pg_plugins/tree/master/decoder_raw)

PostgreSQLのLogical ReplicationではLogical Decodingを利用しています。

## Publication/Subscription
PostgreSQLのLogical Replicationでは、Publication/Subscription形式を採用しており、Publication(送信側)には複数のテーブルを登録することができ、Subscription(受信側)は複数のPublicationを指定してSubscribeすることができます。

# 使い方
2つのPublication(pub_a, pub_b)にそれぞれ1つテーブルが登録されており、その両方のPublicationを設定するSubscrption(test_sub)を作成して、テーブル単位でのレプリケーションができることを確認します。
**※デフォルト設定から`wal_level`を`logical`に変更します。**


```
   pub_a               sub_test
+--------+            +--------+
| tbl_a  | ---------> | tbl_a  |
+--------+      ----> | tbl_b  |  
               /      +--------+
   pub_b      /
+--------+   /
| tbl_b  | -/
+--------+
  
```

## Publicationの設定
### 1. テーブル(主キー付き)を作成

```sql
(pub)=# CREATE TABLE tbl_a (c int primary key);
CREATE TABLE
(pub)=# CREATE TABLE tbl_b (c int primary key);
CREATE TABLE
```

### 2. データを挿入

```sql
(pub)=# INSERT INTO tbl_a SELECT generate_series(1,100);
INSERT 100
(pub)=# INSERT INTO tbl_b SELECT generate_series(1,100);
INSERT 100
```

### 3. Publicationを作成
デフォルトではすべての動作(INSERT, UPDATE, DELETE)がSubscriber側に送られます。

```sql
(pub)=# CREATE PUBLICATION pub_a FOR TABLE tbl_a;
CREATE PUBLICATION
(pub)=# CREATE PUBLICATION pub_b FOR TABLE tbl_b;
CREATE PUBLICATION
```

## Subscriptionの設定
### 1. テーブル作成
**DDLはレプリケートされない**のでSubscription側でも同様にテーブルを作成します。

```sql
(sub)=# CREATE TABLE tbl_a (c int primary key);
CREATE TABLE
(sub)=# CREATE TABLE tbl_b (c int primary key);
CREATE TABLE
```

### 2. Subscriptionを作成
`CONNECTION`にはPublisher側への接続文字列をいれます。

```sql
(sub)=# CREATE SUBSCRIPTION sub_test CONNECTION 'dbname=postgres host=...' PUBLICATION pub_a, pub_b;
CREATE SUBSCRIPTION
```

デフォルトでSubscription作成時に各テーブルの初期データコピーを行います。

```bash
$ cat pg.log
LOG:  logical replication apply worker for subscription "sub_test" has started
LOG:  starting logical decoding for slot "sub_test"
DETAIL:  streaming transactions committing after 0/165A1D0, reading WAL from 0/165A198
LOG:  logical decoding found consistent point at 0/165A198
DETAIL:  There are no running transactions.
LOG:  logical replication table synchronization worker for subscription "sub_test", table "tbl_a" has started
LOG:  logical replication table synchronization worker for subscription "sub_test", table "tbl_b" has started
LOG:  logical decoding found consistent point at 0/165A1D0
DETAIL:  There are no running transactions.
LOG:  logical replication table synchronization worker for subscription "sub_test", table "tbl_a" has finished
LOG:  logical decoding found consistent point at 0/165A208
DETAIL:  There are no running transactions.
LOG:  logical replication table synchronization worker for subscription "sub_test", table "tbl_b" has finished
```

### 3. 初期データコピーを確認

```sql
(sub)=# SELECT count(*) FROM tbl_a;
 count 
-------
   100
(1 row)

(sub)=# SELECT count(*) FROM tbl_b;
 count 
-------
   100
(1 row)
```

## Logical Replicationの挙動を見てみる
* Publication側 : tbl_aとtbl_bそれぞれに1行追加します。

```sql
(pub)=# INSERT INTO tbl_a VALUES(444);
INSERT 1
(pub)=# INSERT INTO tbl_b VALUES(555);
INSERT 1
```

* Subscription側 : tbl_aとtbl_bの中を見てみます。

```sql
(sub)=# SELECT max(c) FROM tbl_a;
 max 
-----
 444
(1 row)

(sub)=# SELECT max(c) FROM tbl_b;
 max 
-----
 555
(1 row)
```

２つのテーブルに対する変更がレプリケートされています。

* もちろん、Subscriber側でのテーブルの更新も可能です

```sql
(sub)=# UPDATE tbl_a SET c = 999 WHERE c = 1;
UPDATE 1
(sub)=# SELECT max(c) FROM tbl_a;
 max 
-----
 999
(1 row)
```

おわり。