物理レプリケーション(前の記事)は「クラスタ丸ごと・同一バージョン」のHAでした。しかし現場には、別の要求があります——「このテーブルだけ別システムへ流したい」「メジャーアップグレードを無停止でやりたい」「変更をイベントとして下流に配りたい(CDC)」。
これらに応えるのが論理レプリケーションです。この記事は、その仕組みと構築、そして最大の応用である版跨ぎのゼロダウンタイム・メジャーアップグレードを、公式ドキュメントに忠実に解説します。あわせて「何が複製されないか」という、事故に直結する制約を徹底的に潰します。本番運用ガイド §7の深掘りです。
この記事のルール:仕様・制約・アップグレード手順・PostgreSQL 18 の変更点は PostgreSQL 18 公式ドキュメント(2026年6月時点) に基づきます。論理レプリケーションは制約が多く事故りやすいため、複製されないものを特に正確に扱います。
1. 論理 vs 物理:何が違うのか
公式の定義:
論理レプリケーションは、レプリケーション識別子(通常は主キー)に基づいてデータオブジェクトとその変更を複製する手法である。これを論理的と呼ぶのは、正確なブロックアドレスとバイト単位で複製する物理レプリケーションとの対比による。
違いを表に。
| 物理レプリケーション | 論理レプリケーション | |
|---|---|---|
| 単位 | クラスタ全体(バイト単位) | テーブル単位(行の変更) |
| バージョン | 同一メジャー版が必須 | 版跨ぎ可(→アップグレードに使える) |
| プラットフォーム | 同一 | 跨ぎ可(Linux→Windows等) |
| 方向 | 一方向 | 双方向・カスケード可 |
| 用途 | HA/DR、リードレプリカ | 選択的複製、CDC、版跨ぎ移行、DB統合 |
公式が挙げる用途には、「個々の変更が到着するたびにサブスクライバでトリガを発火する」(=CDC/イベント駆動)、「複数DBを1つに統合(分析用途等)」、「異なるメジャーバージョン間の複製」(=アップグレード)が含まれます。
2. publish / subscribe で構築する
論理レプリケーションは**パブリッシャ(publish)とサブスクライバ(subscribe)**のモデルです。最小構成:
パブリッシャ側
# postgresql.conf:論理デコードに必要
wal_level = logical
-- 複製したいテーブルの集合を「パブリケーション」として定義
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') で挿入だけ流すこともできます。
サブスクライバ側
-- スキーマは事前に用意しておく(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(インプレース・短時間停止)
# 旧クラスタを停止し、新バージョンへインプレース変換
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 を実務向けに):
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 で設定します。
-- 切替時:各シーケンスをテーブルの最大値より先へ進める(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 の代替にもなる強力なパターンです(信頼性設計はトランザクショナル・アウトボックスの記事も参照)。
6. 監視
-- サブスクライバ側:適用の遅延と状態
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) へ。