<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ロジカルレプリケーション &#8211; SRA OSS Tech Blog</title>
	<atom:link href="https://www.sraoss.co.jp/tech-blog/tag/129dcc32098c25657fa866339fbfcee9/feed/index.xml" rel="self" type="application/rss+xml" />
	<link>https://www.sraoss.co.jp/tech-blog/</link>
	<description>OSS専門の技術ブログ。PostgreSQL、Zabbixを中心に10種類以上のOSSの解説、アップデート情報、運用ヒントを提供。</description>
	<lastBuildDate>Fri, 23 Oct 2026 06:57:28 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>

<image>
	<url>https://www.sraoss.co.jp/tech-blog/wp-content/uploads/2018/07/blog-icon-150x150.png</url>
	<title>ロジカルレプリケーション &#8211; SRA OSS Tech Blog</title>
	<link>https://www.sraoss.co.jp/tech-blog/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>ロジカルレプリケーションによるメジャーバージョンアップ手順</title>
		<link>https://www.sraoss.co.jp/tech-blog/pgsql/version_up_with_logical_replication/</link>
		
		<dc:creator><![CDATA[越野 太基 (Taiki Koshino)]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 02:17:26 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[アップグレード]]></category>
		<category><![CDATA[ロジカルレプリケーション]]></category>
		<guid isPermaLink="false">https://www.sraoss.co.jp/tech-blog/?p=20608</guid>

					<description><![CDATA[PostgreSQL のメジャーバージョンアップにはいくつかの手法がありますが、本記事ではその中でもロジカルレプリケーションを利用した方法を紹介します。ロジカルレプリケーションを用いることで、新旧バージョンのデータベース ...]]></description>
										<content:encoded><![CDATA[<p data-start="42" data-end="215">PostgreSQL のメジャーバージョンアップにはいくつかの手法がありますが、本記事ではその中でもロジカルレプリケーションを利用した方法を紹介します。ロジカルレプリケーションを用いることで、新旧バージョンのデータベースを並行稼働させながらデータを同期できるため、切り替え時のダウンタイムを最小限に抑えたアップグレードが可能です。その具体的な手順とあわせて、実施時に注意すべきポイントについて解説します。</p>
<p><span id="more-20608"></span></p>
<p>なお、PostgreSQLのロジカルレプリケーション自体についての基本的な解説は、以下の記事を参考にしてください。</p>
<ul>
<li><a href="https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-1/">ロジカルレプリケーションの紹介 (1)</a></li>
<li><a href="https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-2/">ロジカルレプリケーションの紹介 (2)</a></li>
<li><a href="https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-3/">ロジカルレプリケーションの紹介 (3)</a></li>
</ul>
<h2>ロジカルレプリケーションによるアップグレードの概要</h2>
<p>ロジカルレプリケーションを利用することで、異なるメジャーバージョン間でデータを同期しながら新しい環境を構築することが可能です。新旧データベースを並行稼働させ、最終的に接続先を切り替えることで、数秒程度のダウンタイムで移行を実現できます。</p>
<p><img fetchpriority="high" decoding="async" class="alignnone wp-image-20681" src="https://www.sraoss.co.jp/tech-blog/wp-content/uploads/2026/04/c8c5fe3773c42d0664e040faf8c8014d-221x300.png" alt="" width="581" height="789" srcset="https://www.sraoss.co.jp/tech-blog/wp-content/uploads/2026/04/c8c5fe3773c42d0664e040faf8c8014d-221x300.png 221w, https://www.sraoss.co.jp/tech-blog/wp-content/uploads/2026/04/c8c5fe3773c42d0664e040faf8c8014d-157x214.png 157w, https://www.sraoss.co.jp/tech-blog/wp-content/uploads/2026/04/c8c5fe3773c42d0664e040faf8c8014d-191x260.png 191w, https://www.sraoss.co.jp/tech-blog/wp-content/uploads/2026/04/c8c5fe3773c42d0664e040faf8c8014d.png 609w" sizes="(max-width: 581px) 100vw, 581px" /></p>
<p>上記イメージはロジカルレプリケーションを用いたバージョンアップのアーキテクチャイメージです。</p>
<p>対応バージョンとしては、Publisher（旧バージョンのデータベース）は PostgreSQL 10 以上である必要があります。例えば PostgreSQL 13 から 16 へのアップグレードは可能ですが、9.6 からの移行には対応していません。</p>
<p>全体の流れは以下のとおりです。これらの手順について詳しく解説します。<br />
ロジカルレプリケーションはデータベース単位で構成されるため、各データベースごとに個別に設定・同期を行います。</p>
<ol>
<li>旧バージョンのデータベースを稼働させたまま、新バージョンのデータベースクラスタを initdb で構築し起動する</li>
</ol>
<p>以下の手順を、対象となるすべてのデータベースに対して実施します。</p>
<ol start="2">
<li>旧バージョンの各データベースで全テーブルを対象としたパブリケーションを作成</li>
<li>新バージョン側に移行するデータベースを作成</li>
<li>旧バージョンの各データベースから pg_dump -s でスキーマ定義の SQL を取得して新バージョンの各データベースにリストア</li>
<li>新バージョンの各データベースでサブスクリプションを作成</li>
</ol>
<p>すべてのデータベースでサブスクリプション作成および同期が完了した後、<br />
以下の作業を実施します。</p>
<ol start="6">
<li>旧バージョンのデータベースクラスタへの更新処理を停止する</li>
<li>各データベースで pg_stat_subscription を確認し、LSN の一致を確認する</li>
<li>同期完了後、シーケンス位置やラージオブジェクトなどロジカルレプリケーションで同期されないデータを各データベースで手動同期する</li>
<li>アプリケーションの接続先を切り替える</li>
</ol>
<h2>ロジカルレプリケーションによるアップグレードの注意点</h2>
<p class="isSelectedEnd">ロジカルレプリケーションでは、テーブルの行データに対する変更がサブスクライバへ同期されます。一方で、行データ以外の更新情報は同期されません。</p>
<p>そのため、以下のようなオブジェクトは別途作成または移行する必要があります。</p>
<ul>
<li>テーブルやスキーマの定義</li>
<li>ビューおよびマテリアライズドビューの定義</li>
<li>パーティションテーブルや外部テーブルの定義</li>
<li>シーケンスの現在値</li>
<li>ラージオブジェクト</li>
<li>拡張機能</li>
<li>UNLOGGED TABLE</li>
<li>ロール</li>
</ul>
<p>特にスキーマ、テーブル定義についてはロジカルレプリケーションでは同期されず、サブスクライバ側で定義されていない状態でロジカルレプリケーションを行うと伝播先のテーブルがないとしてレプリケーションに失敗します。よってスキーマ、テーブル定義については pg_dump を用いて定義の SQL を取得して新バージョンのデータベースで実行するなどして事前にリストアしておく必要があります。今回紹介する手順では pg_dump に -s  オプションを付与して実行し、各 DDL を取得してロジカルレプリケーション前に新バージョンのデータベースで実行するようにします。</p>
<p>またその他にも拡張機能やロールなどもロジカルレプリケーションでレプリケーションされないため必要に応じて別途リストアする必要があります。</p>
<h2>アップグレード手順</h2>
<h3>旧バージョンデータベースのロジカルレプリケーションのセットアップ</h3>
<p>今回使用する環境は以下の通りで、PostgreSQL 14 から PostgreSQL 18 に移行します。</p>
<table style="height: 155px;" width="724">
<tbody>
<tr>
<td width="283">領域</td>
<td width="283">パス</td>
</tr>
<tr>
<td width="283">旧バージョンの データベースクラスタ</td>
<td width="283">/var/lib/pgsql/14</td>
</tr>
<tr>
<td width="283">新バージョンの データベースクラスタ</td>
<td width="283">/var/lib/pgsql/18</td>
</tr>
<tr>
<td width="283">旧バージョンのPostgreSQLコマンドパス</td>
<td width="283">/usr/local/pgsql14/bin</td>
</tr>
<tr>
<td width="283">新バージョンのPostgreSQLコマンドパス</td>
<td width="283">/usr/local/pgsql18/bin</td>
</tr>
</tbody>
</table>
<p>まず最初に旧バージョンのデータベースを稼働させたまま新データベースクラスタを構築し、旧バージョンでロジカルレプリケーションの有効化とパブリケーションを作成を行います。</p>
<pre>#新しいバージョンのデータベースクラスタを作成して起動
$ /usr/local/pgsql18/bin/initdb -D /var/lib/pgsql/18
[postgres@server1 ~]$ /usr/local/pgsql18/bin/pg_ctl -D /var/lib/pgsql/18 -o "-p 5433" start</pre>
<p>ロジカルレプリケーションではパブリッシャの wal_level は logical である必要があります。ロジカルレプリケーションとは別にストリーミングレプリケーションを利用している場合は既に使用しているレプリケーションスロット数に今回のロジカルレプリケーション用に必要なスロット数を加えた値を max_replication_slots に設定してください。複数のデータベースを新バージョンへ移行する場合は、旧バージョン側でデータベースの数分レプリケーションスロットを作成しますのでその分のスロット数も max_replication_slots に乗じてください。</p>
<p>またパブリッシャから WAL 情報を送信する walsender プロセスの上限を決定する max_wal_senders もレプリケーション接続される数を超えるように設定してください。設定後にサーバーを再起動して設定を反映します。</p>
<pre># ロジカルレプリケーションを利用できるよう旧バージョンの postgresql.conf を編集して再起動
[postgres@server1 ~]$ vi /var/lib/pgsql/14/postgresql.conf
wal_level = logical
#max_replication_slots = 10
#max_wal_senders = 10
[postgres@server1 ~]$ /usr/local/pgsql14/bin/pg_ctl -D /var/lib/pgsql/14 -o "-p 5432" restart</pre>
<p>再起動後、今回のレプリケーションの動作確認用に、旧バージョンのデータベースに users テーブルを作成します。</p>
<pre>[postgres@server1 ~]$ /usr/local/pgsql14/bin/psql -p 5432
psql (14.22)
Type "help" for help.

postgres=# CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT
);
CREATE TABLE
postgres=# INSERT INTO users(name) VALUES ('taro');
INSERT 0 1
postgres=# INSERT INTO users(name) VALUES ('jiro');
INSERT 0 1
postgres=# \d
List of relations
Schema | Name         | Type     | Owner
-------+--------------+----------+----------
public |    users     |  table   | postgres
public | users_id_seq | sequence | postgres
(2 rows)

postgres=# SELECT * FROM users;
id | name
---+------
1  | taro
2  | jiro
(2 rows)</pre>
<p>その後パブリケーションを作成します。パブリケーションの対象を FOR ALL TABLES とし、すべてのテーブルを同期対象にするようにします。複数データベースを移行する場合はデータベースごとにパブリケーションを作成してください。</p>
<pre>postgres=# CREATE PUBLICATION pub_all FOR ALL TABLES;

# 他データベースも必要であれば同様に作成する
db2=# CREATE PUBLICATION pub_all_2 FOR ALL TABLES;</pre>
<h3 data-section-id="126z3z" data-start="74" data-end="105">旧バージョンデータベースの pg_hba.conf  設定</h3>
<p data-start="107" data-end="165">旧バージョン側  pg_hba.conf には、新バージョンデータベースからのレプリケーション接続を許可する設定を追加します。論理レプリケーションでは、サブスクライバからパブリッシャへ接続してデータ取得を行うため、replication 権限を持つ接続許可が必要です。</p>
<pre data-start="107" data-end="165"># サブスクライバからのレプリケーション接続を許可
host replication postgres 0.0.0.0/0 trust</pre>
<h3>新バージョンに移行するデータベースとスキーマを移行</h3>
<p>ロジカルレプリケーションではテーブルデータのみが同期対象となり、ロール、データベース、スキーマ定義などは自動的には同期されません。そのため、ロジカルレプリケーションを開始する前にこれらを新バージョン側へ移行しておく必要があります。</p>
<p>まずはロールやテーブルスペースなどのグローバルオブジェクトを移行します。</p>
<pre># グローバルオブジェクトをダンプ・リストア
[postgres@server1 ~]$ /usr/local/pgsql18/bin/pg_dumpall -g | /usr/local/pgsql18/bin/psql -p 5433</pre>
<p>次に新バージョンの各データベースに pg_dump -s -C でスキーマ定義を旧バージョンのデータベースからリストアしたのちにサブスクリプションを作成してテーブルデータを同期します。注意点でも述べた通りロジカルレプリケーションではスキーマやテーブル定義といった DDL までは同期されないので別途に pg_dump によって手動でリストアしておく必要があります。</p>
<p>pg_dump, psql は -d で対象データベースを指定します。データベースを複数移行する場合はデータベースごとに pg_dump, psql を実施し、-d で指定するデータベースを変更してください。</p>
<p>pg_dump の結果を psql で実行した後に \d でリレーションを確認すると旧バージョンで作成した users テーブルやシーケンスがコピーされていることが分かります。</p>
<pre>[postgres@server1 ~]$ /usr/local/pgsql18/bin/pg_dump -p 5432 -s -C -d postgres |  /usr/local/pgsql18/bin/psql -p 5433
[postgres@server1 ~]$ /usr/local/pgsql18/bin/psql -p 5433
psql (18.3)
Type "help" for help.
postgres=# \d
List of relations
Schema | Name         | Type     | Owner
-------+--------------+----------+----------
public | users        |  table   | postgres
public | users_id_seq | sequence | postgres
(2 rows)</pre>
<h3>新バージョンの各データベースでのサブスクリプションのセットアップ</h3>
<p>次に旧バージョンの各データベースで作成したパブリケーションに対してサブスクリプションを作成します。サブスクリプションは移行するデータベースごとに作成し、CONNECTION の引数内の dbname や PUBLICATION でしているパブリケーション名は適宜変更してください。</p>
<p>その後テーブルの中身を SELECT するとテーブルデータが同期されていることが分かります。</p>
<pre>postgres=# CREATE SUBSCRIPTION sub_all
CONNECTION 'host=localhost port=5432 dbname=postgres'
PUBLICATION pub_all;
NOTICE: created replication slot "sub_all" on publisher
CREATE SUBSCRIPTION
postgres=# SELECT * FROM users;
id | name
---+------
1  | taro
2  | jiro
(2 rows)</pre>
<h3>旧バージョンの各データベースへの更新を停止して同期の完了を待ち、シーケンス位置を揃える</h3>
<p>サブスクリプション作成後、旧バージョンの各データベースへの更新を一時停止します。これによって旧バージョン側の LSN を止め、新バージョン側の LSN が追い付ける状態にします。</p>
<p>更新処理停止後、新バージョン各データベースごとに pg_stat_subscription 内を確認して received_lsn と latest_end_lsn が一致していることが確認できれば同期は完了です。received_lsn はパブリッシャから受け取った WAL 位置を、latest_end_lsn はパブリッシャから受け取った WAL をサブスクライバで適用し、その完了をパブリッシャに報告した位置を示します。</p>
<pre>postgres=# SELECT * FROM pg_stat_subscription;
-[ RECORD 1 ]---------+------------------------------
subid                 | 16395
subname               | sub_all
worker_type           | apply
pid                   | 3457
leader_pid            |
relid                 |
received_lsn          | 0/1718270
last_msg_send_time    | 2026-04-27 15:33:53.961503+09
last_msg_receipt_time | 2026-04-27 15:33:53.961586+09
latest_end_lsn        | 0/1718270
latest_end_time       | 2026-04-27 15:33:53.961503+09</pre>
<p>その後シーケンス位置を手動で同期します。ロジカルレプリケーションではシーケンス位置はレプリケーションされず、また pg_dump -s でもシーケンス位置を設定する SQL は出力されません。そのためシーケンスの位置は手動で揃える必要があります。</p>
<p>新バージョン側でシーケンスを調整する前の時点ではシーケンスの開始値が初期値である 1 のままとなっています。そのため、新バージョンのデータベース側で INSERT を実行すると、すでにテーブルに存在する id=1 の行と重複し、ユニーク制約違反が発生します。</p>
<pre>postgres=# SELECT currval('users_id_seq');
ERROR: currval of sequence "users_id_seq" is not yet defined in this session
postgres=# INSERT INTO users(name) VALUES ('saburo');
ERROR: duplicate key value violates unique constraint "users_pkey"
DETAIL: Key (id)=(1) already exists.</pre>
<p data-start="140" data-end="199">シーケンス位置は pg_dump の出力に含まれる setval の SQL を新バージョン側の各データベースで実行して反映します。-d オプションの引数もデータベースごとに適宜変更してください。</p>
<pre>[postgres@server1 ~]$ pg_dump -p 5432 -d postgres &gt; dump.sql
[postgres@server1 ~]$ cat dump.sql
(省略)

--
-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
--

SELECT pg_catalog.setval('public.users_id_seq', 2, true);

[postgres@server1 ~]$ /usr/local/pgsql18/bin/psql -p 5433
psql (18.3)
Type "help" for help.
postgres=# SELECT pg_catalog.setval('public.users_id_seq', 2, true);</pre>
<p>その他ラージオブジェクトや拡張機能など、WAL 書き出されずロジカルレプリケーションの対象外となっているものについても手動で同期を行います。これによって旧バージョンと新バージョンの同期は完了です。</p>
<h3>同期完了後アプリケーションの接続先を新バージョンのデータベースに切り替える</h3>
<p>すべてのデータベースの同期完了後にアプリケーションの接続先を切り替えます。</p>
<p>新バージョンの各データベースのサブスクリプションを無効化、マテリアライズドビューをすべて REFRESH したのちに、新バージョン側のpg_hba.conf にアプリケーションからの接続の認証情報を記述し、アプリケーションの接続先を切り替えればアップグレードは完了です。</p>
<pre># サブスクリプションを無効化
postgres=# ALTER SUBSCRIPTION sub_all DISABLE;

# マテリアライズドビューがあれば REFRESH
postgres=# REFRESH MATERIALIZED VIEW my_mv;

# pg_hba.conf にアプリケーションからの接続の認証情報を記載(環境に合わせて適宜記載)
host all application 0.0.0.0/0 trust</pre>
<h2>まとめ</h2>
<p>ロジカルレプリケーションを用いた PostgreSQL のメジャーアップグレードは、適切に設計・運用することで、サービス停止時間を最小限に抑えた安全な移行を実現できます。ただし、データ以外の要素は自動的には同期されないため、スキーマ、シーケンス位置などを含めた包括的な準備が不可欠です。</p>
<p>事前検証と十分な計画を行うことで、本番環境においても安定したアップグレードを実施できます。</p>
<div>
<a href="https://www.sraoss.co.jp/prod_serv/support/pgsql-mainte/?utm_source=sraoss-techblog&utm_medium=referral&utm_campaign=under-article-pgsql" rel="noopener" style="color:initial;" target="_blank" class="no-ext"></p>
<div style="background-color:#DAEFF6; padding:20px; border-radius:8px; text-align:center;">
SRA OSSでは、PostgreSQL/Pgpool-II のサポートを提供しております。<br />
詳しくは<strong style="color:#2581c4;">弊社ホームページ</strong>をご覧ください。
</div>
<p></a>
</div>

<div>
<div style="background-color: #fffacd; padding: 20px; border-radius: 8px; text-align: center;">2026/10/27（火）「PostgreSQL 19 リリース記念セミナー」開催！<br />
コントリビューターが新機能をわかりやすく解説します。<br />
詳しくは<a href="https://www.sraoss.co.jp/event_seminar/detail/20261027-postgresql19-webinar/?utm_source=techblog&amp;utm_medium=email&amp;utm_campaign=pg19-seminar" target="_blank" rel="noopener"><strong style="color: #2581c4;">こちら</strong></a>をご覧ください。</div>
</div>

]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ロジカルレプリケーションの紹介 (3)</title>
		<link>https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-3/</link>
		
		<dc:creator><![CDATA[データベース技術グループ]]></dc:creator>
		<pubDate>Thu, 09 Jun 2022 15:01:37 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[ロジカルレプリケーション]]></category>
		<guid isPermaLink="false">https://www.sraoss.co.jp/tech-blog/?p=10116</guid>

					<description><![CDATA[ロジカルレプリケーション紹介の 3 回目となります。 1 回目では、ロジカルレプリケーションの概要や基本的な使い方を紹介しました。 2 回目では、ロジカルレプリケーションの構成や監視方法などを紹介しました。 今回はロジカ ...]]></description>
										<content:encoded><![CDATA[<p>ロジカルレプリケーション紹介の 3 回目となります。<br />
<a href="https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-1/">1 回目</a>では、ロジカルレプリケーションの概要や基本的な使い方を紹介しました。<br />
<a href="https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-2/">2 回目</a>では、ロジカルレプリケーションの構成や監視方法などを紹介しました。<br />
今回はロジカルレプリケーションで発生するコンフリクトやエラーについて紹介します。</p>
<p><span id="more-10116"></span></p>
<h3>検証環境</h3>
<p>対象とする PostgreSQL のバージョンは12、サーバやユーザの名前などについては、1 回目と同様に下記の通りです。</p>
<ul>
<li>パブリッシャとなる PostgreSQL のサーバ名を first_server、データベース名を first</li>
<li>サブスクライバとなる PostgreSQL のデータベース名を second</li>
<li>ロジカルレプリケーション用のユーザ名を logi_user</li>
</ul>
<p>そのため、</p>
<ul>
<li>first=# で始まる SQL はパブリッシャで実行するコマンド</li>
<li>second=# で始まる SQL はサブスクライバで実行するコマンド</li>
</ul>
<p>となります。</p>
<h2>コンフリクトの解消</h2>
<p>まずは、ロジカルレプリケーションで発生するコンフリクトについて紹介します。</p>
<p>パブリッシャからサブスクライバにレプリケーションされるデータ操作が、サブスクライバ上でデータ一意制約エラーなどによって、正しくレプリケーションされない場合があります。<br />
この事象がコンフリクトと呼ばれます。</p>
<p>具体的にどのような挙動となるのかを以下で確認していきます。</p>
<p>パブリッシャの first_to_second テーブルにはデータが 1 件登録されています。<br />
主キーは id です。</p>
<pre>first=# SELECT * FROM first_to_second;
 id |    val
----+------------
  1 | from_first
(1 行)</pre>
<p>サブスクライバの first_to_second テーブルにはデータが 2 件登録されています。</p>
<pre>second=# SELECT * FROM first_to_second;
 id |       val
----+------------------
  1 | from_first
  2 | insert_by_second
(2 行)</pre>
<p>この状態で、パブリッシャにデータを 1 件追加します。</p>
<pre>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 行)</pre>
<p>追加されました。<br />
サブスクライバでどうなったかを確認します。</p>
<pre>second=# SELECT * FROM first_to_second;
 id |       val
----+------------------
  1 | from_first
  2 | insert_by_second
(2 行)</pre>
<p>サブスクライバでは、すでに id = 2 のレコードが存在していたため、コンフリクトが発生し、データが追加されませんでした。<br />
サブスクライバ側のログを確認すると、以下のようなログが出力されます。</p>
<pre>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.</pre>
<p>コンフリクトを解消する方法は 2 通りあります。</p>
<ol>
<li>pg_replication_origin_advance 関数を使って、コンフリクトしているトランザクションをスキップする。</li>
<li>コンフリクトしないようにタプルデータを修正する。</li>
</ol>
<p>1 の方法では、pg_replication_origin_advance 関数の引数に、レプリケーションの開始点としたい LSN を指定します。<br />
誤った LSN を指定してしまいますと、レプリケーションさせたいトランザクションまでスキップされる可能性がありますので、LSN の指定は注意深く行ってください。</p>
<p>今回は 2 の方法で解消します。</p>
<p>サブスクライバで id = 2 のデータを削除します。</p>
<pre>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 行)</pre>
<p>無事にコンフリクトが解消され、パブリッシャで行ったデータ追加が反映されました。</p>
<p>以上が、ロジカルレプリケーションでのコンフリクトと、その解消方法です。</p>
<h2>パブリッシャまたはサブスクライバでスキーマ変更を実施したことによるエラー</h2>
<p>次に、テーブルのスキーマを変更したことにより発生するエラーを紹介します。</p>
<p>最初にロジカルレプリケーション用の logi_schema スキーマを作成し、テーブルをそのスキーマに移動します。</p>
<pre>first=# CREATE SCHEMA logi_schema AUTHORIZATION logi_user;
CREATE SCHEMA

first=# ALTER TABLE first_to_second SET SCHEMA logi_schema;
ALTER TABLE</pre>
<p>テーブルのスキーマ移動に伴い、パブリケーションに追加するテーブルにもスキーマを指定します。</p>
<pre>first=# ALTER PUBLICATION pb_first_to_second SET TABLE logi_schema.first_to_second;
ALTER PUBLICATION</pre>
<p>スキーマを変更した first_to_second テーブルに id = 2 のデータを追加して挙動を確認します。</p>
<pre>first=# INSERT INTO logi_schema.first_to_second VALUES (2, 'from_logi_schema_first');
INSERT 0 1</pre>
<p>サブスクライバ側ではデータが追加されませんでした。</p>
<pre>second=# SELECT * FROM first_to_second;
 id |    val
----+------------
  1 | from_first
(1 行)</pre>
<p>これは、サブスクライバ側で出力される下記ログのとおり、サブスクライバ側で first_to_second が logi_schema スキーマに存在していないことが原因です。</p>
<pre>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</pre>
<p>サブスクライバ側でも、logi_schema スキーマを作成し、first_to_second テーブルを移動させます。</p>
<pre>second=# CREATE SCHEMA logi_schema AUTHORIZATION logi_user;
CREATE SCHEMA

second=# ALTER TABLE first_to_second SET SCHEMA logi_schema;
ALTER TABLE</pre>
<p>改めて、サブスクライバでデータを確認します。</p>
<pre>second=# SELECT * FROM logi_schema.first_to_second;
 id |          val
----+------------------------
  1 | from_first
  2 | from_logi_schema_first
(2 行)</pre>
<p>エラーが解消されたことで、パブリッシャのデータ追加がサブスクライバに反映されました。</p>
<h2>サブスクリプションの作成や削除時に発生するエラー</h2>
<p>サブスクリプションを作成または削除する際にも、いくつかのエラーが発生する可能性がありますので、紹介いたします。</p>
<h3>レプリケーションスロットの重複エラー</h3>
<p>サブスクリプションの作成時に、パブリッシャにレプリケーションスロットを作成されます。<br />
作成しようとするレプリケーションスロット名と同名のレプリケーションスロットが、すでにパブリッシャに存在する場合、下記のようなエラーが発生します。</p>
<pre>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</pre>
<p>このエラーが発生した場合には、レプリケーションスロット名が重複しないように、サブスクリプション作成時にレプリケーションスロット名を指定する必要があります。<br />
レプリケーションスロット名の指定については、第 1 回目の記事を参考にしてください。</p>
<h3>レプリケーションスロットの削除エラー</h3>
<p>サブスクライバがパブリッシャに接続できていない状態で、サブスクリプションを削除しようとすると、下記のようなエラーが発生します。</p>
<pre>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.</pre>
<p>これはパブリッシャに作成したレプリケーションスロットを削除できないために、サブスクリプションの削除がエラーとなりました。<br />
この場合には、</p>
<ul>
<li>サブスクライバとパブリッシャが正しく接続された状態にする</li>
<li>（ログメッセージ内の「HINT」にもあるように）サブスクリプションのレプリケーションスロットを NONE に変更する</li>
</ul>
<p>上記いずれかの対応を行うことで、サブスクリプションを削除できるようになります。</p>
<h3>レプリケーションスロットがパブリッシャに存在しないエラー</h3>
<p>パブリッシャにレプリケーションスロットが存在しない状態で、サブスクリプションを削除しようとした場合には、下記のようなエラーが出力されます。</p>
<pre>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</pre>
<p>この場合にも、サブスクリプションのレプリケーションスロットを NONE に変更する必要があります。</p>
<h4>サブスクリプションのレプリケーションスロットを NONE に変更する</h4>
<p>サブスクリプションのレプリケーションスロットを変更する際に、サブスクリプションが、有効（ENABLE）状態ですと、下記のようなエラーが発生します。</p>
<pre>second=# ALTER SUBSCRIPTION logical_sub SET (slot_name = NONE);
ERROR:  cannot set slot_name = NONE for enabled subscription</pre>
<p>サブスクリプションを、無効（DISABLED）状態にすることで、レプリケーションスロットを変更できるようになります。</p>
<pre>second=# ALTER SUBSCRIPTION logical_sub DISABLE;
ALTER SUBSCRIPTION

second=# ALTER SUBSCRIPTION logical_sub SET (slot_name = NONE);
ALTER SUBSCRIPTION</pre>
<p>変更後にもロジカルレプリケーションを続ける場合には、忘れずにサブスクリプションを有効（ENABLE）状態に戻してください。</p>
<h2>ロジカルレプリケーションが行われない場合に出力されるエラーについて</h2>
<p>ここからは、ロジカルレプリケーションが正しく機能していない場合に出力されるエラーメッセージと、その対応方法について紹介します。</p>
<h3>エラーメッセージ「could not receive data from WAL stream」について</h3>
<p>レプリケーションの遅延や停滞が発生に伴い、サブスクライバ側に「could not receive data from WAL stream」というログメッセージが出力される場合があります。<br />
これはサブスクライバで行っている WAL ストリーム受信でのネットワーク接続が切断されたことを表すメッセージです。<br />
サブスクライバの OS レベルで正しくネットワーク接続できていない場合に発生します。<br />
OS ログにネットワークに関するエラーメッセージが出力されていないか、ネットワーク環境に障害が発生していないかなどを確認してください。</p>
<h3>エラーメッセージ「could not connect to the publisher」について</h3>
<p>レプリケーションの遅延や停滞が発生に伴い、サブスクライバ側に「could not connect to the publisher」というログメッセージが出力される場合があります。<br />
これはサブスクライバからパブリッシャに接続できないことを表すメッセージです。<br />
パブリッシャの PostgreSQL が停止していないかを確認し、停止していないのであれば正しく接続できるかどうかを確認してください。</p>
<h3>警告メッセージ「out of logical replication worker slots」について</h3>
<p>ロジカルレプリケーションを開始した際に、サブスクライバに「out of logical replication worker slots」というログメッセージが出力される場合があります。<br />
これは、ロジカルレプリケーションを行うためのワーカーの数が、設定値 max_logical_replication_workers を超えていることを表すメッセージです。<br />
1 つのサブスクリプションで 2 つのワーカーが作成されるため、max_logical_replication_workers の設定を「サブスクリプションの数 x 2」以上にする必要があります。</p>
<h3>エラーメッセージ「permission denied for table 《テーブル名》」について</h3>
<p>ロジカルレプリケーションを開始した際に「permission denied for table 《テーブル名》」というログメッセージが出力される場合があります。<br />
これは、サブスクリプションの作成時に指定したユーザが、該当テーブルに対してデータ操作を行うための権限が足りないことを表しています。<br />
この場合には、データ操作を行うための権限を付与することで、正常にデータ操作がレプリケーションされるようになります。</p>
<h2>おわりに</h2>
<p>今回はロジカルレプリケーションで起こりうるコンフリクトや、エラーなどについて紹介いたしました。<br />
まだまだロジカルレプリケーションの詳細な部分については紹介しきれていませんが、3 回に渡ってロジカルレプリケーションの概要や基本的な運用方法などを紹介いたしました。<br />
本紹介記事の内容が PostgreSQL でロジカルレプリケーションを運用する際の手助けになれば幸いです。</p>
<div>
<a href="https://www.sraoss.co.jp/prod_serv/support/pgsql-mainte/?utm_source=sraoss-techblog&utm_medium=referral&utm_campaign=under-article-pgsql" rel="noopener" style="color:initial;" target="_blank" class="no-ext"></p>
<div style="background-color:#DAEFF6; padding:20px; border-radius:8px; text-align:center;">
SRA OSSでは、PostgreSQL/Pgpool-II のサポートを提供しております。<br />
詳しくは<strong style="color:#2581c4;">弊社ホームページ</strong>をご覧ください。
</div>
<p></a>
</div>

]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ロジカルレプリケーションの紹介 (2)</title>
		<link>https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-2/</link>
		
		<dc:creator><![CDATA[データベース技術グループ]]></dc:creator>
		<pubDate>Thu, 09 Jun 2022 15:00:10 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[ロジカルレプリケーション]]></category>
		<guid isPermaLink="false">https://www.sraoss.co.jp/tech-blog/?p=10107</guid>

					<description><![CDATA[ロジカルレプリケーション紹介の 2 回目となります。1 回目では、ロジカルレプリケーションの概要や基本的な使い方を紹介しました。今回はロジカルレプリケーションでトリガを利用する方法や、ロジカルレプリケーションの構成、運用 ...]]></description>
										<content:encoded><![CDATA[<p>ロジカルレプリケーション紹介の 2 回目となります。<a href="https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-1/">1 回目</a>では、ロジカルレプリケーションの概要や基本的な使い方を紹介しました。今回はロジカルレプリケーションでトリガを利用する方法や、ロジカルレプリケーションの構成、運用時の注意点などについて紹介します。</p>
<p><span id="more-10107"></span></p>
<h3>検証環境</h3>
<p>対象とする PostgreSQL のバージョンは12、サーバやユーザの名前などについては、1 回目と同様に下記の通りです。</p>
<ul>
<li>パブリッシャとなる PostgreSQL のサーバ名を first_server、データベース名を first</li>
<li>サブスクライバとなる PostgreSQL のデータベース名を second</li>
<li>ロジカルレプリケーション用のユーザ名を logi_user</li>
</ul>
<p>そのため、</p>
<ul>
<li>first=# で始まる SQL はパブリッシャで実行するコマンド</li>
<li>second=# で始まる SQL はサブスクライバで実行するコマンド</li>
</ul>
<p>となります。</p>
<h2>サブスクライバでトリガ起動</h2>
<p>パブリッシャよりデータ操作がレプリケーションされたことを契機にトリガを起動する方法について紹介します。</p>
<p>以下では</p>
<ul>
<li>トリガ関数によって情報を書き込まれるテーブル</li>
<li>データ変更トリガに登録するトリガ関数</li>
<li>サブスクライバがデータを受け取ったときに起動するデータ変更トリガ</li>
</ul>
<p>の 3 つを作成して、サブスクライバがデータを受け取った際に、トリガ起動する挙動を確認します。</p>
<h3>時刻を記録するテーブルの作成</h3>
<p>まずはトリガ発火時に、その時刻を記録するテーブルをサブスクライバに作成します。</p>
<pre>second=# CREATE TABLE logical_table_history (id integer PRIMARY KEY, ope_type char(1), ope_timestamp timestamp);
CREATE TABLE</pre>
<h3>トリガ関数の作成</h3>
<p>次に、トリガ発火時に実行する FUNCTION をサブスクライバに作成します。<br />
戻り値を TRIGGER 型にすることで、データ変更トリガの発火時に実行されるトリガ関数として作成できます。</p>
<pre>second=# CREATE OR REPLACE FUNCTION register_logical_table_history()
second=# RETURNS trigger AS $$
second=# BEGIN
second=#     IF NEW.id IS NULL THEN
second=#         -- DELETE 操作
second=#         UPDATE public.logical_table_history SET ope_type = substr(TG_OP, 1, 1), ope_timestamp = now() WHERE id = OLD.id;
second=#     ELSE
second=#         -- INSERT または UPDATE 操作
second=#         INSERT INTO public.logical_table_history VALUES (NEW.id, substr(TG_OP, 1, 1), now())
second=#         ON CONFLICT ON CONSTRAINT logical_table_history_pkey
second=#         DO UPDATE SET ope_type = substr(TG_OP, 1, 1), ope_timestamp = now();
second=#     END IF;
second=#     RETURN NULL;
second=# END;
second=# $$ LANGUAGE plpgsql;
CREATE FUNCTION
</pre>
<h3>データ変更トリガの作成</h3>
<p>続けて、サブスクライバに作成したトリガ関数を実行するためのデータ変更トリガを作成します。</p>
<p>ロジカルレプリケーションでは行レベル（FOR EACH ROW)のトリガのみサポートされていますので、契機として設定できる操作に TRUNCATE を含めることはできません。<br />
関数呼び出しを行うタイミングを指定する BEFORE / AFTER については、制約はなく、どちらもサポートされています。</p>
<p>今回作成するトリガは、AFTER を指定し、契機となる操作を INSERT、UPDATE、DELETE としています。</p>
<pre>second=# CREATE TRIGGER logical_table_trigger AFTER INSERT OR UPDATE OR DELETE ON logical_table FOR EACH ROW EXECUTE PROCEDURE register_logical_table_history();
CREATE TRIGGER</pre>
<p>ちなみに、TRUNCATE を含めると、下記のようなエラーが発生します。</p>
<pre>second=# CREATE TRIGGER logical_table_trigger AFTER TRUNCATE ON logical_table FOR EACH ROW EXECUTE PROCEDURE register_logical_table_history();
ERROR:  TRUNCATE FOR EACH ROW triggers are not supported</pre>
<h3>サブスクライバのテーブルに対して、ENABLE REPLICA TRIGGER の指定</h3>
<p>ロジカルレプリケーションがデータ操作を適用するセッションは session_replication_role が replica となります。<br />
そのため、ロジカルレプリケーションによるデータ操作時にトリガを発火させたい場合には、サブスクライバのテーブルに対して ENABLE REPLICA TRIGGER の指定を行う必要があります。</p>
<pre>second=# ALTER TABLE logical_table ENABLE REPLICA TRIGGER logical_table_trigger;
ALTER TABLE</pre>
<h3>パブリッシャでデータ挿入</h3>
<p>以上の対応を行うことで、サブスクライバがデータを受け取った際に、トリガを発火させて、トリガ関数を実行できるようになりました。</p>
<p>準備ができましたので、実際にパブリッシャ側でデータを挿入して動作を確認します。</p>
<pre>first=# INSERT INTO logical_table VALUES (1, 1, 'trigger_test');
INSERT 0 1</pre>
<p>サブスクライバ側でデータの状態を確認します。</p>
<pre>second=# SELECT * FROM logical_table;
 id | no |     data
----+----+--------------
  1 |  1 | trigger_test
(1 行)

second=# SELECT * FROM logical_table_history;
 id | ope_type |       ope_timestamp
----+----------+----------------------------
  1 | I        | 2022-03-31 15:13:57.637665
(1 行)</pre>
<p>データ操作を契機としたトリガ関数の実行により、logical_table_history テーブルにデータ操作の履歴が記録されたことを確認できました。</p>
<h2>カスケードレプリケーションの設定</h2>
<p>ロジカルレプリケーションは、サブスクライバから更に別のサブスクライバへレプリケーションさせることも可能です。</p>
<p>first → second → third という構成でロジカルレプリケーションを構成します。</p>
<h3>first データベースにパブリケーションを作成します</h3>
<p>すべてのテーブルを対象としたパブリケーションを作成します、</p>
<pre>first=# CREATE PUBLICATION pb_logical_first FOR ALL TABLES;
CREATE PUBLICATION</pre>
<h3>second データベースにパブリケーションとサブスクリプションを作成します</h3>
<p>second データベースでも同様にすべてのテーブルを対象としたパブリケーションを作成します。</p>
<pre>second=# CREATE PUBLICATION pb_logical_second FOR ALL TABLES;
CREATE PUBLICATION</pre>
<p>次に、first からデータ操作をサブスクライブするためのサブスクリプションを作成します。</p>
<pre>second=# CREATE SUBSCRIPTION sb_logical_second CONNECTION 'host=first_server port=5432 user=logi_user dbname=first' PUBLICATION pb_logical_first;
CREATE SUBSCRIPTION</pre>
<h3>third データベースにサブスクリプションを作成します</h3>
<p>second からデータ操作をサブスクライブするためのサブスクリプションを作成します。</p>
<pre>third=# CREATE SUBSCRIPTION sb_logical_third CONNECTION 'host=second_server port=5432 user=logi_user dbname=second' PUBLICATION pb_logical_second;
CREATE SUBSCRIPTION</pre>
<h3>first データベースのテーブルにデータを追加します</h3>
<p>パブリケーション、サブスクリプションをそれぞれ作成できましたので、first データベースの logical_table テーブルにデータを追加します。</p>
<pre>first=# INSERT INTO logical_table VALUES (1, 1, 'from_first');
INSERT 0 1

first=# SELECT * FROM logical_table;
 id | no |   data
----+----+-----------
  1 |  1 | from_first
(1 行)</pre>
<p>second データベースにデータ操作がレプリケーションされているかを確認します。</p>
<pre>second=# SELECT * FROM logical_table;
 id | no |   data
----+----+-----------
  1 |  1 | from_first
(1 行)</pre>
<p>first データベースでのデータ操作が、second データベースにレプリケーションされたことを確認できました。</p>
<p>次に、third データベースにデータがレプリケーションされているかを確認します。</p>
<pre>third=# SELECT * FROM logical_table;
 id | no |   data
----+----+-----------
  1 |  1 | from_first
(1 行)</pre>
<p>first で行ったデータ操作が、second を通じて third にレプリケーションされたことを確認できました。</p>
<h2>カスケードレプリケーション時の注意点</h2>
<p>どこかのデータベースが機能停止になった場合、そのデータベースからカスケードされているデータベース以降に、データ操作がレプリケーションされなくなります。</p>
<p>たとえば、今回の例でいいますと、second データベースが停止した場合、third データベースにもデータ操作がレプリケーションされなくなることにご注意ください。<br />
その場合には、third データベースに作成した second データベースからデータ操作を受け取るサブスクリプションを削除し、新しく first データベースからデータ操作を受け取るサブスクリプションを作成する、などの対応が必要となります。</p>
<h2>双方向レプリケーション</h2>
<p>今度は、双方向のレプリケーションについて確認します。<br />
first から second へのレプリケーションと、second から first へのレプリケーションを行う環境を構築します。</p>
<h3>first データベースの環境構築</h3>
<p>first データベースに、ロジカルレプリケーションを行うためのテーブル、パブリケーション、サブスクリプションをそれぞれ作成します。</p>
<pre>first=# CREATE TABLE first_to_second (id INTEGER PRIMARY KEY, val TEXT);
CREATE TABLE

first=# CREATE TABLE second_to_first (id INTEGER PRIMARY KEY, val TEXT);
CREATE TABLE

first=# CREATE PUBLICATION pb_first_to_second FOR TABLE first_to_second;
CREATE PUBLICATION

first=# CREATE SUBSCRIPTION sb_second_to_first CONNECTION 'host=second_server port=5432 user=logi_user dbname=second' PUBLICATION pb_second_to_first;
CREATE SUBSCRIPTION</pre>
<h3>second データベースの環境構築</h3>
<p>second データベースにも同様にテーブル、パブリケーション、サブスクリプションをそれぞれ作成します。</p>
<pre>second=# CREATE TABLE first_to_second (id INTEGER PRIMARY KEY, val TEXT);
CREATE TABLE

second=# CREATE TABLE second_to_first (id INTEGER PRIMARY KEY, val TEXT);
CREATE TABLE

second=# CREATE PUBLICATION pb_second_to_first FOR TABLE second_to_first;
CREATE PUBLICATION

second=# CREATE SUBSCRIPTION sb_first_to_second CONNECTION 'host=first_server port=5432 user=logi_user dbname=first' PUBLICATION pb_first_to_second;
CREATE SUBSCRIPTION</pre>
<h3>レプリケーションの確認</h3>
<p>first データベースおよび second データベースで、ロジカルレプリケーションを行う準備ができました。</p>
<p>first データベース側でデータを追加して挙動を確認します。</p>
<pre>first=# INSERT INTO first_to_second VALUES (1, 'from_first');
INSERT 0 1</pre>
<p>second データベースにレプリケーションされているかを確認します。</p>
<pre>second=# SELECT * FROM first_to_second;
 id |    val
----+------------
  1 | from_first
(1 行)</pre>
<p>無事にレプリケーションされました。<br />
今度は、second データベース側でデータを追加します。</p>
<pre>second=# INSERT INTO second_to_first VALUES (1, 'from_second');
INSERT 0 1</pre>
<p>first データベースにレプリケーションされているかを確認します。</p>
<pre>first=# SELECT * FROM second_to_first;
 id |     val
----+-------------
  1 | from_second
(1 行)</pre>
<p>無事にレプリケーションされました。</p>
<p>ストリーミングレプリケーションでは、一方向のレプリケーションしかできませんでしたが、ロジカルレプリケーションでは双方向のレプリケーションを実現できます。<br />
ただし、同じテーブルに対して（マルチマスタのような）双方向のレプリケーションは行えませんので、ご注意ください。</p>
<h2>ロジカルレプリケーションが正常に動作しているかの監視方法</h2>
<p>ロジカルレプリケーションが正常に動作しているかを確認する方法をご紹介します。</p>
<p>ロジカルレプリケーションが正常に動作しているかは、パブリッシャの「現在の WAL 書き込み位置」と、サブスクライバの「適用済み WAL の最終位置」を比較することで確認できます。</p>
<h3>パブリッシャの現在の WAL 書き込み位置の確認</h3>
<p>現在の WAL 書き込み位置は pg_current_wal_lsn 関数を実行することで確認できます。</p>
<pre>first=# SELECT pg_current_wal_lsn();
 pg_current_wal_lsn
--------------------
 6/CD052A10
(1 行)</pre>
<h3>サブスクライバの「適用済み WAL の最終位置」の確認</h3>
<p>サブスクライバの「適用済み WAL の最終位置」は</p>
<ul>
<li>パブリッシャの pg_stat_replication.replay_lsn</li>
<li>サブスクライバの pg_stat_wal_receiver.received_lsn</li>
</ul>
<p>のいずれかで確認できます。</p>
<p>今回はパブリッシャの pg_stat_replication.replay_lsn を確認します。</p>
<p>pg_stat_replication.replay_lsn が、サブスクライバに適用済み WAL の最終位置となります。</p>
<p>遅延が発生している場合には、replay_lag や flush_lag に遅延の時間が表示されます。replay_lag や flush_lag に値が表示されていない（NULL）なら、遅延は発生していないと判断できます。</p>
<p>pg_stat_replication にサブスクライバの情報が存在しない場合は、そもそもロジカルレプリケーションは正常に動作していません。</p>
<pre>first=# SELECT * FROM pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid              | 7400
usesysid         | 10
usename          | logi_user
application_name | sb_logical
client_addr      | 192.168.33.11
client_hostname  |
client_port      | 34358
backend_start    | 2022-03-31 16:27:44.879862+09
backend_xmin     |
state            | streaming
sent_lsn         | 6/CD052A10
write_lsn        | 6/CD052A10
flush_lsn        | 6/CD052A10
replay_lsn       | 6/CD052A10
write_lag        |
flush_lag        |
replay_lag       |
sync_priority    | 0
sync_state       | async
reply_time       | 2022-03-31 17:42:11.128553+09</pre>
<p>今回の実行では、pg_stat_replication.replay_lsn と pg_current_wal_lsn 関数の結果が同一でしたので、遅延なくロジカルレプリケーションが行われていることが分かります。</p>
<h2>ロジカルレプリケーションを運用する上での注意点</h2>
<p>最後に、ロジカルレプリケーションを運用する上での注意点を紹介いたします。</p>
<h3>pg_dump 時の注意点</h3>
<p>pg_dump を行うユーザがスーパーユーザ権限を持っていない場合、サブスクリプションは pg_dump の対象となりません。<br />
ロジカルレプリケーションが機能しているデータベースに対して pg_dump を行う場合、スーパーユーザ権限を持ったユーザで行う必要がありますので、ご注意ください。<br />
以下で、実際にどのような挙動となるのかを紹介します。</p>
<h4>非スーパーユーザで pg_dump 実行</h4>
<p>非スーパーユーザで pg_dump を実行してみます。</p>
<pre>$ pg_dump -d second -U logi_user &gt; pg_dump_by_logi_user.dmp
pg_dump: 警告: 現在のユーザがスーパユーザではないため、サブスクリプションはダンプされません</pre>
<p>非スーパーユーザでは、上記のような警告メッセージが表示され、出力されたダンプファイル内には、サブスクリプションが含まれません。</p>
<h4>スーパーユーザで pg_dump 実行</h4>
<p>今度はスーパーユーザで pg_dump を実行します。</p>
<pre>-bash-4.2$ pg_dump -d second -U postgres &gt; pg_dump_by_postgres.dmp
-bash-4.2$</pre>
<p>スーパーユーザで実行すると警告なく pg_dump が完了し、ダンプファイルにサブスクリプションが含まれます。</p>
<h3>パブリッシャにテーブルを追加した際の注意点</h3>
<p>パブリッシャとサブスクライバがロジカルレプリケーションを機能している状態で、パブリッシャに新たにテーブルを追加しても、そのテーブルに対して行ったデータ操作はレプリケーションされないことにご注意ください。</p>
<p>サブスクライバ側でサブスクリプションのリフレッシュを行うことで、データ操作がレプリケーションされるようになります。</p>
<p>具体的な挙動を確認します。</p>
<p>まずパブリッシャで、すべてのテーブルを対象としたパブリケーションを作成します。</p>
<pre>first=# CREATE PUBLICATION pb_all_table FOR ALL TABLES;
CREATE PUBLICATION</pre>
<p>サブスクライバで、そのパブリケーションをサブスクライブするサブスクリプションを作成します。</p>
<pre>second=# CREATE SUBSCRIPTION sb_all_table CONNECTION 'host=first_server port=5432 user=logi_user dbname=first' PUBLICATION pb_all_table;</pre>
<p>これでロジカルレプリケーションが機能する状態となりました。</p>
<p>この状態で、パブリッシャで新たにテーブルを作成してデータを追加します。<br />
ここでは add_table という名前のテーブルを作成しました。</p>
<pre>first=# CREATE TABLE add_table (id INTEGER PRIMARY KEY, val TEXT);
CREATE TABLE

first=# INSERT INTO add_table VALUES (1, 'add');
INSERT 0 1</pre>
<p>サブスクライバでも、同名のテーブルを作成し、データ操作がレプリケーションされるか確認します。</p>
<pre>second=# CREATE TABLE add_table (id INTEGER PRIMARY KEY, val TEXT);
CREATE TABLE

second=# SELECT * FROM add_table;
 id | val
----+-----
(0 行)</pre>
<p>予想どおり、新たに追加したテーブルに対しては、データ操作がレプリケーションされません。</p>
<p>この後、サブスクリプションをリフレッシュすることで、新たに追加したテーブルに対してもデータ操作がレプリケーションされるようになります。</p>
<pre>second=# ALTER SUBSCRIPTION sb_all_table REFRESH PUBLICATION;
ALTER SUBSCRIPTION

second=# SELECT * FROM add_table;
 id | val
----+-----
  1 | add
(1 行)</pre>
<p>無事に初期コピーが実行(※）され、データが同期されました。パブリッシャで行ったデータ操作も、レプリケーションされるようになっています。<br />
このリフレッシュ操作は、CREATE SUBSCRIPTION または、最後にリフレッシュした後に、パブリケーションに追加されたテーブルが対象となります。<br />
すでにレプリケーションが行われているテーブルに関しては、リフレッシュの対象とはならず、初期コピーは実行されません。</p>
<p>※オプションで、リフレッシュ時の初期コピーを行わないことも可能です。</p>
<h3>外部キーで依存関係のあるテーブルに対する TRUNCATE を実施する場合の注意点</h3>
<p>外部キーで依存関係があるテーブルに対して、TRUNCATE を実施した場合の挙動についても確認します。</p>
<p>パブリッシャのテーブルに対して TRUNCATE を実行した場合、サブスクライバにも TRUNCATE がレプリケーションされてデータが消去されます。<br />
サブスクライバ側のテーブルに外部キーで依存関係があり、同一のサブスクリプションにその依存関係のテーブルが含まれていない場合、TRUNCATE は失敗します。</p>
<p>上記の説明を理解するために、実際に category（カテゴリ）、products（特産品） という 2 つのテーブルを用意して、外部キーで依存関係のあるテーブルに対する TRUNCATE の挙動を確認します。</p>
<h4>パブリケーションの作成</h4>
<p>products テーブルの category_no にはcategory テーブルの category_no を参照先とした外部キー制約が付与されていますが、category テーブルのみを対象としたパブリケーションを作成します。</p>
<pre>first=# CREATE PUBLICATION pb_category FOR TABLE category;
CREATE PUBLICATION</pre>
<h4>サブスクリプションの作成</h4>
<p>サブスクライバで、category テーブルのみをサブスクライブするサブスクリプションを作成します。</p>
<pre>second=# CREATE SUBSCRIPTION sb_category CONNECTION 'host=first_server port=5432 user=logi_user dbname=first' PUBLICATION pb_category;
CREATE SUBSCRIPTION</pre>
<h4>TRUNCATE して挙動を確認</h4>
<p>この状態でパブリッシャで category テーブルに対して TRUNCATE を行って挙動を検証します。</p>
<p>※category.category_no は products.category_no の依存先となっていますので、category テーブルに対する TRUNCATE は CASCADE をつけて実行します。</p>
<pre>first=# TRUNCATE TABLE category CASCADE;
NOTICE: テーブル"products"へのカスケードを削除します
TRUNCATE TABLE</pre>
<p>TRUNCATE が実行されましたので、データの内容を確認します。</p>
<pre>first=# SELECT * FROM category;
 category_no | category_name
-------------+---------------
(0 行)

first=# SELECT * FROM products;
 products_no | products_name | category_no
-------------+---------------+-------------
(0 行)</pre>
<p>パブリッシャでは問題なく category テーブルと、products テーブルが TRUNCATE されたことを確認できました。</p>
<p>次に、サブスクライバのテーブルの内容を確認します。</p>
<pre>second=# SELECT * FROM category;
 category_no | category_name
-------------+---------------
           1 | 果物野菜
           2 | 魚介類海産物
           3 | 肉類乳製品
           4 | 郷土料理など
           5 | 工芸品民芸品
           6 | その他
(6 行)

second=# SELECT * FROM products;
 products_no | products_name | category_no
-------------+---------------+-------------
           1 | 夕張メロン    |           1
           2 | ジャガイモ    |           1
:
:
(128 行)</pre>
<p>上記のように、サブスクライバ側では category および products テーブルは TRUNCATE されずにデータが存在しています。</p>
<p>これは、products テーブルがサブスクリプションに含まれていないので、category テーブルに連動して products テーブルを TRUNCATE できないことから、category テーブルへの TRUNCATE 自体が行われなかったためです。<br />
この場合、products テーブルを category テーブルと同一のサブスクリプションに含めることで、パブリッシャで行った TRUNCATE がサブスクライバに正しくレプリケーションされるようになります。</p>
<h2>おわりに</h2>
<p>今回は、ロジカルレプリケーションの構成や注意点、ロジカルレプリケーションが機能しているかの確認方法を紹介いたしました。<br />
パブリッシャとサブスクライバ間で単純にテーブルをレプリケーションするだけでなく、（ストリーミングレプリケーションと比べて）柔軟に構成できるのがロジカルレプリケーションの特徴です。<br />
次回は、ロジカルレプリケーションで発生するコンフリクトやエラーについて紹介します。</p>
<div>
<a href="https://www.sraoss.co.jp/prod_serv/support/pgsql-mainte/?utm_source=sraoss-techblog&utm_medium=referral&utm_campaign=under-article-pgsql" rel="noopener" style="color:initial;" target="_blank" class="no-ext"></p>
<div style="background-color:#DAEFF6; padding:20px; border-radius:8px; text-align:center;">
SRA OSSでは、PostgreSQL/Pgpool-II のサポートを提供しております。<br />
詳しくは<strong style="color:#2581c4;">弊社ホームページ</strong>をご覧ください。
</div>
<p></a>
</div>

]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>ロジカルレプリケーションの紹介 (1)</title>
		<link>https://www.sraoss.co.jp/tech-blog/pgsql/logical-replication-1/</link>
		
		<dc:creator><![CDATA[データベース技術グループ]]></dc:creator>
		<pubDate>Thu, 21 Jan 2021 01:43:14 +0000</pubDate>
				<category><![CDATA[PostgreSQL]]></category>
		<category><![CDATA[PostgreSQL 10]]></category>
		<category><![CDATA[ロジカルレプリケーション]]></category>
		<guid isPermaLink="false">https://www.sraoss.co.jp/tech-blog/?p=6683</guid>

					<description><![CDATA[更新日:2025年5月1日 本記事では、PostgreSQL 10 より導入された新たなレプリケーション方式「ロジカルレプリケーション」の概要、構築方法について紹介します。 ロジカルレプリケーションの概要 ロジカルレプリ ...]]></description>
										<content:encoded><![CDATA[<p>更新日:2025年5月1日</p>
<p>本記事では、PostgreSQL 10 より導入された新たなレプリケーション方式「ロジカルレプリケーション」の概要、構築方法について紹介します。</p>
<p><span id="more-6683"></span></p>
<h2>ロジカルレプリケーションの概要</h2>
<h3>ロジカルレプリケーションとは</h3>
<p>ロジカルレプリケーションは PostgreSQL 10 より導入されたレプリケーションの機能です。レプリケーション元となる PostgreSQL でパブリケーション（publication・発行者）を作成し、レプリケーション先となる PostgreSQL でサブスクリプション（subscription・購読者）を作成することで、レプリケーション機能が有効となります。また、レプリケーション元の PostgreSQL をパブリッシャ、レプリケーション先の PostgreSQL をサブスクライバと呼びます。まずはストリーミングレプリケーションとの違いを見ながらロジカルレプリケーションの概要をご説明いたします。</p>
<p>※本記事では PostgreSQL 12 を想定しています。</p>
<h3>ロジカルレプリケーションとストリーミングレプリケーションの違い</h3>
<p>ロジカルレプリケーションとストリーミングレプリケーションの違いは以下のとおりです。</p>
<table>
<thead>
<tr>
<th>項目</th>
<th>ロジカルレプリケーション</th>
<th>ストリーミングレプリケーション</th>
</tr>
</thead>
<tbody>
<tr>
<th>転送するデータ</th>
<td>WALレコードに書き込まれた「どのような操作を行ったか」という論理的な情報</td>
<td>WAL レコード</td>
</tr>
<tr>
<th>データ転送のタイミング</th>
<td>COMMIT 完了時に送信</td>
<td>WAL レコードが発生する都度送信</td>
</tr>
<tr>
<th>送信に関連するプロセス</th>
<td>wal_sender</td>
<td>wal_sender</td>
</tr>
<tr>
<th>受信に関連するプロセス</th>
<td>logical replication launcher, logical replication worker</td>
<td>walreceiver, startup</td>
</tr>
<tr>
<th>レプリケーションの単位</th>
<td>テーブル単位、データベース単位</td>
<td>データベースクラスタ単位</td>
</tr>
<tr>
<th>レプリケーションされない操作</th>
<td>TRUNCATE(*1)以外の DDL 文</td>
<td>なし</td>
</tr>
<tr>
<th>レプリケーションされないオブジェクト</th>
<td>通常のテーブル以外(*2)</td>
<td>UNLOGGED テーブル</td>
</tr>
<tr>
<th>異なるメジャーバージョンでも動作できるか</th>
<td>できる</td>
<td>できない</td>
</tr>
<tr>
<th>異なる OS、アーキテクチャでも動作できるか</th>
<td>できる</td>
<td>できない</td>
</tr>
<tr>
<th>同期／非同期レプリケーション</th>
<td>できる</td>
<td>できる</td>
</tr>
<tr>
<th>スタンバイへの参照</th>
<td>できる</td>
<td>設定次第でできる</td>
</tr>
<tr>
<th>スタンバイへの更新</th>
<td>できる</td>
<td>できない</td>
</tr>
<tr>
<th>スタンバイでトリガ起動</th>
<td>できる</td>
<td>できない</td>
</tr>
</tbody>
</table>
<p>(*1) PostgreSQL 10 では TRUNCATE はレプリケーションされませんので TRUNCATE の代わりに DELETE で削除する必要があります。<br />
(*2) シーケンス、ラージオブジェクト、ビューなど。</p>
<h3>ロジカルレプリケーションを活用できる状況</h3>
<p>上記表でご確認いただいたように、下記のようなことを行いたい場合、ロジカルレプリケーションが役立つのではないかと考えられます。</p>
<ul>
<li>テーブル単位でのレプリケーションを行いたい</li>
<li>メジャーバージョンが異なる PostgreSQL 間でのレプリケーションを行いたい</li>
<li>サブスクライバ側でデータを受け取った際に、トリガを起動させたい</li>
<li>ストリーミングレプリケーションで発生する「マスタの更新と、スタンバイの参照の競合」を回避したい</li>
</ul>
<h2>ロジカルレプリケーション環境の構築</h2>
<p>実際にパブリケーションとサブスクリプションを作成して、ロジカルレプリケーションが動作する環境を構築していきます。</p>
<p>以降の解説内では</p>
<ul>
<li>パブリッシャとなる PostgreSQL のサーバ名を first_server、データベース名を first</li>
<li>サブスクライバとなる PostgreSQL のデータベース名を second</li>
<li>ロジカルレプリケーション用のユーザ名を logi_user</li>
</ul>
<p>としています。</p>
<p>そのため、</p>
<ul>
<li>first=# で始まる SQL はパブリッシャで実行するコマンド</li>
<li>second=# で始まる SQL はサブスクライバで実行するコマンド</li>
</ul>
<p>となります。</p>
<h3>postgresql.conf の設定</h3>
<p>ロジカルレプリケーションを機能させるには、パブリッシャの wal_level が logical である必要があります。wal_level のデフォルト値は replica ですので、パブリッシャの postgresql.conf で下記のように wal_level を logical に変更します。</p>
<pre>wal_level = 'logical'
</pre>
<p>その他、ロジカルレプリケーションに関連するパラメータは</p>
<ul>
<li>max_logical_replication_workers</li>
<li>max_replication_slots</li>
<li>max_sync_workers_per_subscription</li>
<li>max_wal_senders</li>
<li>max_worker_processes</li>
</ul>
<p>があります。機能させたいロジカルレプリケーションの数によっては、上記パラメータの値を調整する必要があります。それぞれのパラメータに必要な設定値は下記マニュアルをご確認ください。</p>
<p>[ 参考マニュアル &#8211; <a href="https://www.sraoss.co.jp/PostgreSQL/Manual/document/12/logical-replication-config.html">論理レプリケーション 構成設定</a> ]</p>
<h3>pg_hba.conf の設定</h3>
<p>サブスクライバの PostgreSQL からパブリッシャの PostgreSQL に接続できるように、パブリッシャの pg_hba.conf に設定を加えます。</p>
<pre>host  first           logi_user           xxx.xxx.xxx.xxx/32      md5
</pre>
<h3>パブリケーションの作成</h3>
<p>レプリケーション元となるパブリッシャで、パブリケーションを作成します。パブリケーションには、1つのテーブルだけでなく、複数のテーブルを指定できますので、複数のテーブルをまとめてレプリケーションできます。また、オプションでロジカルレプリケーションの対象とする操作（INSERT、UPDATE、DELETE、TRUNCATE）を指定できます。たとえば、INSERT のみをレプリケーションさせることができます。</p>
<p>ただし、UPDATE と DELETE をレプリケーションするためには、パブリッシャのテーブルの REPLICA IDENTITY （行を一意に特定できるカラムの情報）の設定が必要となります。テーブルに主キーが存在する場合は自動的に REPLICA IDENTITY に主キーが設定されます。主キーが存在しないテーブルに REPLICA IDENTITY の指定（USING INDEX または FULL）がない場合、UPDATE および DELETE を実行した際に、実行エラーが発生しますのでご注意ください。</p>
<h4>ロジカルレプリケーション用のテーブル作成</h4>
<p>ロジカルレプリケーションを行うためのテーブル logical_table をパブリッシャに作成します。</p>
<pre>first=# CREATE TABLE logical_table
        (id INTEGER PRIMARY KEY, no INTEGER , data TEXT);
</pre>
<h4>パブリケーションの作成例</h4>
<p>作成したテーブルに対して行った操作をサブスクライバに伝搬させるためのパブリケーションを作成します。パブリケーションの作成例を下記に記載します。</p>
<pre>-- logical_table に対する操作を伝搬させるパブリケーションの作成
first=# CREATE PUBLICATION logical_pub FOR TABLE logical_table;

-- すべてのテーブルに対する操作を伝搬させるパブリケーションの作成
first=# CREATE PUBLICATION logical_pub_all_table FOR ALL TABLES;

-- logical_table に対する INSERT を伝搬させるパブリケーションの作成
first=# CREATE PUBLICATION logical_pub_insert_only
        FOR TABLE logical_table WITH (publish = 'insert');

-- パブリケーションの枠だけ作成
first=# CREATE PUBLICATION logical_pub_non_table ;
</pre>
<p>※各パラメータの詳細は下記マニュアルをご確認ください。</p>
<p>[ 参考マニュアル &#8211; <a href="https://www.sraoss.co.jp/PostgreSQL/Manual/document/12/sql-createpublication.html">CREATE PUBLICATION</a> ]</p>
<h4>パブリケーションの確認</h4>
<p>pg_publication システムカタログを参照することで、作成されているパブリケーションを確認できます。</p>
<pre>first=# SELECT * FROM pg_publication;

  oid  |    pubname    | pubowner | puballtables | pubinsert | pubupdate | pubdelete | pubtruncate
-------+---------------+----------+--------------+-----------+-----------+-----------+-------------
 16651 | logical_pub   |       10 | f            | t         | t         | t         | t
(1 行)

</pre>
<p>psql であればメタコマンド <code>\dRp</code> で確認できます。</p>
<pre>first=# \dRp
                               パブリケーション一覧
    名前     |  所有者  | 全テーブル | Insert文 | Update文 | Delete文 | Truncate文
-------------+----------+------------+----------+----------+----------+------------
 logical_pub | postgres | f          | t        | t        | t        | t
(1 行)
</pre>
<h4>パブリケーションとテーブルのマッピングを確認する</h4>
<p>pg_publication_tables システムカタログで、パブリケーションとそれに含まれるテーブルのマッピングを確認できます。</p>
<pre>first=# SELECT * FROM pg_publication_tables;

    pubname   | schemaname | tablename
--------------+------------+---------------
 logical_pub  | public     | logical_table
(1 行)
</pre>
<p>上記の出力から、logical_pub というパブリケーションに logical_table テーブルがマッピングされていることが分かります。</p>
<p>パブリケーションとテーブルのマッピングを、名称ではなく oid で確認するには pg_publication_rel システムカタログを参照します。</p>
<pre>first=# select * from pg_publication_rel;
  oid  | prpubid | prrelid
-------+---------+---------
 16665 |   16664 |   16658
(1 行)
</pre>
<h3>サブスクリプションの作成</h3>
<p>パブリケーションを作成できたら、今度はパブリケーションから送られる更新情報を受け取るためのサブスクリプションをサブスクライバに作成します。</p>
<h4>ロジカルレプリケーション用のテーブル作成</h4>
<p>サブスクライバにもパブリッシャと同様のテーブルを作成します。これがロジカルレプリケーションの受け口になるテーブルです。</p>
<p>サブスクライバのテーブルは、パブリッシャのテーブルと</p>
<ul>
<li>スキーマ名</li>
<li>テーブル名</li>
<li>列名</li>
</ul>
<p>が同じである必要があります。サブスクライバのテーブル定義はパブリッシャのテーブルと完全に同一である必要はなく、サブスクライバのテーブル定義がパブリッシャのテーブル定義を内包しているのであれば、ロジカルレプリケーションが行われます。</p>
<p>下記の作成例では、パブリッシャのテーブル定義に加えて data2 カラムが存在しますが、id, no, data カラムがパブリッシャのテーブル定義と同一なためロジカルレプリケーションが行われます。</p>
<pre>second=# CREATE TABLE logical_table
         (id INTEGER PRIMARY KEY, no INTEGER, data TEXT, data2 TEXT);
</pre>
<h4>サブスクリプションの作成例</h4>
<p>パブリッシャへの接続文字列などを指定して、サブスクリプションを作成します。サブスクリプションの作成例を下記に記載します。</p>
<pre>-- パブリッシャの logical_pub パブリケーションに対する操作を受け取るための
-- サブスクリプションを作成
second=# CREATE SUBSCRIPTION logical_sub
         CONNECTION 'host=first_server port=5432 user=logi_user dbname=first'
         PUBLICATION logical_pub;

-- パブリッシャに作成するレプリケーションスロット名（slot_name）を指定して、
-- サブスクリプションを作成
-- (slot_name を指定しない場合、レプリケーションスロット名にはサブスクリプション名が設定されます)
second=# CREATE SUBSCRIPTION logical_sub
         CONNECTION 'host=first_server port=5432 user=logi_user dbname=first'
         PUBLICATION logical_pub WITH (slot_name = 'logical_sub_slot');

-- パブリッシャに通知するアプリケーション名（application_name）を指定して、
-- サブスクリプションを作成
-- (application_name を指定しない場合、アプリケーション名にはサブスクリプション名が設定されます)
second=# CREATE SUBSCRIPTION logical_sub
         CONNECTION 'host=first_server port=5432 user=logi_user dbname=first application_name=logical_sub_app'
         PUBLICATION logical_pub;

-- コミットの同期モード（synchronous_commit）を指定して、サブスクリプションを作成
-- （postgresql.conf で設定した synchronous_commit よりも優先されます）
-- （synchronous_commit を指定しない場合、off が設定されます）
second=# CREATE SUBSCRIPTION logical_sub
         CONNECTION 'host=first_server port=5432 user=logi_user dbname=first'
         PUBLICATION logical_pub WITH (synchronous_commit = 'on');
</pre>
<p>※上記以外にも指定できるパラメータやオプションが存在します。詳細はマニュアルをご確認ください。</p>
<p>[ 参考マニュアル &#8211; <a href="https://www.sraoss.co.jp/PostgreSQL/Manual/document/12/sql-createsubscription.html">CREATE SUBSCRIPTION</a> ]</p>
<h4>作成したサブスクリプションの確認</h4>
<p>作成したサブスクリプションの情報は pg_subscription を参照することで確認できます。</p>
<pre>second=# SELECT * FROM pg_subscription;

-[ RECORD 1 ]---+--------------------------------------------------------------------------------------------
oid             | 24704
subdbid         | 13266
subname         | logical_sub
subowner        | 10
subenabled      | t
subconninfo     | host=first_server port=5432 user=logi_user dbname=first application_name=logical_sub_app
subslotname     | logical_sub_slot
subsynccommit   | on
subpublications | {logical_pub}
</pre>
<p>psql であればメタコマンド <code>\dRs</code> で確認できます。</p>
<pre>second=# \dRs
                サブスクリプション一覧
    名前     |  所有者  | 有効状態 | パブリケーション
-------------+----------+----------+------------------
 logical_sub | postgres | t        | {logical_pub}
(1 行)
</pre>
<p>サブスクリプションのレプリケーション状態は pg_stat_subscription を参照することで確認できます。</p>
<pre>second=# SELECT * FROM pg_stat_subscription;

-[ RECORD 1 ]---------+------------------------------
subid                 | 24704
subname               | logical_sub
pid                   | 9374
relid                 |
received_lsn          | 6/B9064B18
last_msg_send_time    | 2020-10-14 16:43:37.585108+09
last_msg_receipt_time | 2020-10-14 16:43:37.586771+09
latest_end_lsn        | 6/B9064B18
latest_end_time       | 2020-10-14 16:43:37.585108+09
</pre>
<p>また、サブスクリプションとテーブルのマッピングは pg_subscription_rel で確認できます。</p>
<pre>second=# SELECT * FROM pg_subscription_rel;

 srsubid | srrelid | srsubstate | srsublsn
---------+---------+------------+----------
   24704 |   24679 | d          |
(1 行)
</pre>
<p>上記のとおり、pg_subscription_rel の中にはテーブルの名前ではなく oid で情報が保持されています。パブリケーションと違い、現時点でマッピングの情報を名称で確認できるシステムカタログはないようです。サブスクリプションとテーブル名のマッピングは pg_subscription と pg_class を合わせて参照することで確認できます。</p>
<pre>second=# SELECT
    pg_subscription.subname
  , pg_class.relname
  , pg_subscription_rel.srsubstate
  , pg_subscription_rel.srsublsn
FROM
  pg_subscription_rel
    INNER JOIN pg_subscription
            ON pg_subscription_rel.srsubid = pg_subscription.oid
    INNER JOIN pg_class
            ON pg_subscription_rel.srrelid = pg_class.oid
;

   subname   |    relname    | srsubstate | srsublsn
-------------+---------------+------------+----------
 logical_sub | logical_table | d          |
(1 行)
</pre>
<h4>初期データコピー</h4>
<p>サブスクリプションを作成した時点で、初期コピーと呼ばれる「パブリッシャのテーブルからサブスクライバのテーブルへのデータコピー処理」が実行されます。</p>
<pre>-- サブスクリプション作成前の logical_table の中身（初期コピー前は 0 件）
second=# =# SELECT * FROM logical_table;
 id | no | data | data2
----+----+------+-------
(0 行)

-- サブスクリプション作成（初期コピー実施）
second=# CREATE SUBSCRIPTION logical_sub
         CONNECTION 'host=first_server port=5432 user=logi_user dbname=first'
         PUBLICATION logical_pub;
CREATE SUBSCRIPTION

-- サブスクリプション作成後の logical_table の中身
-- （パブリッシャの logical_table のデータがコピー）
second=# SELECT * FROM logical_table;
 id | no |  data  | data2
----+----+--------+-------
  1 |  1 | 北海道 |
  2 |  2 | 青森県 |
  3 |  3 | 岩手県 |
  : |  : | 　:　 |
  : |  : | 　:　 |
 47 | 47 | 沖縄県 |
(47 行)

</pre>
<p>また、この初期コピーの際に、コピーされるパブリッシャのデータとサブスクライバの既存のデータが競合し、一意制約エラーの発生する可能性があります。</p>
<p>これは初期コピーが「差分のみを追加する」などの制御を行わずに、単純に「パブリッシャのテーブルのデータを、そのままサブスクライバのテーブルへ追加する」ためです。</p>
<pre># データ競合時のエラーメッセージ
2020-10-14 15:15:27.775 JST [8365](err_code=23505) ERROR:  duplicate key value violates unique constraint "logical_table_pkey"
2020-10-14 15:15:27.775 JST [8365](err_code=23505) DETAIL:  Key (id)=(1) already exists.
</pre>
<p>こういった場合、copy_data オプションに false を指定し、初期コピーを行わないことでエラーを回避できます。</p>
<pre>second=# CREATE SUBSCRIPTION logical_sub
         CONNECTION 'host=first_server port=5432 user=logi_user dbname=first'
         PUBLICATION logical_pub WITH (copy_data = false);
</pre>
<h3>サブスクリプションの有効／無効の切り替え</h3>
<p>サブスクリプションは有効／無効を切り替えることができます。そのため、ロジカルレプリケーションを一時的に中断したい場合でも、サブスクリプションを削除する必要がありません。</p>
<h4>サブスクリプションの無効化</h4>
<p>サブスクリプションを無効にします。無効化している間は、当然ですがパブリッシャで行った操作がサブスクライバへレプリケーションされなくなります。</p>
<pre>second=# ALTER SUBSCRIPTION logical_sub DISABLE;
</pre>
<h4>サブスクリプションの有効化</h4>
<p>サブスクリプションを有効にします。サブスクリプションを有効にした場合、無効化した時点からレプリケーションが行われます。そのため、サブスクリプションを有効化することで、無効化されている間に行った操作がサブスクライバへレプリケーションされます。</p>
<pre>second=# ALTER SUBSCRIPTION logical_sub ENABLE;
</pre>
<h2>まとめ</h2>
<p>以上、ロジカルレプリケーションの概要と基本的な使い方をご紹介させていただきました。次回以降では、ロジカルレプリケーションの機能を実現するプロセスについての解説や、カスケードレプリケーションの設定やコンフリクト発生時の解消方法などの運用に関する情報などをお伝えする予定です。</p>
<div>
<a href="https://www.sraoss.co.jp/prod_serv/support/pgsql-mainte/?utm_source=sraoss-techblog&utm_medium=referral&utm_campaign=under-article-pgsql" rel="noopener" style="color:initial;" target="_blank" class="no-ext"></p>
<div style="background-color:#DAEFF6; padding:20px; border-radius:8px; text-align:center;">
SRA OSSでは、PostgreSQL/Pgpool-II のサポートを提供しております。<br />
詳しくは<strong style="color:#2581c4;">弊社ホームページ</strong>をご覧ください。
</div>
<p></a>
</div>

]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
