ロジカルレプリケーションの紹介 (3)

ロジカルレプリケーション紹介の 3 回目となります。
1 回目では、ロジカルレプリケーションの概要や基本的な使い方を紹介しました。
2 回目では、ロジカルレプリケーションの構成や監視方法などを紹介しました。
今回はロジカルレプリケーションで発生するコンフリクトやエラーについて紹介します。

検証環境

対象とする PostgreSQL のバージョンは12、サーバやユーザの名前などについては、1 回目と同様に下記の通りです。

  • パブリッシャとなる PostgreSQL のサーバ名を first_server、データベース名を first
  • サブスクライバとなる PostgreSQL のデータベース名を second
  • ロジカルレプリケーション用のユーザ名を logi_user

そのため、

  • first=# で始まる SQL はパブリッシャで実行するコマンド
  • second=# で始まる SQL はサブスクライバで実行するコマンド

となります。

コンフリクトの解消

まずは、ロジカルレプリケーションで発生するコンフリクトについて紹介します。

パブリッシャからサブスクライバにレプリケーションされるデータ操作が、サブスクライバ上でデータ一意制約エラーなどによって、正しくレプリケーションされない場合があります。
この事象がコンフリクトと呼ばれます。

具体的にどのような挙動となるのかを以下で確認していきます。

パブリッシャの first_to_second テーブルにはデータが 1 件登録されています。
主キーは id です。

first=# SELECT * FROM first_to_second;
 id |    val
----+------------
  1 | from_first
(1 行)

サブスクライバの first_to_second テーブルにはデータが 2 件登録されています。

second=# SELECT * FROM first_to_second;
 id |       val
----+------------------
  1 | from_first
  2 | insert_by_second
(2 行)

この状態で、パブリッシャにデータを 1 件追加します。

first=# INSERT INTO first_to_second VALUES (2, 'from_first');
INSERT 0 1

first=# SELECT * FROM first_to_second;
 id |    val
----+------------
  1 | from_first
  2 | from_first
(2 行)

追加されました。
サブスクライバでどうなったかを確認します。

second=# SELECT * FROM first_to_second;
 id |       val
----+------------------
  1 | from_first
  2 | insert_by_second
(2 行)

サブスクライバでは、すでに id = 2 のレコードが存在していたため、コンフリクトが発生し、データが追加されませんでした。
サブスクライバ側のログを確認すると、以下のようなログが出力されます。

2022-04-08 13:13:35.662 JST [8075](err_code=23505) ERROR: duplicate key value violates unique constraint "first_to_second_pkey"
2022-04-08 13:13:35.662 JST [8075](err_code=23505) DETAIL: Key (id)=(2) already exists.

コンフリクトを解消する方法は 2 通りあります。

  1. pg_replication_origin_advance 関数を使って、コンフリクトしているトランザクションをスキップする。
  2. コンフリクトしないようにタプルデータを修正する。

1 の方法では、pg_replication_origin_advance 関数の引数に、レプリケーションの開始点としたい LSN を指定します。
誤った LSN を指定してしまいますと、レプリケーションさせたいトランザクションまでスキップされる可能性がありますので、LSN の指定は注意深く行ってください。

今回は 2 の方法で解消します。

サブスクライバで id = 2 のデータを削除します。

second=# DELETE FROM first_to_second WHERE id = 2;
DELETE 1

second=# SELECT * FROM first_to_second;
 id |    val
----+------------
  1 | from_first
  2 | from_first
(2 行)

無事にコンフリクトが解消され、パブリッシャで行ったデータ追加が反映されました。

以上が、ロジカルレプリケーションでのコンフリクトと、その解消方法です。

パブリッシャまたはサブスクライバでスキーマ変更を実施したことによるエラー

次に、テーブルのスキーマを変更したことにより発生するエラーを紹介します。

最初にロジカルレプリケーション用の logi_schema スキーマを作成し、テーブルをそのスキーマに移動します。

first=# CREATE SCHEMA logi_schema AUTHORIZATION logi_user;
CREATE SCHEMA

first=# ALTER TABLE first_to_second SET SCHEMA logi_schema;
ALTER TABLE

テーブルのスキーマ移動に伴い、パブリケーションに追加するテーブルにもスキーマを指定します。

first=# ALTER PUBLICATION pb_first_to_second SET TABLE logi_schema.first_to_second;
ALTER PUBLICATION

スキーマを変更した first_to_second テーブルに id = 2 のデータを追加して挙動を確認します。

first=# INSERT INTO logi_schema.first_to_second VALUES (2, 'from_logi_schema_first');
INSERT 0 1

サブスクライバ側ではデータが追加されませんでした。

second=# SELECT * FROM first_to_second;
 id |    val
----+------------
  1 | from_first
(1 行)

これは、サブスクライバ側で出力される下記ログのとおり、サブスクライバ側で first_to_second が logi_schema スキーマに存在していないことが原因です。

2022-04-08 13:28:43.874 JST [8705](err_code=55000) ERROR: logical replication target relation "logi_schema.first_to_second" does not exist

サブスクライバ側でも、logi_schema スキーマを作成し、first_to_second テーブルを移動させます。

second=# CREATE SCHEMA logi_schema AUTHORIZATION logi_user;
CREATE SCHEMA

second=# ALTER TABLE first_to_second SET SCHEMA logi_schema;
ALTER TABLE

改めて、サブスクライバでデータを確認します。

second=# SELECT * FROM logi_schema.first_to_second;
 id |          val
----+------------------------
  1 | from_first
  2 | from_logi_schema_first
(2 行)

エラーが解消されたことで、パブリッシャのデータ追加がサブスクライバに反映されました。

サブスクリプションの作成や削除時に発生するエラー

サブスクリプションを作成または削除する際にも、いくつかのエラーが発生する可能性がありますので、紹介いたします。

レプリケーションスロットの重複エラー

サブスクリプションの作成時に、パブリッシャにレプリケーションスロットを作成されます。
作成しようとするレプリケーションスロット名と同名のレプリケーションスロットが、すでにパブリッシャに存在する場合、下記のようなエラーが発生します。

second=# CREATE SUBSCRIPTION logical_sub CONNECTION 'host=192.168.33.10 port=5432 user=logi_user dbname=first' PUBLICATION logical_pub;
ERROR:  could not create replication slot "logical_sub": ERROR:  replication slot "logical_sub" already exists

このエラーが発生した場合には、レプリケーションスロット名が重複しないように、サブスクリプション作成時にレプリケーションスロット名を指定する必要があります。
レプリケーションスロット名の指定については、第 1 回目の記事を参考にしてください。

レプリケーションスロットの削除エラー

サブスクライバがパブリッシャに接続できていない状態で、サブスクリプションを削除しようとすると、下記のようなエラーが発生します。

second=# DROP SUBSCRIPTION sb_prefectures_products_fkey;
ERROR:  could not connect to publisher when attempting to drop the replication slot "sb_prefectures_products_fkey"
DETAIL:  The error was: could not connect to server: Connection refused
        Is the server running on host "first_server" (192.168.33.10) and accepting
        TCP/IP connections on port 5432?
HINT:  Use ALTER SUBSCRIPTION ... SET (slot_name = NONE) to disassociate the subscription from the slot.

これはパブリッシャに作成したレプリケーションスロットを削除できないために、サブスクリプションの削除がエラーとなりました。
この場合には、

  • サブスクライバとパブリッシャが正しく接続された状態にする
  • (ログメッセージ内の「HINT」にもあるように)サブスクリプションのレプリケーションスロットを NONE に変更する

上記いずれかの対応を行うことで、サブスクリプションを削除できるようになります。

レプリケーションスロットがパブリッシャに存在しないエラー

パブリッシャにレプリケーションスロットが存在しない状態で、サブスクリプションを削除しようとした場合には、下記のようなエラーが出力されます。

second=# DROP subscription logical_sub;
ERROR:  could not drop the replication slot "logical_sub_slot" on publisher
DETAIL:  The error was: ERROR:  replication slot "logical_sub_slot" does not exist

この場合にも、サブスクリプションのレプリケーションスロットを NONE に変更する必要があります。

サブスクリプションのレプリケーションスロットを NONE に変更する

サブスクリプションのレプリケーションスロットを変更する際に、サブスクリプションが、有効(ENABLE)状態ですと、下記のようなエラーが発生します。

second=# ALTER SUBSCRIPTION logical_sub SET (slot_name = NONE);
ERROR:  cannot set slot_name = NONE for enabled subscription

サブスクリプションを、無効(DISABLED)状態にすることで、レプリケーションスロットを変更できるようになります。

second=# ALTER SUBSCRIPTION logical_sub DISABLE;
ALTER SUBSCRIPTION

second=# ALTER SUBSCRIPTION logical_sub SET (slot_name = NONE);
ALTER SUBSCRIPTION

変更後にもロジカルレプリケーションを続ける場合には、忘れずにサブスクリプションを有効(ENABLE)状態に戻してください。

ロジカルレプリケーションが行われない場合に出力されるエラーについて

ここからは、ロジカルレプリケーションが正しく機能していない場合に出力されるエラーメッセージと、その対応方法について紹介します。

エラーメッセージ「could not receive data from WAL stream」について

レプリケーションの遅延や停滞が発生に伴い、サブスクライバ側に「could not receive data from WAL stream」というログメッセージが出力される場合があります。
これはサブスクライバで行っている WAL ストリーム受信でのネットワーク接続が切断されたことを表すメッセージです。
サブスクライバの OS レベルで正しくネットワーク接続できていない場合に発生します。
OS ログにネットワークに関するエラーメッセージが出力されていないか、ネットワーク環境に障害が発生していないかなどを確認してください。

エラーメッセージ「could not connect to the publisher」について

レプリケーションの遅延や停滞が発生に伴い、サブスクライバ側に「could not connect to the publisher」というログメッセージが出力される場合があります。
これはサブスクライバからパブリッシャに接続できないことを表すメッセージです。
パブリッシャの PostgreSQL が停止していないかを確認し、停止していないのであれば正しく接続できるかどうかを確認してください。

警告メッセージ「out of logical replication worker slots」について

ロジカルレプリケーションを開始した際に、サブスクライバに「out of logical replication worker slots」というログメッセージが出力される場合があります。
これは、ロジカルレプリケーションを行うためのワーカーの数が、設定値 max_logical_replication_workers を超えていることを表すメッセージです。
1 つのサブスクリプションで 2 つのワーカーが作成されるため、max_logical_replication_workers の設定を「サブスクリプションの数 x 2」以上にする必要があります。

エラーメッセージ「permission denied for table 《テーブル名》」について

ロジカルレプリケーションを開始した際に「permission denied for table 《テーブル名》」というログメッセージが出力される場合があります。
これは、サブスクリプションの作成時に指定したユーザが、該当テーブルに対してデータ操作を行うための権限が足りないことを表しています。
この場合には、データ操作を行うための権限を付与することで、正常にデータ操作がレプリケーションされるようになります。

おわりに

今回はロジカルレプリケーションで起こりうるコンフリクトや、エラーなどについて紹介いたしました。
まだまだロジカルレプリケーションの詳細な部分については紹介しきれていませんが、3 回に渡ってロジカルレプリケーションの概要や基本的な運用方法などを紹介いたしました。
本紹介記事の内容が PostgreSQL でロジカルレプリケーションを運用する際の手助けになれば幸いです。