PostgreSQL 10がやってくる！（その5） ロジカルレプリケーション基本編

# はじめに
今日はPostgreSQL 10の目玉機能の一つになるであろう、ロジカルレプリケーションを使ってみた話を書きますよ。

## 参考にしたもの
実はPostgreSQL 10のロジカルレプリケーションに関しては、[postgresql-jp Slack](https://postgresql-hackers-jp.herokuapp.com/)で、[篠田さん](https://www.facebook.com/noriyoshi.shinoda?fref=ts)＠HPEや前JPUG理事長の[永安さん](https://www.facebook.com/snaga)が簡単に検証した例を紹介しているので、そっちを見ればいいのだろうけど、まあこういうのは自分で手を動かさないと覚えないので、自分でもやってみることにする。

# ロジカルレプリケーションって何よ？
名前のとおり、論理WALを伝播させてレプリケーションさせる機能。
PostgreSQL 9.0から導入されたストリーミングレプリケーションは物理レプリケーションであり、WALをそのままダイレクトに反映させる機構。なので、レプリケーション元/先のメジャーバージョンやOSアーキテクチャが異なると適用できなかった。
PostgreSQL 10のロジカルレプリケーションは、異なるバージョンや異なるOSアーキテクチャ間でも適用が可能になる（はず）。また、ストリーミングレプリケーションでは、データベースクラスタ全体がレプリケーション対象となるため、特定のテーブルのみをレプリケーションしたいという用途には使えなかった。

PostgreSQL 9.4からサポートされているロジカルデコーディングの技術をベースにしている。今まではプラグインや論理WALを反映させる機構の作成が必要だった。
自分もロジカルデコーディングを使って、[PostgreSQLのWALをグラフデータベースNeo4jに反映させてみる](http://www.slideshare.net/toshiharada/do-postgresdreamofgraphdatabase)、というを作って遊んでいたりしてたけど、まあ、ぶっちゃけ面倒です。
ロジカルレプリケーションでは、幾つかのSQLコマンドを叩くだけで、ロジカルデコーディングでやっていた論理WALへの変換とレプリケーション先への反映をやってくれるというものになっている（はず）。

じゃあ、早速使ってみようか。なお、検証に使った版は今日の最新ソースをベースにしたものなんだけど、まだまだ開発途中の版なので、beta版リリース時には仕様等、ちょっと変わっているかもしれないので悪しからず。

# 準備
## postgresql.conf
ロジカルレプリケーションもストリーミングレプリケーションに類似した設定が必要になってくる。
ロジカルレプリケーションを動作させるためには、最低限、以下のパラメータの変更がレプリケーション元のサーバには必要になる。

|パラメータ名|設定|説明|
|:--        |:--|:--|
|wal_level  |logical|SRの場合にはreplicaでいいが、ロジカルレプリケーションの場合はlogical必須|
|max_wal_senders|1より大きい値|SRと併用する場合、その数の考慮要
|max_replication_slots|1より大きい値|論理レプリケーション要のスロット数。SRでスロットを併用する場合はそれも考慮要|

## pg_hba.conf
ロジカルレプリケーションもレプリケーションプロトコルを使った接続を行うので、ストリーミングレプリケーションと同様にpg_hba.confにreplicationデータベースとして登録しておく。
今回は面倒なのでpostgresのままで設定したが、実際に運用する場合には、レプリケーション専用ロールを使って接続させるのが良さげ。

# ロジカルレプリケーションの設定
レプリケーションの設定は、ソースと反映先の2つに設定が必要になる。
ひじょーにざっくり書くと、以下のような設定を行う。
Slony-Iとかの経験がある人だと、「あー、そんな用語だったねー」と思うはず。

* レプリケーション元
  * レプリケーション対象のテーブルを指定する。PKを持つテーブルにしておくのが無難。
  * CREATE PUBLICATIONコマンドでレプリケーション元の設定を行う。このときにロジカルレプリケーション対象のテーブルを選択する。
  * たぶんDB全体とか、そういうまるっとした設定もできるはず。
* レプリケーション先
  * レプリケーション先のテーブルを指定する。これもPKを持たせておかないと、後々いろいろ面倒なことになりそう。つーか、とりあえずはレプリケーション元に合わせて作っておくが吉。
  * CREATE SUBSCRIPTIONの購読先を指定する。このときには先だって作成していたロジカルレプリケーション元を指定する。

# 検証モデル
今回は同一Linuxマシン上にport=5432とport=5433の2つのデータベースクラスタを作成してそれを起動し、port=5432側(srv1)のtestdb内のjapan表をport=5433側(srv2)へ論理レプリケーションするモデルにした。
また、このモデルにより、町田問題が解決するのかを検証した。

# 検証その１
## srv1側

まず、何も考えずテーブルを定義する（これが後で後悔する原因となる）。

```
CREATE TABLE japan (id int, pref text, city text, data text);
CREATE PUBLICATION pub_srv1_testdb_japan FOR TABLE japan;
```
2行目がレプリケーション元となるテーブルを指定するためのPUBLICATION構文である。

## srv2側
レプリケーションの受け側でも、テーブルは同じように作成しておく。
レプリケーションの受け側ではPUBLICATION ではなく、SUBSCRIPTIONを作成する。

```
testdb=# CREATE TABLE japan (id int, pref text, city text, data text);
CREATE TABLE
testdb=# CREATE SUBSCRIPTION sub_srv2_testdb_japan CONNECTION 'dbname=testdb port=5432 user=postgres' PUBLICATION pub_srv1_testdb_japan;
NOTICE:  created replication slot "sub_srv2_testdb_japan" on publisher
CREATE SUBSCRIPTION
```

SUBSCRIPTIONのPUBLICATION句には、既に存在しているPUBLICATIONを指定する必要がある（が、存在チェックとかは今のところしてなさそう？）。
また、SUBSCRIPTIONに指定する名前と同じ名前のものが、レプリケーション送り元に生成されるっぽい。

これでjapanテーブルの内容がレプリケーションされるはず。

## 挿入内容をレプリケーションする

srv1側のjapanテーブルにINSERT文で挿入してみる。

```
testdb=# INSERT INTO japan VALUES (1, '東京', '大田', 'らーめん 潤');
INSERT 0 1
testdb=# INSERT INTO japan VALUES (2, '神奈川', '横浜', '麺恋亭');
INSERT 0 1
testdb=# TABLE japan;
 id |  pref  | city |    data
----+--------+------+-------------
  1 | 東京   | 大田 | らーめん 潤
  2 | 神奈川 | 横浜 | 麺恋亭
(2 rows)
```

srv2側のjapanテーブルを参照してみよう。

```
testdb=# SELECT * FROM japan;
 id |  pref  | city |    data
----+--------+------+-------------
  1 | 東京   | 大田 | らーめん 潤
  2 | 神奈川 | 横浜 | 麺恋亭
(2 rows)
```
やったね♪ きちんとsrv2側にも反映されました。

さて、ここでpgdata2のjapanにデータを挿入してみます。

```
testdb=# INSERT INTO japan VALUES (3, '神奈川', '町田', '竹之助');
INSERT 0 1
testdb=# TABLE japan;
 id |  pref  | city |    data
----+--------+------+-------------
  1 | 東京   | 大田 | らーめん 潤
  2 | 神奈川 | 横浜 | 麺恋亭
  3 | 神奈川 | 町田 | 竹之助
(3 rows)
```
prefが神奈川でcityが町田。何か間違ってるっけ？


ストリーミングレプリケーションとは異なり、反映先のテーブルにもフツーに挿入できます。これがロジカルレプリケーションの面白いところ。

さて、この状態でsrv1に、以下のようなINSERT文を使って挿入してみます。

```
testdb=# INSERT INTO japan VALUES (3, '神奈川', '町田', '竹之助');
INSERT 0 1
testdb=# TABLE japan;
 id |  pref  | city |    data
----+--------+------+-------------
  1 | 東京   | 大田 | らーめん 潤
  2 | 神奈川 | 横浜 | 麺恋亭
  3 | 神奈川 | 町田 | 竹之助
(3 rows)
```

あれ、フツーに挿入されちゃったよ。
この状態でsrv2側のjapanテーブルを参照してみると

```
testdb=# TABLE japan;
 id |  pref  | city |    data
----+--------+------+-------------
  1 | 東京   | 大田 | らーめん 潤
  2 | 神奈川 | 横浜 | 麺恋亭
  3 | 神奈川 | 町田 | 竹之助
  3 | 神奈川 | 町田 | 竹之助
(4 rows)
```
あ・・・これなんかアカンやつだｗ

## TRUNCATEしてみる

とりあえず、きれいな状態に戻したいので、srv1側でTRUNCATEしてみる。

```
testdb=# TRUNCATE japan;
TRUNCATE TABLE
testdb=# TABLE japan;
 id | pref | city | data
----+------+------+------
(0 rows)
```

で、srv2側のjapanテーブルを参照してみると・・・

```
testdb=# TABLE japan;
 id |  pref  | city |    data
----+--------+------+-------------
  1 | 東京   | 大田 | らーめん 潤
  2 | 神奈川 | 横浜 | 麺恋亭
  3 | 神奈川 | 町田 | 竹之助
  3 | 神奈川 | 町田 | 竹之助
(4 rows)
```

(ﾉ∀`)ｱﾁｬｰ
どうやら、現状はTRUNCATEはロジカルレプリケーションの対象になっていないようだ。
なので、TRUNCATEをかける場合には、レプリケーション元であるsrv1にもレプリケーション先であるsrv2の両方にTRUNCATEをかける必要があるようだ。めんどくせえ。
ぬーん、TRUNCATEのWALって出力されているはずなのに。
まあ、まだ開発中のバージョンだし、このへんは今後改善されていくと思います。
仕方ないのでpgdata2側も手動でTRUNCATEする。

```
testdb=# TRUNCATE japan;
TRUNCATE TABLE
testdb=# TABLE japan;
 id | pref | city | data
----+------+------+------
(0 rows)
```

## COPYでバルクロードしてみる
srv1のjapanテーブルに対して、以下のようなデータをCOPYでバルクロードする。

```
$ cat /tmp/japan.txt
1       東京    品川    丸直
2       東京    八丈島  蓮華
3       静岡    熱海    雨風本舗
4       静岡    藤枝    池田屋
5       神奈川  横浜    麺恋亭
6       神奈川  横浜    吉村家
7       神奈川  川崎    クマさん
8       神奈川  川崎    ニュータンタン本舗 本店
9       神奈川  町田    竹の助

testdb=# COPY japan FROM '/tmp/japan.txt';
COPY 9
testdb=# TABLE japan;
 id |  pref  |  city  |          data
----+--------+--------+-------------------------
  1 | 東京   | 品川   | 丸直
  2 | 東京   | 八丈島 | 蓮華
  3 | 静岡   | 熱海   | 雨風本舗
  4 | 静岡   | 藤枝   | 池田屋
  5 | 神奈川 | 横浜   | 麺恋亭
  6 | 神奈川 | 横浜   | 吉村家
  7 | 神奈川 | 川崎   | クマさん
  8 | 神奈川 | 川崎   | ニュータンタン本舗 本店
  9 | 神奈川 | 町田   | 竹の助
(9 rows)
```
COPYもフツーに出来ますね。
この状態でsrv2側も参照してみる。

```
testdb=# TABLE japan;
 id |  pref  |  city  |          data
----+--------+--------+-------------------------
  1 | 東京   | 品川   | 丸直
  2 | 東京   | 八丈島 | 蓮華
  3 | 静岡   | 熱海   | 雨風本舗
  4 | 静岡   | 藤枝   | 池田屋
  5 | 神奈川 | 横浜   | 麺恋亭
  6 | 神奈川 | 横浜   | 吉村家
  7 | 神奈川 | 川崎   | クマさん
  8 | 神奈川 | 川崎   | ニュータンタン本舗 本店
  9 | 神奈川 | 町田   | 竹の助
(9 rows)
```

うん。COPYも問題なく使えそう。

## UPDATE
じゃあ、次はUPDATEしてみよう。
町田は東京だという妄言を吐く人が町田のprefを東京に変えようとしてUPDATE文を発行してみるが、

```
testdb=# UPDATE japan SET pref = '東京' WHERE id = 9;
ERROR:  cannot update table "japan" because it does not have replica identity and publishes updates
HINT:  To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.
testdb=#
```
アバーッ！このように、更新に失敗してしまう。
これは **町田が神奈川だから更新に失敗する** のではなく、replica identityというものを設定しないとUPDATEとDELETEはできないっぽい。
とりあえずid にUNIQUE INDEX張って、ALTER TABLEでREPLICA IDENTITYを設定してみるか・・・。

```
testdb=# CREATE UNIQUE INDEX japan_id ON japan (id);
CREATE INDEX
testdb=#

testdb=# ALTER TABLE japan REPLICA IDENTITY USING INDEX japan_id ;
ERROR:  index "japan_id" cannot be used as replica identity because column "id" is nullable
testdb=#
```
グワーッ！UNIQUE+NULLABLEでないといけない，つまりPrimary Keyが存在し、それをREPLICA IDENTITYに設定しないといけないのな。
一旦、テーブルと、PUBLISHER、SUBSCRIBEを作り直すか・・・

## PUBLISHERとSUBSCRIBEの削除
とりあえずsrv1側のjapanテーブルを削除してみる。

```
testdb=# DROP TABLE japan CASCADE ;
DROP TABLE
testdb=# 
```
これでPUBLICATIONも消えるのかな・・・と思ったら消えてない。

```
testdb=# TABLE pg_publication;
        pubname        | pubowner | puballtables | pubinsert | pubupdate | pubdelete
-----------------------+----------+--------------+-----------+-----------+-----------
 pub_srv1_testdb_japan |       10 | f            | t         | t         | t
(1 row)
```
どうやら、PUBLISHERはテーブルとは依存関係にはないようだ。
あれかなー、PCREATE PUBLICATIONは、対象が表だけじゃなくてDB全体とか指定ができたりするからかな。

仕方ないからDROP PUBLICATIONで削除する。

```
testdb=# DROP PUBLICATION pub_srv1_testdb_japan;
DROP PUBLICATION
testdb=# TABLE pg_publication;
 pubname | pubowner | puballtables | pubinsert | pubupdate | pubdelete
---------+----------+--------------+-----------+-----------+-----------
(0 rows)
```
pgdata2側のTABLEとSUBSCRIBERも同様に削除する。

```
testdb=# DROP SUBSCRIPTION sub_srv2_testdb_japan;
NOTICE:  dropped replication slot "sub_srv2_testdb_japan" on publisher
DROP SUBSCRIPTION
testdb=# DROP TABLE japan ;
DROP TABLE
testdb=# TABLE pg_subscription;
 subdbid | subname | subowner | subenabled | subconninfo | subslotname | subpublications
---------+---------+----------+------------+-------------+-------------+-----------------
(0 rows)
```
SUBSCRIPTIONを削除すると、publisher(pgdata1側)の同名slotも削除されるようだ。

とりあえずこれで仕切り直しだ。

# 検証2 - primary key制約付きの表で再作成

japanテーブルのidをprimary keyとするようにして再定義してみる。

```
testdb=# CREATE TABLE japan (id int, pref text, city text, data text);
CREATE TABLE
testdb=# CREATE UNIQUE INDEX japan_id ON japan (id);
CREATE INDEX
testdb=# ALTER TABLE japan ADD PRIMARY KEY USING INDEX japan_id ;
ALTER TABLE
testdb=#
```
え、なんでid intの定義にprimary key を指定しないかだって？
それはPostgreSQL 10のパーティションテーブルにprimary keyが設定できないからだよっ！（後々、パーティションの一部のテーブルだけロジカルレプリケーションするという検証をしたいので）

それはさておき、PUBLICATIONを再度設定してみる。

```
CREATE PUBLICATION pub_srv1_testdb_japan FOR TABLE japan;
```
pgdata2側も同じようにテーブル定義、インデックス定義、プライマリキー定義をしておく。

```
testdb=# CREATE TABLE japan (id int, pref text, city text, data text);
CREATE TABLE
testdb=# CREATE UNIQUE INDEX japan_id ON japan (id);
CREATE INDEX
testdb=# ALTER TABLE japan ADD PRIMARY KEY USING INDEX japan_id ;
ALTER TABLE
testdb=#
```

で、SUBSCRIPTIONを再度定義する。

```
CREATE SUBSCRIPTION sub_srv2_testdb_japan CONNECTION 'dbname=testdb port=5432 user=postgres' PUBLICATION pub_srv1_testdb_japan;
testdb=# CREATE SUBSCRIPTION sub_srv2_testdb_japan CONNECTION 'dbname=testdb port=5432 user=postgres' PUBLICATION pub_srv1_testdb_japan;
NOTICE:  created replication slot "sub_srv2_testdb_japan" on publisher
CREATE SUBSCRIPTION
testdb=#
```

## UPDATE再検証
さっきと同じようにsrv1のjapanテーブルに対してCOPYでデータをロードしておく。
ロード後にsrv1はこうなっている。

```
testdb=# TABLE japan;
 id |  pref  |  city  |          data
----+--------+--------+-------------------------
  1 | 東京   | 品川   | 丸直
  2 | 東京   | 八丈島 | 蓮華
  3 | 静岡   | 熱海   | 雨風本舗
  4 | 静岡   | 藤枝   | 池田屋
  5 | 神奈川 | 横浜   | 麺恋亭
  6 | 神奈川 | 横浜   | 吉村家
  7 | 神奈川 | 川崎   | クマさん
  8 | 神奈川 | 川崎   | ニュータンタン本舗 本店
  9 | 神奈川 | 町田   | 竹の助
(9 rows)
```
少し経つと、srv2側のjapanテーブルにも更新内容が伝播されているのがわかる。

```
testdb=# TABLE japan;
 id |  pref  |  city  |          data
----+--------+--------+-------------------------
  1 | 東京   | 品川   | 丸直
  2 | 東京   | 八丈島 | 蓮華
  3 | 静岡   | 熱海   | 雨風本舗
  4 | 静岡   | 藤枝   | 池田屋
  5 | 神奈川 | 横浜   | 麺恋亭
  6 | 神奈川 | 横浜   | 吉村家
  7 | 神奈川 | 川崎   | クマさん
  8 | 神奈川 | 川崎   | ニュータンタン本舗 本店
  9 | 神奈川 | 町田   | 竹の助
(9 rows)
```

とりあえずUPDATEできるか再確認する。
pgdata2側も更新が伝播されてますねー。

```
testdb=# SELECT * FROM japan WHERE id = 9;
 id | pref | city |  data
----+------+------+--------
  9 | 東京 | 町田 | 竹の助
(1 row)
```
町田は東京に奪われました・・・ぐぬぬ。

## 重複エラーとなるINSERT
pgdata1からキー重複のあるINSERTももちろん、エラーになる。

```
testdb=# INSERT INTO japan VALUES (9, '神奈川', '町田', '竹之助');
ERROR:  duplicate key value violates unique constraint "japan_id"
DETAIL:  Key (id)=(9) already exists.
testdb=#
```
pgdata2から挿入しても同様。

```
testdb=# INSERT INTO japan VALUES (9, '神奈川', '町田', '竹之助');
ERROR:  duplicate key value violates unique constraint "japan_id"
DETAIL:  Key (id)=(9) already exists.
testdb=#
```

## DELETE
こんなに苦しいのなら悲しいのなら……町田などいらぬ！というわけではないのだが、DELETEもやってみる。
が、今回はちょいと一捻り。

まずBEGINしてDELETEする。
そうするとpgdata1上はもちろんこうなる。

```
testdb=# BEGIN;
BEGIN
testdb=# DELETE FROM japan WHERE id = 9;
DELETE 1
testdb=# SELECT * FROM japan WHERE id = 9;
 id | pref | city | data
----+------+------+------
(0 rows)
```

では、このときにpgdata2上はどう見えてみるのか。

はい、もちろんCOMMITされていない＝WALを生成していないのでpgdata2上は削除されていない。

```
testdb=# SELECT * FROM japan WHERE id = 9;
 id | pref | city |  data
----+------+------+--------
  9 | 東京 | 町田 | 竹の助
(1 row)

testdb=#
```
で、このときに、pgdata2上で町田の店を削除してCOMMITし、その後にpgdata1側をCOMMITするとどうなるのか。

```
testdb=# COMMIT;
COMMIT
testdb=#
```

pgdata1側でCOMMITしてみる。

```
testdb=# COMMIT;
COMMIT
testdb=#
```

あらら、この場合はしれっと両方とも削除とCOMMITに成功しちゃいますね。
なんかイヤーンな感じ。

## 挿入のコンフリクト

さて、現在はpgdata1→pgdata2の片方向のレプリケーションしか設定していない。しかし、pgdata2側のjapanは更新可能である。
では、両方の更新がコンフリクトしたらどうなるのか。
これはなかなか面倒そうな話ですねー。
コンフリクト検証は別途、みっちりやらねばと思うけど、とりあえず、簡単なケースを今回は1つだけやってみる。
INSERT時のキーが重複するようなケースだ。

まず、srv2側(反映先)のトランザクションを先にINSERTまで実施する。

```
testdb=# BEGIN;
BEGIN
testdb=# INSERT INTO japan VALUES (9, '東京都', '町田', 'RA-MEN 3SO');
INSERT 0 1
testdb=# SELECT * FROM japan WHERE id = 9;
 id |  pref  | city |    data
----+--------+------+------------
  9 | 東京都 | 町田 | RA-MEN 3SO
(1 row)
```

その後に、pgdata1側のトランザクションでINSERTまで実施する。

```
testdb=# BEGIN;
BEGIN
testdb=# INSERT INTO japan VALUES (9, '神奈川', '町田', 'RA-MEN 3SO');
INSERT 0 1
testdb=# SELECT * FROM japan WHERE id = 9;
 id |  pref  | city |    data
----+--------+------+------------
  9 | 神奈川 | 町田 | RA-MEN 3SO
(1 row)
```
この状態で、pgdata2側がコミットする。

```
testdb=# COMMIT;
COMMIT
testdb=# SELECT * FROM japan WHERE id = 9;
 id |  pref  | city |    data
----+--------+------+------------
  9 | 東京都 | 町田 | RA-MEN 3SO
(1 row)
```

pgdata1側がその後にコミットする

```
testdb=# COMMIT;
COMMIT
testdb=#
```

ふむ。
srv2側を見ると、

```
testdb=# SELECT * FROM srv WHERE id = 9;
 id |  pref  | city |    data
----+--------+------+------------
  9 | 東京都 | 町田 | RA-MEN 3SO
(1 row)
```

げげ、レプリケーションが破綻しとるがなｗ

```
testdb=# INSERT INTO japan VALUES (10, '山梨', '大月', '喜楽');
INSERT 0 1
testdb=# SELECT * FROM japan WHERE id = 10;
 id | pref | city | data
----+------+------+------
 10 | 山梨 | 大月 | 喜楽
(1 row)
```


この後にpgdata2側

```
testdb=# SELECT * FROM japan WHERE id = 10;
 id | pref | city | data
----+------+------+------
(0 rows)
```
を参照してみるが、レプリケーションされてない。
srv1側には実はこんなエラーがしれっと出ている。

```
2017-02-19 19:10:39.583 JST [7842] LOG:  starting logical decoding for slot "sub_srv2_testdb_japan"
2017-02-19 19:10:39.583 JST [7842] DETAIL:  streaming transactions committing after 0/16A0920, reading WAL from 0/16A0340
2017-02-19 19:10:39.583 JST [7842] LOG:  logical decoding found consistent point at 0/16A0340
2017-02-19 19:10:39.583 JST [7842] DETAIL:  There are no running transactions.
2017-02-19 19:10:39.585 JST [7842] LOG:  could not receive data from client: Connection reset by peer
2017-02-19 19:10:39.585 JST [7842] LOG:  unexpected EOF on standby connection
```


また、srv2側には実はこんなエラーがしれっと出ている。

```
2017-02-19 19:10:39.584 JST [7841] ERROR:  duplicate key value violates unique constraint "japan_id"
2017-02-19 19:10:39.584 JST [7841] DETAIL:  Key (id)=(9) already exists.
2017-02-19 19:10:39.585 JST [7108] LOG:  worker process: logical replication worker for subscription 16406 (PID 7841) exited with exit code 1
2017-02-19 19:10:44.586 JST [7115] LOG:  starting logical replication worker for subscription "sub_srv2_testdb_japan"
2017-02-19 19:10:44.588 JST [7847] LOG:  logical replication apply for subscription "sub_srv2_testdb_japan" has started
2017-02-19 19:10:44.592 JST [7847] ERROR:  duplicate key value violates unique constraint "japan_id"
2017-02-19 19:10:44.592 JST [7847] DETAIL:  Key (id)=(9) already exists.
```

さて、この状態からどうやって復旧するんだろう・・・

```
testdb=# SELECT * FROM japan WHERE id = 9;
 id |  pref  | city |    data
----+--------+------+------------
  9 | 東京都 | 町田 | RA-MEN 3SO
(1 row)
```

さて、このエラーだが、これは **東京が不当に町田を所有しているからエラー** ・・・になるのではなく、id=9の行がpgdata2側に存在するために、pgdata1側のid=9のINSERTが成功せず、その後のINSERTも反映を待っている、ということなんだろう。
なので、pgdata2のid＝9の行を削除する。

DELETE直後にみると

```
testdb=# SELECT * FROM japan WHERE id = 9;
 id | pref | city | data
----+------+------+------
(0 rows)
```

あれ？行がない。
が、ほんのすこし経過すると、

```
testdb=# SELECT * FROM japan WHERE id = 9;
 id |  pref  | city |    data
----+--------+------+------------
  9 | 神奈川 | 町田 | RA-MEN 3SO
(1 row)

testdb=# SELECT * FROM japan WHERE id = 10;
 id | pref | city | data
----+------+------+------
 10 | 山梨 | 大月 | 喜楽
(1 row)
```

pgdata1側のid=9のINSERTが反映され、その後のid=10のINSERTも反映される。
おもしろーい！

# これから検証すること。

- マルチマスタ構成
- 様々なコンフリクト
- パーティションとの組み合わせ
これはいろいろ試しがいのある機能だぜ・・・。

# まとめ
- ロジカルレプリケーションではテーブル単位にレプリケーション対象が指定可能。
- 反映元ではCREATE PUBLICAGTIONコマンドを実行させること。
- 反映先では、CREATE SUBSCRIPTIONコマンドを実行させること。
- 現状はTRUNCATEはロジカルレプリケーションはきちんと動作しないよ。要検証。
- 反映元のテーブルにはPK相当を持たせること。でないとUPDATEやDELETEができなくなる。
- 反映先へ更新がある場合、レプリケーション接続が切れるケースがある。要注意。
- 挙動が変な場合、サーバログを確認すべし。
- 町田は神奈川。