PostgreSQL での論理レプリケーション (Logical Replication) の設定手順メモです。

環境

論理レプリケーションについて

“論理レプリケーション” とはデータベースのレプリケーション方式の 1つです。”物理レプリケーション” ではデータファイルを物理的に (バイト単位で) そのままレプリケーションするのに対し、論理レプリケーションではソースデータベースで行われた変更内容を SQL 等の形で宛先データベースへレプリケーションします。

物理レプリケーションではソースデータベースと宛先データベースのバージョンが一致しないといけないなどの制約が多いですが、論理レプリケーションでは異なるバージョン間でのレプリケーションが可能など柔軟な構成が可能です。

この記事では PostgreSQL で論理レプリケーションを構成する手順を説明します。

用語

PostgreSQL の論理レプリケーションではいくつか固有の用語が使われています。

論理レプリケーションの概要

Logical Decoding Plugin

PostgreSQL では、Publication で定義されたテーブルへの変更が Replication Slot に記録され、Subscription は Replication Slot から変更データを取得し、自身のテーブルに反映します。

変更データは WAL から取得されますが、WAL は物理的な変更のみを記録しています (例えば “データファイルのどのバイトからどのバイトまでを変更したか” 等) 。

論理レプリケーションでは物理的な表現から論理的な表現 (例えば “ID 10 のレコードの name カラムを変更する” 等) に変換する必要があり、この変換を行うプラグインを Logical Decoding Plugin と呼びます。

デフォルトでは pgoutput というプラグインが使用されており、今回もこのプラグインを使用します。

Replication Slot の並行性についての注意点

複数の Subscription が 1つの Replication Slot から変更データを取得することは可能ですが、Replication Slot は一度に 1つの Subscription に対してしかデータを送ることができません。

公式ドキュメントには以下のように記載されています:

A logical replication slot knows nothing about the state of the receiver(s). It's even possible to have multiple different receivers using the same slot at different times; they'll just get the changes following on from when the last receiver stopped consuming them. Only one receiver may consume changes from a slot at any given time.

そのため、レプリケーションを構成する際は 1つの Subscription に対して 1つの Replication Slot を作成するのが推奨されます。

Conflict (変更の競合) について

PostgreSQL の論理レプリケーションでは、Subscriber は Read Only ではありません。

Subscriber でも書き込みを行うことができるため、例えば Publisher サーバーが障害で停止してしまった場合も、何も変更せず Subscriber へ書き込みを行うことができます。

Subscriber での変更が Publisher での変更と競合した場合、”Conflict” という競合状態になります。

Conflict は自動で解決されないため、利用者が手動で解決する必要があります。競合が解決されるまで、レプリケーションは停止します。

競合を解決するには、Subscriber で特定のトランザクションログをスキップするよう設定して競合を回避するよう操作を行います。

一般的なシナリオでは、基本的に Conflict が発生することは無いと思いますので、この記事では詳細は省きます。公式ドキュメントを参照してください。

論理レプリケーションに関する設定

論理レプリケーションを行うには、以下の設定項目を適切に設定する必要があります。

Publisher の設定

設定項目説明
wal_levelWAL に記録される変更データの内容を指定します。
論理レプリケーションを行うには logical と設定する必要があります。
max_replication_slotsPublisher で作成される Replication Slot の最大数。
Subscription の数 + レプリケーションするテーブル数 を設定します。
(初めにテーブル同期を行う時に、一時的な Replication Slot が作成されることがあるため)
デフォルトは 10 です。
max_wal_sendersmax_replication_slots と同じ値を設定します。
もし物理レプリケーションも同時に行う場合は、同時に接続される物理レプリケーションの数も追加して設定します。
max_slot_wal_keep_sizeReplication Slot で保持される WAL の最大サイズを指定します。
このサイズを超えると古い WAL から削除されます。もし Subscriber がその古い WAL をまだ適用していなかった場合、Replication はエラーで停止します。
デフォルトは -1 (disabled) で制限はありません。
Subscriber が受信した WAL は自動的に Replication Slot から削除されるため、デフォルトの設定のままで大丈夫です。

Subscriber の設定

設定項目説明
wal_levelPublisher と同様、logical に設定します。
max_logical_replication_workers論理レプリケーションで変更データを取得し反映する worker プロセスの最大数を指定します。
Subscription の数 + レプリケーションするテーブル数 を指定します。
(初めにテーブル同期を行う時に、一時的に追加のプロセスが実行されることがあるため)
デフォルトは 4 です。
max_worker_processesPostgreSQL のバックグラウンドプロセスの最大数を指定する項目です。
max_logical_replication_workers で設定した値 + 1 以上を指定します。
(変更データを取得/反映するプロセスは、バックグラウンドプロセスの 1つとして実行されるため)
デフォルトは 8 です。

手順

以下、PostgreSQL で論理レプリケーションを設定する手順を説明します。

今回使用する環境は以下の通りです。

1). Publisher の設定

まず Publisher の設定を行います。Publisher では必要なサーバー設定とユーザー作成などを行った後、レプリケーションするテーブルのスキーマを出力し、最後に Publication を作成します。

1-1). サーバー設定とユーザー作成

まず論理レプリケーションのためのサーバー設定と、レプリケーション用ユーザーを作成します。

レプリケーション用ユーザーには REPLICATION 属性を付与する必要があります。

また PostgreSQL のレプリケーションでは Subscriber が Publisher にアクセスして変更データを取得するため、Subscriber からのリモートアクセスを許可するよう併せて設定します。

Publisher サーバー

# レプリケーション用の設定
$ sudo nano /etc/postgresql/14/main/postgresql.conf
---
listen_addresses = '*'   # リモートアクセスを有効化
wal_level = logical
max_replication_slots = 5
max_wal_senders = 5
max_slot_wal_keep_size = -1
---

# Subscriber からのリモートアクセスを許可
$ sudo nano /etc/postgresql/14/main/pg_hba.conf
---
host     testdb     repuser     172.16.11.17/32     scram-sha-256
---

$ sudo systemctl restart postgresql

# レプリケーション用ユーザーの作成
$ sudo su postgres
$ psql
postgres=# CREATE ROLE repuser REPLICATION LOGIN PASSWORD 'password';
postgres=# exit

1-2). テスト用データベースの作成

テスト用のデータベースとテーブルを作成します。

論理レプリケーションでは初めにテーブルのスナップショットが取得されて同期されるので、レプリケーション用ユーザーへは同期される各テーブルに対して SELECT 権限が付与されている必要があります。

Publisher サーバー

$ sudo su postgres
$ psql

# テスト用データベースとテーブルを作成
postgres=# CREATE DATABASE testdb;
postgres=# \c testdb

# テスト用テーブルを作成
testdb=# CREATE TABLE table1 (id INT, name TEXT);
testdb=# CREATE TABLE table2 (id INT, name TEXT);
testdb=# INSERT INTO table1 (id, name) SELECT s, CONCAT('name1 ', s) FROM GENERATE_SERIES(1,10) s;
testdb=# INSERT INTO table2 (id, name) SELECT s, CONCAT('name1 ', s) FROM GENERATE_SERIES(1,10) s;

# レプリケーション用ユーザーに権限付与
testdb=# GRANT SELECT ON ALL TABLES IN SCHEMA public TO repuser;
testdb=# exit

1-3). レプリケーションするテーブルのスキーマを出力

最後に、レプリケーションするテーブルのスキーマを出力します。

レプリケーションを開始する前に、Subscriber 側でも同じデータベース、同じスキーマのテーブルを前もって作成しておく必要があるので、ここでテーブルのスキーマを出力して保存しておきます。

(なお、Publication を作成した後だとスキーマに Publication が含まれてしまうため、Publication を作成する前にスキーマを出力しておきます)

今回は テーブルスキーマを /replication/testdb_schema.sql へ保存します。

Publisher サーバー

# テーブルスキーマを出力
$ sudo mkdir /replication
$ sudo chown postgres:postgres /replication
$ sudo chmod 755 /replication

$ sudo su postgres
$ pg_dump --schema-only testdb > /replication/testdb_schema.sql

ここで作成したスキーマファイル (testdb_schema.sql) は Subscriber サーバーへコピーしておきます。

1-4). Publication の作成

最後に Publication を作成します。

今回は testdbpub という名前の Publication を作成し、上記で作成した各テーブルをレプリケーション対象に含めています。

Publisher サーバー

$ sudo su postgres
psql testdb

# Publication を作成
testdb=# CREATE PUBLICATION testdbpub FOR TABLE table1, table2;

# 作成された Publication を確認
testdb=# SELECT * FROM pg_publication_tables;
pubname  | schemaname | tablename 
-----------+------------+-----------
 testdbpub | public     | table1
 testdbpub | public     | table2
(2 rows)

2). Subscriber 側の設定

Subscriber 側では、必要なサーバー設定を行った後、レプリケーションの宛先となるデータベースとテーブルを作成し、最後に Subscription を作成します。

2-1). サーバー設定

Subscriber サーバー

$ sudo nano /etc/postgresql/14/main/postgresql.conf
---
wal_level = logical
max_logical_replication_workers = 5
max_worker_processes = 10
---

$ sudo systemctl restart postgresql

2-2). レプリケーションの宛先となるデータベース / テーブル作成

手順 1-3) で取得したスキーマファイルを使って、レプリケーションの宛先となるデータベースやテーブルを作成します。

スキーマファイルは /replication/testdb_schema.sql にコピーしてきたものと想定しています。

Subscriber サーバー

$ sudo su postgres
$ psql

# まずデータベースを作成します
postgres=# CREATE DATABASE testdb;
postgres=# exit

# スキーマファイルを使ってテーブルを作成します
# ※ "repuser" は Subscriber にはいないため、GRANT 文でエラーとなりますが問題ありません 
$ psql testdb < /replication/testdb_schema.sql
...
ERROR:  role "repuser" does not exist  # <-- これは問題ありません
...

2-3). Subscription の作成

最後に Subscription を作成します。

Subscription を作成すると自動的に Publisher 側で Replication Slot が作成され、初期同期が行われた後 レプリケーションが開始されます。

Subscriber サーバー

$ sudo su postgres
$ psql testdb

# Subscription 作成
testdb=# CREATE SUBSCRIPTION testdbsub CONNECTION 'dbname=testdb host=172.16.11.16 user=repuser password=password' PUBLICATION testdbpub;

# 作成した Subscription を確認
testdb=# SELECT * FROM pg_subscription;
 subname   | subconninfo       | subslotname | subpublications (省略)
-----------+-------------------+-------------+------------------
 testdbsub | dbname=testdb ... | testdbsub   | {testdbpub}
(1 row)

3). Publisher 側でのレプリケーション状況の確認

Subscription 作成時に自動で Replication Slot が作成されていますので、Publisher 側で確認することができます。

Publisher サーバー

$ sudo su postgres
$ psql testdb

# 作成された Replication Slot を確認
testdb=# SELECT * FROM pg_replication_slots;
 slot_name |  plugin  | slot_type | database | active | confirmed_flush_lsn 
-----------+----------+-----------+----------+--------+---------------------
 testdbsub | pgoutput | logical   | testdb   | t      | 0/17253D8           
(1 row)

モニタリング

上記でも記載したように、PostgreSQL の論理レプリケーションでは まずトランザクションログが WAL に書かれ、その変更内容が Replication Slot に記録され、Subscription が Replication Slot から変更内容を取得します。

レプリケーションでどの程度の遅延が発生しているかは、以下の 3つの LSN (Log Sequence Number) を比較することでわかります。

(なお、LSN とは各トランザクションログに付与される連番です)

  1. Publisher サーバーの現在の WAL の LSN
  2. Replication Slot に記録されている、最後に Subscritpion に送信された WAL の LSN
  3. Subscription で最後に受信した WAL の LSN

Replication Slot で最後に送信された LSN が Publisher の現在の LSN より遅れている場合、WAL が Replication Slot に記録される部分で遅延が発生していると仮定できます。

また Subscription で最後に受信した LSN が、Replication Slot が最後に送信した LSN より遅れている場合、ネットワークや Subscription で実行される worker プロセスで遅延が発生していると仮定できます。

各 LSN の確認方法は以下の通りです。

Publisher サーバー

$ sudo su postgres
$ psql testdb

# Publisher サーバーの現在の WAL の LSN
testdb=# SELECT pg_current_wal_lsn();
 pg_current_wal_lsn 
--------------------
 0/1725908
(1 row)

# Replication Slot で最後に送信された LSN ("sent_lsn" を確認)
testdb=# SELECT * FROM pg_stat_replication;
 application_name | sent_lsn  | write_lsn | flush_lsn | replay_lsn (省略) 
------------------+-----------+-----------+-----------+------------
 testdbsub        | 0/1725908 | 0/1725908 | 0/1725908 | 0/1725908
(1 row)

Subscriber サーバー

$ sudo su postgres
$ psql testdb

# Subscription で最後に受信した LSN ("received_lsn" を確認)
testdb=# SELECT * FROM pg_stat_subscription;
  subname  | received_lsn | latest_end_lsn |     last_msg_receipt_time (省略)     
-----------+--------------+----------------+-------------------------------
 testdbsub | 0/1725908    | 0/1725908      | 2023-01-22 14:47:25.535615+09
(1 row)