# PostgreSQL 論理レプリケーション実践（publish/subscribe・CDC・版跨ぎのゼロダウンタイム・メジャーアップグレード・v18対応）

> PostgreSQL の論理レプリケーションを本番で使う実践ガイド。物理レプリケーションとの違い、publish/subscribeの構築、CDC(変更データキャプチャ)、そして版跨ぎのゼロダウンタイム・メジャーアップグレード(数秒の停止で18へ)までを解説。DDL・シーケンスが複製されないという最重要の制約と切替時の落とし穴、pg_upgradeの--swap、PostgreSQL 18の改善も公式ドキュメントに忠実に。

- 公開日: 2026-06-22
- 著者: 友田 陽大
- タグ: PostgreSQL, アーキテクチャ設計
- URL: https://tomodahinata.com/blog/postgresql-logical-replication-cdc-zero-downtime-upgrade-guide

## 要点

- 論理レプリケーションは『レプリケーション識別子(通常は主キー)に基づく行単位の複製』。物理(バイト単位・クラスタ全体・同一版)と違い、テーブル単位・版跨ぎ・双方向ができる
- publish/subscribeモデル：パブリッシャでCREATE PUBLICATION、サブスクライバでCREATE SUBSCRIPTION、wal_level=logical。初期コピー後にストリーミング
- 最重要の制約：DDL(スキーマ変更)は複製されない／シーケンスは複製されない／UPDATE・DELETEには主キー(レプリカ識別子)が必要／ラージオブジェクト・ビューは対象外。切替時にシーケンスを手で進める必要がある
- 版跨ぎのゼロダウンタイム・アップグレード：新版インスタンスへ論理複製し、追いついたら切替＝公式曰く『数秒のダウンタイム』。pg_upgrade(インプレース)はPG18の--swapで最速化
- PostgreSQL 18：生成列の論理複製、CREATE SUBSCRIPTIONのstreaming既定がparallelに、競合のログ記録。pg_upgradeは統計引き継ぎ・並列チェック(--jobs)・--swapを追加

---

物理レプリケーション（[前の記事](/blog/postgresql-streaming-replication-high-availability-failover-guide)）は「クラスタ丸ごと・同一バージョン」のHAでした。しかし現場には、別の要求があります——「**このテーブルだけ**別システムへ流したい」「**メジャーアップグレードを無停止で**やりたい」「変更を**イベントとして**下流に配りたい（CDC）」。

これらに応えるのが**論理レプリケーション**です。この記事は、その仕組みと構築、そして最大の応用である**版跨ぎのゼロダウンタイム・メジャーアップグレード**を、公式ドキュメントに忠実に解説します。あわせて「**何が複製されないか**」という、事故に直結する制約を徹底的に潰します。[本番運用ガイド §7](/blog/postgresql-production-operations-guide)の深掘りです。

> **この記事のルール**：仕様・制約・アップグレード手順・PostgreSQL 18 の変更点は **PostgreSQL 18 公式ドキュメント（2026年6月時点）** に基づきます。論理レプリケーションは制約が多く事故りやすいため、**複製されないもの**を特に正確に扱います。

---

## 1. 論理 vs 物理：何が違うのか

公式の定義：

> 論理レプリケーションは、**レプリケーション識別子（通常は主キー）に基づいて**データオブジェクトとその変更を複製する手法である。これを*論理的*と呼ぶのは、**正確なブロックアドレスとバイト単位**で複製する*物理*レプリケーションとの対比による。

違いを表に。

| | 物理レプリケーション | 論理レプリケーション |
| --- | --- | --- |
| 単位 | クラスタ全体（バイト単位） | **テーブル単位**（行の変更） |
| バージョン | 同一メジャー版が必須 | **版跨ぎ可**（→アップグレードに使える） |
| プラットフォーム | 同一 | **跨ぎ可**（Linux→Windows等） |
| 方向 | 一方向 | **双方向・カスケード可** |
| 用途 | HA/DR、リードレプリカ | 選択的複製、CDC、版跨ぎ移行、DB統合 |

公式が挙げる用途には、**「個々の変更が到着するたびにサブスクライバでトリガを発火する」**（＝CDC/イベント駆動）、**「複数DBを1つに統合（分析用途等）」**、**「異なるメジャーバージョン間の複製」**（＝アップグレード）が含まれます。

---

## 2. publish / subscribe で構築する

論理レプリケーションは**パブリッシャ（publish）とサブスクライバ（subscribe）**のモデルです。最小構成：

### パブリッシャ側

```ini
# postgresql.conf：論理デコードに必要
wal_level = logical
```

```sql
-- 複製したいテーブルの集合を「パブリケーション」として定義
CREATE PUBLICATION app_pub FOR TABLE users, orders;
-- スキーマ単位・全テーブルも可
-- CREATE PUBLICATION all_pub FOR ALL TABLES;
-- CREATE PUBLICATION prod_pub FOR TABLES IN SCHEMA production;
```

`publish` パラメータの既定は `'insert, update, delete, truncate'`（全操作）。`WITH (publish = 'insert')` で挿入だけ流すこともできます。

### サブスクライバ側

```sql
-- スキーマは事前に用意しておく（DDLは複製されない。§3）
-- CREATE TABLE users (...); CREATE TABLE orders (...);

CREATE SUBSCRIPTION app_sub
  CONNECTION 'host=publisher dbname=appdb user=replicator sslmode=verify-full'
  PUBLICATION app_pub;
-- 既定で初期データがコピーされ、その後ストリーミングが始まる
```

公式：サブスクリプションは既定で**初期データをコピー**し（`copy_data`）、その後の変更をストリーミングします。PostgreSQL 18 では `CREATE SUBSCRIPTION` の `streaming` 既定が `off` から **`parallel`** に変わりました。

---

## 3. 最重要：何が複製されないか

論理レプリケーションの事故は、ほぼ全て**「複製されないもの」を知らない**ことから起きます。公式の制約を正確に押さえます。

### ⚠ DDL（スキーマ変更）は複製されない

> データベーススキーマと DDL コマンドは複製されない。初期スキーマは `pg_dump --schema-only` で手動コピーできる。**後続のスキーマ変更は手動で同期し続ける必要がある**。

つまり**パブリッシャで `ALTER TABLE` しても、サブスクライバには反映されません**。しかも公式は重要な順序を示します：

> パブリッシャでスキーマが変わり、サブスクライバのテーブル定義に合わないデータが届くと、スキーマを更新するまで**複製はエラーで止まる**。多くの場合、**サブスクライバに先に追加的なスキーマ変更を適用**することで、断続的エラーを避けられる。

→ **列追加は「サブスクライバ先 → パブリッシャ後」**の順で。

### ⚠ シーケンスは複製されない（切替時の地雷）

> シーケンスデータは複製されない。serial / identity 列の値はテーブルの一部として複製されるが、**シーケンス自体はサブスクライバで開始値のまま**である。…**切替やフェイルオーバーを意図する場合、シーケンスを最新値に更新する必要がある**。

これは**アップグレードの切替時に最も人を刺す落とし穴**です（§4 で対処）。

### ⚠ UPDATE/DELETE には主キー（レプリカ識別子）が必要

論理レプリケーションは「レプリケーション識別子（通常は主キー）」に基づきます。**主キーや明示的な `REPLICA IDENTITY` を持たないテーブルは、UPDATE/DELETE を複製できません**（INSERT は可）。全テーブルに適切な主キーを——これは設計の前提になります。

### その他の制約

- **ラージオブジェクトは複製されない**（回避策なし。通常テーブルに格納すること）。
- **テーブルのみ**が対象（**ビュー・マテビュー・外部テーブルはエラー**）。
- パーティションテーブルは既定で**リーフパーティションから**複製される（`publish_via_partition_root` で変更可）。

---

## 4. 版跨ぎのゼロダウンタイム・メジャーアップグレード

論理レプリケーションの最大の実用価値が、**メジャーアップグレードの無停止化**です。メジャーアップグレードには2つの道があります（公式 §18.6）。

### 道A：pg_upgrade（インプレース・短時間停止）

```bash
# 旧クラスタを停止し、新バージョンへインプレース変換
pg_upgrade \
  --old-datadir=/var/lib/postgresql/17/main \
  --new-datadir=/var/lib/postgresql/18/main \
  --old-bindir=/usr/lib/postgresql/17/bin \
  --new-bindir=/usr/lib/postgresql/18/bin \
  --jobs=4 \        # PG18：チェックを並列化
  --swap            # PG18：ディレクトリ入替で最速（コピー/リンクより速い）
```

公式：「pg_upgrade はインストールを**インプレースで**あるメジャー版から別の版へ移行できる。特に `--link` モードなら**数分で**完了する」。PostgreSQL 18 の改善：

- **統計の引き継ぎ**（`--no-statistics` で無効化可）——アップグレード後の「統計が空でプランナーが誤動作」を防ぐ。
- **`--jobs` による並列チェック**——オブジェクトが多いDBで高速。
- **`--swap`**——ディレクトリを入れ替える最速モード（ただし旧クラスタは破壊的に変更され、以後起動不可）。

`--swap` の注意（公式）：「ファイル転送が始まると旧クラスタは破壊的に変更され、**もはや安全に起動できない**」。事前のバックアップ必須です。

### 道B：論理レプリケーションで切替（数秒の停止）

「ほぼ無停止」を極めるなら、**新バージョンのインスタンスへ論理複製し、追いついたら切り替える**。公式：

> 論理レプリケーションは異なるメジャー版間の複製をサポートするため、**更新済みバージョンのスタンバイを作れる**。…プライマリ（旧版）に追いついたら、プライマリを切り替えてスタンバイを新プライマリにし、旧インスタンスを停止する。**この切替は数秒のダウンタイムで済む**。

手順の骨子（公式 §29.13 を実務向けに）：

```text
1. 新バージョン(18)で空のインスタンスを構築（wal_level=logical）
2. パブリッシャ(旧17)の現スキーマを pg_dump --schema-only で新18へ適用（DDLは複製されないため）
3. 旧17に CREATE PUBLICATION、新18に CREATE SUBSCRIPTION → 初期コピー＋ストリーミング開始
4. レプリケーション遅延が0に追いつくのを待つ（pg_stat_subscription / lag を監視）
5. 【切替】アプリを一時停止 → 残りの変更が新18へ届くのを確認
6. 【最重要】シーケンスを手で進める（複製されないため）
7. アプリの接続先を新18へ向けて再開 → 旧17を停止
```

**ステップ6のシーケンス更新**を忘れると、切替直後に主キー衝突（重複ID）で**本番が壊れます**。公式の指示どおり、`pg_dump` でシーケンス現在値を写すか、テーブルの最大値から十分大きい値を `setval` で設定します。

```sql
-- 切替時：各シーケンスをテーブルの最大値より先へ進める（ID衝突を防ぐ）
SELECT setval('users_id_seq', (SELECT max(id) FROM users));
SELECT setval('orders_id_seq', (SELECT max(id) FROM orders));
-- 多数あるなら information_schema から生成して一括実行する
```

> **どちらを選ぶか**：停止許容が「分」なら `pg_upgrade`（シンプル）。停止許容が「数秒」、かつダウンタイムが事業に直結するなら論理レプリケーション（手間は増えるが無停止に近い）。**まず `pg_upgrade` を検討し、それでは停止時間が長すぎる場合に論理レプリケーションへ**——これが現実的な判断順です。

---

## 5. CDC：変更をイベントとして配る

論理レプリケーションの基盤（論理デコード）は、**変更データキャプチャ（CDC）**にも使えます。公式が用途に挙げる「個々の変更が届くたびにサブスクライバでトリガを発火」がそれ。さらに **Debezium** などは論理デコードのスロットを購読し、行変更を Kafka 等へ**イベントとして**流します。

これにより、「DBの変更 → 検索インデックス更新／キャッシュ無効化／監査ログ／マイクロサービス連携」を、アプリのコードに二重書き込みを仕込まずに実現できます。アプリは DB に書くだけ。下流はWALから変更を受け取る——**Transactional Outbox の代替**にもなる強力なパターンです（信頼性設計は[トランザクショナル・アウトボックスの記事](/blog/transactional-outbox-pattern-reliable-event-publishing-guide)も参照）。

---

## 6. 監視

```sql
-- サブスクライバ側：適用の遅延と状態
SELECT subname, received_lsn, latest_end_lsn,
       last_msg_receipt_time, latest_end_time
FROM pg_stat_subscription;

-- PG18：競合がログと統計に記録される
SELECT * FROM pg_stat_subscription_stats;   -- 競合カウント（apply_error_count 等）
```

PostgreSQL 18 は**論理複製の競合をログに記録**し、`pg_stat_subscription_stats` の新カラムで可視化します。**生成列の論理複製**（`publish_generated_columns`）、`ALTER SUBSCRIPTION` での二相コミット切替も追加されました。

---

## 7. まとめ

- **論理レプリケーション**＝行単位・テーブル単位・**版跨ぎ**・双方向。物理（クラスタ全体・同一版）とは別物。
- **publish/subscribe** で構築（`wal_level=logical`）。初期コピー後にストリーミング。
- **複製されないもの**を必ず把握：**DDL・シーケンス・ラージオブジェクト・ビュー**。UPDATE/DELETE には**主キー**が要る。
- **版跨ぎの無停止アップグレード**：`pg_upgrade`（分単位・PG18の`--swap`で最速）か、論理複製＋切替（**数秒**）。**切替時のシーケンス更新**を絶対に忘れない。
- **CDC** にも使える（Debezium 等）。アプリの二重書き込みなしに変更をイベント配信。

「進化させる」が固まったら、日々のスキーマ変更を止めずに行う技術——[ゼロダウンタイムのスキーマ変更（ロックセーフDDL）](/blog/postgresql-zero-downtime-schema-migration-lock-safe-ddl-guide) へ。

---

### 参考（PostgreSQL 18 公式ドキュメント）

- [Chapter 29. Logical Replication](https://www.postgresql.org/docs/18/logical-replication.html)
- [29.4. Restrictions（複製されないもの）](https://www.postgresql.org/docs/18/logical-replication-restrictions.html)
- [29.13. Upgrade（論理複製でのアップグレード）](https://www.postgresql.org/docs/18/logical-replication-upgrade.html)
- [18.6. Upgrading a PostgreSQL Cluster](https://www.postgresql.org/docs/18/upgrading.html)
- [pg_upgrade（--swap / --jobs / 統計引き継ぎ）](https://www.postgresql.org/docs/18/pgupgrade.html)
