PostgreSQL のメジャーバージョンアップにはいくつかの手法がありますが、本記事ではその中でもロジカルレプリケーションを利用した方法を紹介します。ロジカルレプリケーションを用いることで、新旧バージョンのデータベースを並行稼働させながらデータを同期できるため、切り替え時のダウンタイムを最小限に抑えたアップグレードが可能です。その具体的な手順とあわせて、実施時に注意すべきポイントについて解説します。
なお、PostgreSQLのロジカルレプリケーション自体についての基本的な解説は、以下の記事を参考にしてください。
ロジカルレプリケーションによるアップグレードの概要
ロジカルレプリケーションを利用することで、異なるメジャーバージョン間でデータを同期しながら新しい環境を構築することが可能です。新旧データベースを並行稼働させ、最終的に接続先を切り替えることで、数秒程度のダウンタイムで移行を実現できます。

上記イメージはロジカルレプリケーションを用いたバージョンアップのアーキテクチャイメージです。
対応バージョンとしては、Publisher(旧バージョンのデータベース)は PostgreSQL 10 以上である必要があります。例えば PostgreSQL 13 から 16 へのアップグレードは可能ですが、9.6 からの移行には対応していません。
全体の流れは以下のとおりです。これらの手順について詳しく解説します。
ロジカルレプリケーションはデータベース単位で構成されるため、各データベースごとに個別に設定・同期を行います。
- 旧バージョンのデータベースを稼働させたまま、新バージョンのデータベースクラスタを initdb で構築し起動する
以下の手順を、対象となるすべてのデータベースに対して実施します。
- 旧バージョンの各データベースで全テーブルを対象としたパブリケーションを作成
- 新バージョン側に移行するデータベースを作成
- 旧バージョンの各データベースから pg_dump -s でスキーマ定義の SQL を取得して新バージョンの各データベースにリストア
- 新バージョンの各データベースでサブスクリプションを作成
すべてのデータベースでサブスクリプション作成および同期が完了した後、
以下の作業を実施します。
- 旧バージョンのデータベースクラスタへの更新処理を停止する
- 各データベースで pg_stat_subscription を確認し、LSN の一致を確認する
- 同期完了後、シーケンス位置やラージオブジェクトなどロジカルレプリケーションで同期されないデータを各データベースで手動同期する
- アプリケーションの接続先を切り替える
ロジカルレプリケーションによるアップグレードの注意点
ロジカルレプリケーションでは、テーブルの行データに対する変更がサブスクライバへ同期されます。一方で、行データ以外の更新情報は同期されません。
そのため、以下のようなオブジェクトは別途作成または移行する必要があります。
- テーブルやスキーマの定義
- ビューおよびマテリアライズドビューの定義
- パーティションテーブルや外部テーブルの定義
- シーケンスの現在値
- ラージオブジェクト
- 拡張機能
- UNLOGGED TABLE
- ロール
特にスキーマ、テーブル定義についてはロジカルレプリケーションでは同期されず、サブスクライバ側で定義されていない状態でロジカルレプリケーションを行うと伝播先のテーブルがないとしてレプリケーションに失敗します。よってスキーマ、テーブル定義については pg_dump を用いて定義の SQL を取得して新バージョンのデータベースで実行するなどして事前にリストアしておく必要があります。今回紹介する手順では pg_dump に -s オプションを付与して実行し、各 DDL を取得してロジカルレプリケーション前に新バージョンのデータベースで実行するようにします。
またその他にも拡張機能やロールなどもロジカルレプリケーションでレプリケーションされないため必要に応じて別途リストアする必要があります。
アップグレード手順
旧バージョンデータベースのロジカルレプリケーションのセットアップ
今回使用する環境は以下の通りで、PostgreSQL 14 から PostgreSQL 18 に移行します。
| 領域 | パス |
| 旧バージョンの データベースクラスタ | /var/lib/pgsql/14 |
| 新バージョンの データベースクラスタ | /var/lib/pgsql/18 |
| 旧バージョンのPostgreSQLコマンドパス | /usr/local/pgsql14/bin |
| 新バージョンのPostgreSQLコマンドパス | /usr/local/pgsql18/bin |
まず最初に旧バージョンのデータベースを稼働させたまま新データベースクラスタを構築し、旧バージョンでロジカルレプリケーションの有効化とパブリケーションを作成を行います。
#新しいバージョンのデータベースクラスタを作成して起動 $ /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
ロジカルレプリケーションではパブリッシャの wal_level は logical である必要があります。ロジカルレプリケーションとは別にストリーミングレプリケーションを利用している場合は既に使用しているレプリケーションスロット数に今回のロジカルレプリケーション用に必要なスロット数を加えた値を max_replication_slots に設定してください。複数のデータベースを新バージョンへ移行する場合は、旧バージョン側でデータベースの数分レプリケーションスロットを作成しますのでその分のスロット数も max_replication_slots に乗じてください。
またパブリッシャから WAL 情報を送信する walsender プロセスの上限を決定する max_wal_senders もレプリケーション接続される数を超えるように設定してください。設定後にサーバーを再起動して設定を反映します。
# ロジカルレプリケーションを利用できるよう旧バージョンの 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
再起動後、今回のレプリケーションの動作確認用に、旧バージョンのデータベースに users テーブルを作成します。
[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)
その後パブリケーションを作成します。パブリケーションの対象を FOR ALL TABLES とし、すべてのテーブルを同期対象にするようにします。複数データベースを移行する場合はデータベースごとにパブリケーションを作成してください。
postgres=# CREATE PUBLICATION pub_all FOR ALL TABLES; # 他データベースも必要であれば同様に作成する db2=# CREATE PUBLICATION pub_all_2 FOR ALL TABLES;
旧バージョンデータベースの pg_hba.conf 設定
旧バージョン側 pg_hba.conf には、新バージョンデータベースからのレプリケーション接続を許可する設定を追加します。論理レプリケーションでは、サブスクライバからパブリッシャへ接続してデータ取得を行うため、replication 権限を持つ接続許可が必要です。
# サブスクライバからのレプリケーション接続を許可 host replication postgres 0.0.0.0/0 trust
新バージョンに移行するデータベースとスキーマを移行
ロジカルレプリケーションではテーブルデータのみが同期対象となり、ロール、データベース、スキーマ定義などは自動的には同期されません。そのため、ロジカルレプリケーションを開始する前にこれらを新バージョン側へ移行しておく必要があります。
まずはロールやテーブルスペースなどのグローバルオブジェクトを移行します。
# グローバルオブジェクトをダンプ・リストア [postgres@server1 ~]$ /usr/local/pgsql18/bin/pg_dumpall -g | /usr/local/pgsql18/bin/psql -p 5433
次に新バージョンの各データベースに pg_dump -s -C でスキーマ定義を旧バージョンのデータベースからリストアしたのちにサブスクリプションを作成してテーブルデータを同期します。注意点でも述べた通りロジカルレプリケーションではスキーマやテーブル定義といった DDL までは同期されないので別途に pg_dump によって手動でリストアしておく必要があります。
pg_dump, psql は -d で対象データベースを指定します。データベースを複数移行する場合はデータベースごとに pg_dump, psql を実施し、-d で指定するデータベースを変更してください。
pg_dump の結果を psql で実行した後に \d でリレーションを確認すると旧バージョンで作成した users テーブルやシーケンスがコピーされていることが分かります。
[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)
新バージョンの各データベースでのサブスクリプションのセットアップ
次に旧バージョンの各データベースで作成したパブリケーションに対してサブスクリプションを作成します。サブスクリプションは移行するデータベースごとに作成し、CONNECTION の引数内の dbname や PUBLICATION でしているパブリケーション名は適宜変更してください。
その後テーブルの中身を SELECT するとテーブルデータが同期されていることが分かります。
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)
旧バージョンの各データベースへの更新を停止して同期の完了を待ち、シーケンス位置を揃える
サブスクリプション作成後、旧バージョンの各データベースへの更新を一時停止します。これによって旧バージョン側の LSN を止め、新バージョン側の LSN が追い付ける状態にします。
更新処理停止後、新バージョン各データベースごとに pg_stat_subscription 内を確認して received_lsn と latest_end_lsn が一致していることが確認できれば同期は完了です。received_lsn はパブリッシャから受け取った WAL 位置を、latest_end_lsn はパブリッシャから受け取った WAL をサブスクライバで適用し、その完了をパブリッシャに報告した位置を示します。
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
その後シーケンス位置を手動で同期します。ロジカルレプリケーションではシーケンス位置はレプリケーションされず、また pg_dump -s でもシーケンス位置を設定する SQL は出力されません。そのためシーケンスの位置は手動で揃える必要があります。
新バージョン側でシーケンスを調整する前の時点ではシーケンスの開始値が初期値である 1 のままとなっています。そのため、新バージョンのデータベース側で INSERT を実行すると、すでにテーブルに存在する id=1 の行と重複し、ユニーク制約違反が発生します。
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.
シーケンス位置は pg_dump の出力に含まれる setval の SQL を新バージョン側の各データベースで実行して反映します。-d オプションの引数もデータベースごとに適宜変更してください。
[postgres@server1 ~]$ pg_dump -p 5432 -d postgres > 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);
その他ラージオブジェクトや拡張機能など、WAL 書き出されずロジカルレプリケーションの対象外となっているものについても手動で同期を行います。これによって旧バージョンと新バージョンの同期は完了です。
同期完了後アプリケーションの接続先を新バージョンのデータベースに切り替える
すべてのデータベースの同期完了後にアプリケーションの接続先を切り替えます。
新バージョンの各データベースのサブスクリプションを無効化、マテリアライズドビューをすべて REFRESH したのちに、新バージョン側のpg_hba.conf にアプリケーションからの接続の認証情報を記述し、アプリケーションの接続先を切り替えればアップグレードは完了です。
# サブスクリプションを無効化 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
まとめ
ロジカルレプリケーションを用いた PostgreSQL のメジャーアップグレードは、適切に設計・運用することで、サービス停止時間を最小限に抑えた安全な移行を実現できます。ただし、データ以外の要素は自動的には同期されないため、スキーマ、シーケンス位置などを含めた包括的な準備が不可欠です。
事前検証と十分な計画を行うことで、本番環境においても安定したアップグレードを実施できます。