アルファテックブログ

論理レプリケーションを用いたPostgreSQL 12→17へのメジャーバージョンアップ

カバー

はじめに

当社では、コミュニケーションツールとしてMattermostを利用しており、私はその管理を担当しています。 これまで、MattermostのデータベースにはPostgreSQL 12を使用してきましたが、サポート終了に伴い、PostgreSQL 17へのメジャーバージョンアップが必要になりました。

移行方法を検討する中で、利用者が多いMattermostではサービス停止時間をできるだけ短くすることが重要な課題でした。 従来の実績ある方法はpg_dumpとデータインポートによる移行ですが、データ量が多いため、ダンプやインポートに時間がかかり、停止時間が長引く懸念がありました。 また、pg_upgradeも検討しましたが、現在のPostgreSQL 12は社内のOpenStack Troveで管理されており、直接のアップグレードは困難でした。 そこで、停止時間の短縮とメジャーバージョンアップを両立できる方法として、「論理レプリケーション」を採用しました。

結果として、サービス停止時間は数十秒程度に抑えつつ、PostgreSQL 17への移行を実現できました。 この記事では、論理レプリケーションを用いたメジャーバージョン間のデータ移行手順を紹介します。

論理レプリケーションとは

論理レプリケーションは、公式のドキュメントには以下のように説明されています。

論理レプリケーションは、レプリケーションアイデンティティに基づき、データオブジェクトと、それに対する変更を複製する手法です。

といってもわかりにくいと思いますので、なるべく噛み砕いて説明します。

レプリケーションとは

レプリケーションの目的はさまざまですが、例えば以下のような用途があります。

  • 冗長化:障害が起きても別のサーバーに切り替えてサービスを継続できる(高可用性)
  • 負荷分散:読み取り処理を複製したサーバーに分散して性能を向上させる
  • 移行やバックアップ:新しい環境へのスムーズなデータ移行

シンプルに言うと、「データを常に最新に保ったまま、別の場所にも置いておく」という考え方です。 PostgreSQLで利用できるレプリケーションの方法としては「ストリーミングレプリケーション」と「論理レプリケーション」があります。

ストリーミングレプリケーションの概要

「ストリーミングレプリケーション」は、データの更新情報をリアルタイムに複製する仕組みです。

仕組みのイメージ

  • PostgreSQLは更新情報をWAL(Write Ahead Log)というログファイルに記録する

    • WALの詳細な説明は省略しますが、簡単にいうとデータファイルのどこをどう書き換えたかを記録した変更ログ。人間が理解できる内容ではない
      • 例:「テーブルのページ123の〇〇という位置を△△というバイト列に更新」のようなもの
  • ストリーミングレプリケーションは、このWALをほぼリアルタイムで他のサーバー(レプリカ)に送り、適用する

  • 結果として、レプリカ側は常にほぼ最新状態を保持する

    ストリーミングレプリケーションのデータ同期イメージ

    図1 ストリーミングレプリケーションのデータ同期イメージ

特徴

  • 物理レプリケーションの一種
  • データファイルやページ単位で、そのままバイナリとして複製する。レプリカはプライマリと同じバージョン・同じ構造を持つ必要がある
  • 通常は「読み取り専用」として使われ、書き込みはできない

利点と制約

  • 利点:高速で信頼性が高く、本番環境の冗長化に向いている
  • 制約:スキーマの自由度が低く、異なるバージョンや構造での複製には不向き

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

「論理レプリケーション」は、ストリーミングレプリケーションとは異なり、データベースの更新を「論理的な単位」で複製します。

仕組みのイメージ

  • 「このテーブルにINSERTされた」、「この行がUPDATEされた」というように、行単位の変更を論理的に伝える

  • 受信側では、その変更を実行してデータを反映する

  • PostgreSQL上では、パブリッシャー(送信元)とサブスクライバー(受信先)という役割で構成される

    論理レプリケーションのデータ同期イメージ

    図2 論理レプリケーションのデータ同期イメージ

特徴

  • ストリーミングレプリケーションと違い、データを行ベース(論理)で送るため、テーブル単位で複製対象を選択できる
    • データベース全体も対象にできる
  • 異なるPostgreSQLバージョン間でもレプリケーション可能
  • 複製側でも書き込み可能(双方向構成も可能)

利点と制約

  • 利点:部分的なデータのコピーやバージョンを跨いだ移行が可能。ダウンタイム短縮にも利用できる
  • 制約:DDL(テーブル構造変更)のレプリケーションは基本的に非対応。大量書き込みや特定データ型は注意が必要

例えると……

  • ストリーミングレプリケーション

    「原本のページレイアウトそのままに、コピー機で丸ごと複写する」……内容・形式どちらも同じになるが、異なるフォーマットへの変換はできない。

  • 論理レプリケーション

    「文章の内容だけを書き写す」……用紙や書式を変えても再現できるが、図や罫線は別途用意する必要がある。

なぜ論理レプリケーションを採用したか

「はじめに」で説明したとおり、MattermostのデータベースとしてPostgreSQLを採用しており、メジャーバージョンアップ+データ移行時のサービスの停止時間を極力減らしたいと考えました。 実績のある私たちの方法では、pg_dumpとデータインポートによるデータ移行になります。 しかし、Mattermostは長年運用しているため、pg_dumpにかかる時間や、新しいデータベースへのインサートを考慮すると作業時間が長引く可能性がありました。その間、多くのユーザーが利用するMattermostを停止しないといけません。

pg_upgradeでのバージョンアップも検討しましたが、現在のPostgreSQL 12は社内で提供されているOpenStack Troveのデータベースインスタンスの管理下にあるため実施が困難でした。実現するためには、一度pg_dumpとデータインポートによるデータ移行をする必要があり、非効率だと考えました。

そこで、メジャーバージョンアップとサービスの停止時間の減少を両立できる論理レプリケーションを採用することになりました。

pg_dumpとデータインポート、pg_upgradeなどのデータの移行と比較し、論理レプリケーションを採用することで、以下のように停止時間を短縮することが可能になります。

移行方式停止時間の目安メリットデメリット
pg_dumpとデータインポート数十分~数時間確実、安全、どの環境でも可データ量依存で時間がかかる、サービス停止時間が長い
pg_upgrade数分~数十分高速OpenStack Trove管理下では困難、アップグレード中はサービスは停止される
論理レプリケーション数秒~数分事前同期でサービス停止時間を短縮可、同期中もサービス継続可能設定がやや複雑、同期データには制約あり

論理レプリケーションの適用方法

今回のデータ移行の対象となる2つのインスタンスの情報は以下となります。 パブリッシャー側のインスタンスはOpenStack Troveで提供されるデータベースインスタンスです。

  • パブリッシャー側:PostgreSQL 12.7

    • OS:Ubuntu 16.04 LTS
    • IP:192.168.1.1
    • CPU:4コア
    • RAM:16 GB
    • Disk:30 GB
  • サブスクライバー側:PostgreSQL 17.6

    • OS:Ubuntu 24.04 LTS
    • IP:192.168.1.2
    • CPU:4コア
    • RAM:16 GB
    • Disk:30 GB

以降、PostgreSQL 12を「PG12」、PostgreSQL 17を「PG17」と略して表記します。

Mattermostは現在、パブリッシャー側のPG12に接続して運用しています。 論理レプリケーションの準備、データの同期を行い、Mattermostの接続先をPG17に切り替えるという流れで作業します。 全体的な作業のイメージは以下の図のようになります。

論理レプリケーションによるPG12→17へのバージョンアップの流れ

図3 論理レプリケーションによるPG12→PG17へのバージョンアップの流れ

論理レプリケーションの準備

データベーススキーマのコピー

パブリッシャー側のデータベースのスキーマをサブスクライバー側にコピーし、その後、データベースの全テーブルを対象に論理レプリケーションを設定します。

  • パブリッシャー側のmattermostデータベースのpg_dumpをスキーマオンリーで取得
    Terminal window
    # pg_dump -h 192.168.1.1 -U <ユーザー名> mattermost --schema-only -f <ファイル名>
  • サブスクライバー側でスキーマオンリーのpg_dumpデータをインポートする
    Terminal window
    # sudo -u postgres psql -d mattermost < <ファイル名>

パブリッシャー側の設定

  • pg_hba.confに以下を追記

    host replication postgres 192.168.1.0/24 md5
    • ここではpostgresユーザーに他ホストとのレプリケーションアクセス権限を付与している
  • データベースに接続し以下の論理レプリケーションに関する設定を変更

    postgres=# ALTER SYSTEM SET wal_level = logical;
    ALTER SYSTEM
    postgres=# ALTER SYSTEM SET max_wal_senders = 10;
    ALTER SYSTEM
  • 変更後はpostgresqlサービスの再起動を実施

    Terminal window
    # systemctl restart postgresql
  • パブリケーションの作成

    • mattermostデータベースにパブリケーションを作成する(必ず同期対象のデータベースに作成する)
      mattermost=# CREATE PUBLICATION mattermost FOR ALL TABLES;
      CREATE PUBLICATION

サブスクライバー側の設定

  • データベースに接続し以下の論理レプリケーションに関する設定を変更する
    postgres=# ALTER SYSTEM SET wal_level = logical;
    ALTER SYSTEM
  • 変更後はpostgresqlサービスの再起動を実施
    Terminal window
    # systemctl restart postgresql
  • 再度データベースに接続し、サブスクリプションを作成する。作成後に自動的に同期が開始される
    mattermost=# CREATE SUBSCRIPTION mattermost
    CONNECTION 'host=192.168.1.1 port=5432 user=postgres password=<接続ユーザーのパスワード> dbname=mattermost'
    PUBLICATION mattermost;
    NOTICE: created replication slot "mattermost" on publisher
    CREATE SUBSCRIPTION

初期同期+差分同期

論理レプリケーションを設定すると、まず初期同期が行われ、その後差分同期に移ります。同期状況を確認するには、パブリッシャー側とサブスクライバー側で以下のビューを参照します。

  • パブリッシャー側

    mattermost=# SELECT pid, application_name, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, pg_current_wal_lsn() AS current_lsn FROM pg_stat_replication;
    pid | application_name | state | sent_lsn | write_lsn | flush_lsn | replay_lsn | current_lsn
    -------+-----------------------------------------+-----------+------------+------------+------------ +------------+-------------
    25017 | mattermost | streaming | 6/59DA4D28 | 6/59DA4D28 | 6/59DA4D28 | 6/59DA4D28 | 6/59DA4D28
    25018 | pg_18370_sync_17766_7569140716182775401 | startup | | | | | 6/59DA4D28
    25121 | pg_18370_sync_17808_7569140716182775401 | startup | | | | | 6/59DA4D28
    (3 rows)

    確認ポイント

    • application_name:サブスクライバー名や同期ワーカー名
    • state:
      • startup → 初期同期中
        • 初期同期が完了すると、このプロセスは終了される
      • streaming → 差分同期中(正常)
    • sent_lsnとcurrent_lsnが近ければ送信側のWALはほぼ追いついている
  • サブスクライバー側

    mattermost=# SELECT subid, subname, worker_type, pid, received_lsn, latest_end_lsn, latest_end_time FROM pg_stat_subscription;
    subid | subname | worker_type | pid | received_lsn | latest_end_lsn | latest_end_time
    -------+------------+-----------------------+--------+--------------+----------------+-------------------------------
    18370 | mattermost | table synchronization | 246237 | | | 2025-11-10 15:31:36. 666178+09
    18370 | mattermost | table synchronization | 246223 | | | 2025-11-10 15:31:32. 084409+09
    18370 | mattermost | apply | 246222 | 6/59D7A2D8 | 6/59D7A2D8 | 2025-11-10 15:31:36. 749378+09
    (3 rows)

    確認ポイント

    • worker_type:
      • table synchronization → 初期同期中
        • 初期同期が完了すると、このプロセスは終了される
      • apply → 差分適用中(正常)
    • received_lsnとlatest_end_lsnが更新されていれば、データが適用されている

以下のような状態になっていれば、遅延なく差分同期が追従できていると判断し、最終同期に移ります。

  • 初期同期プロセスが終了している
  • パブリッシャー側:sent_lsnとcurrent_lsnが同じ値
  • サブスクライバー側:received_lsnとlatest_end_lsnが同じ値

最終同期+切り替え

Mattermostを一度停止します。私たちの環境ではMattermost停止後、残りの差分が適用され、数秒~数十秒で完全同期が完了しました。

  • パブリッシャー側

    mattermost=# SELECT pid, application_name, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, pg_current_wal_lsn() AS current_lsn FROM pg_stat_replication;
    pid | application_name | state | sent_lsn | write_lsn | flush_lsn | replay_lsn | current_lsn
    -------+------------------+-----------+------------+------------+------------+------------+-------------
    25017 | mattermost | streaming | 6/62513EB8 | 6/62513EB8 | 6/62513EB8 | 6/62513EB8 | 6/62513EB8
    (1 row)
  • サブスクライバー側

    mattermost=# SELECT subid, subname, worker_type, pid, received_lsn, latest_end_lsn, latest_end_time FROM pg_stat_subscription;
    subid | subname | worker_type | pid | received_lsn | latest_end_lsn | latest_end_time
    -------+------------+-------------+--------+--------------+----------------+-------------------------------
    18370 | mattermost | apply | 246222 | 6/62513EB8 | 6/62513EB8 | 2025-11-12 12:08:43.616102+09
    (1 row)

このように、パブリッシャーとサブスクライバーの各LSN(Log Sequence Number)の値が同じ値になっていれば完全同期が完了していると判断できます。 完全同期を確認したら、次にMattermostの参照するデータベースを切り替えます。

Mattermostが参照するデータベースを新しいPG17に切り替えます。私たちの環境ではMattermostをKubernetes上で運用しているため、新しいデータベース接続情報を設定したmanifestでPodを再デプロイするだけで切り替えが完了します。

その後、完全同期が確認されたサブスクライバー側でサブスクリプション設定を削除します。

mattermost=# DROP SUBSCRIPTION mattermost;
NOTICE: dropped replication slot "mattermost" on publisher
DROP SUBSCRIPTION

これにてメジャーバージョンアップとデータ移行が完了し、PG17でサービスを開始できました。

おわりに

PostgreSQLのメジャーバージョンアップは、単なるバージョン切り替えではなく、サービスの可用性に直結する重要な作業です。

従来のpg_dumpとデータインポートは確実な方法ですが、データ量が多い場合は長時間の停止が避けられません。今回採用した論理レプリケーションは、事前同期によって停止時間を数十秒程度に抑えられ、利用者への影響を最小限に抑えることができました。 論理レプリケーションは、設定や運用に工夫が必要ですが、停止時間を短縮できるメリットは非常に大きく、今後のメジャーバージョンアップでも有力な選択肢になると考えています。

この記事が、同様の課題を抱える方々の参考になれば幸いです。


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。