第3回「ロジカルレプリケーション」

本記事ではPostgreSQL 10の新機能のひとつである「ロジカルレプリケーション」について紹介します。

PostgreSQLでは、バージョン9.0よりストリーミングレプリケーション(Streaming Replication)機能を利用することができます。そして、バージョン10からはロジカルレプリケーションが利用可能になり、PostgreSQLでは2つの種類のレプリケーション機能が利用できるようになります。これらの2種類のレプリケーション機能にはどのような違いがあるのでしょうか。また、ロジカルレプリケーションを使いこなすためにはどうすれば良いのか、本コラムでは「ロジカルレプリケーション」について、従来のストリーミングレプリケーションの違いや、ロジカルレプリケーションの基本的な使い方、また、使いこなすためのポイントを紹介します。

ロジカルレプリケーションとストリーミングレプリケーション

PostgreSQLではロジカルレプリケーションとストリーミングレプリケーションの2種類のレプリケーション機能を利用することができます。バージョン9.0で導入されたストリーミングレプリケーションは、一般に「物理レプリケーション」と呼ばれる機能で、データ変更時に生成されるトランザクションログ(WAL)をスタンバイサーバーへ送信します。WALには、データの物理的な変更情報(○○ファイルの△△オフセットを□□に変更する、など)が格納されているので、スタンバイサーバーにてWALを適用することで、バイナリレベルで全く同じデータベースを複製することができます。
一方、ロジカルレプリケーションは、一般に「論理レプリケーション」と呼ばれる機能で、データの論理的な変更情報(○○テーブルのUSERID=100の行を□□に変更する、など)を下流のサーバーに送信することで、データを複製することができます。主な違いや特徴を表1にまとめます。

表1
特徴 ストリーミングレプリケーション
(物理レプリケーション)
ロジカルレプリケーション
(論理レプリケーション)
レプリケーション対象 全てのデータベース データベース単位、テーブル単位など柔軟に設定可能
利用用途 バックアップ、参照負荷分散 部分的なレプリケーション、バージョンアップ
何を伝搬するか トランザクションログ(WAL) 変更された行の情報
受信側での更新 不可能 可能
異なるバージョン間でのレプリケーション 不可能 可能
対応バージョン 9.0以降 10以降

データベース内で発生したテーブルやインデックスの変更情報は全てのWALに出力されており、ストリーミングレプリケーション、ロジカルレプリケーション共にWALを利用します。ストリーミングレプリケーションではWALをそのまま送信しますが、ロジカルレプリケーションでは、ロジカルデコーディング機能よってWALから論理的な変更情報を抽出し、独自のバイナリ形式に変換した上で、他のPostgreSQLサーバーへ送信します。

ロジカルレプリケーションは以下のような用途での活躍が期待できます。

  • データベース単位もしくは一部のテーブルのみを複製
  • メジャーバージョンアップ
  • 複数データベースの集約

ロジカルレプリケーションの制約

ロジカルレプリケーションには、いくつか制約があります。特に、レプリケーション対象となるオブジェクトや操作については、十分に理解して利用する必要があります。

レプリケーション対象外の操作
TRUNCATEコマンドやDDLはレプリケーションされません。
レプリケーション対象外のオブジェクト
シーケンス、ラージオブジェクトや通常のテーブル以外(インデックス、ビュー、マテリアライズド・ビューやパーティションテーブルの親テーブルなど)もレプリケーションされません。
双方向レプリケーションはできません
コミットされた変更情報のみ、コミット時に送信します
ロジカルレプリケーションでは、コミット時にコミットされた変更情報のみを受信サーバーへ送信し、ロールバックされた変更情報は送信されません*1。これは、送信量を削減できるメリットもありますが、同期レプリケーションのような、送信サーバーが受信サーバーでの受信確認を待つような利用用途では、レイテンシが大きくなりやすいというデメリットにもなります。
  • *1: ストリーミングレプリケーションでは、トランザクションのコミット、ロールバックに関わらず、すべての変更情報がトランザクションの途中でも送信されます

ロジカルレプリケーションの使い方

ロジカルレプリケーションは、PostgreSQLのデフォルト設定では利用できないため、ロジカルレプリケーションを利用するためには、送信側でwal_level パラメーターをlogicalに設定する必要があります。その他、関連する設定パラメーターについては公式マニュアル*2をご参照ください。

PUBLICATION/SUBSCRIPTIONの作成(ロジカルレプリケーションの開始)

PostgreSQLのロジカルレプリケーションは、PUBLICATION/SUBSCRIPTIONと呼ばれるオブジェクトを利用して制御します。PUBLICATIONはデータを送信するサーバー(publisherと呼ばれます)に作成され、それを購読(Subscribe)するSUBSCRIPTIONがデータを受信するサーバー(subscriberと呼ばれます)に作成されることで、publisherで発生したデータがsubscriberに伝播されます。

ロジカルレプリケーションは、レプリケーション開始時にpublisherからの初期データのコピーから始まり*3、初期データのコピーが完了後、リアルタイムで変更情報がsubscriberに送信されます。

PUBLICATIONはCREATE PUBLICATIONコマンドを利用して作成し、pg_publication_tablesシステムビューにて作成したPUBLICATIONを確認することができます。

-- レプリケーションするテーブルの作成
=# CREATE TABLE table_a (c int primary key);
-- 10件のデータを挿入
=# INSERT INTO table_a SELECT generate_series(1,10);
INSERT 10
-- PUBLICATIONの作成
=# CREATE PUBLICATION test_pub FOR TABLE table_a WITH (publish = 'insert');
-- 作成したPUBLICATIONの確認
=# SELECT * FROM pg_publication_tables;
 pubname  | schemaname | tablename
----------+------------+-----------
 test_pub | public     | table_a
(1 row)

PUBLICATIONの作成では、レプリケーション対象のテーブル(複数指定可)や、レプリケーション対象の操作(insert, update, deleteから複数選択可)を指定します。

SUBSCRIPTIONはCREATE SUBSCRIPTIONコマンドを利用して作成します。CREATE SUBSCRIPTIONには、接続先情報(CONNECTION句)や、購読するPUBLICATION(PUBLICATION句)を指定します。CREATE SUBSCRIPTIONコマンド実行時に、ロジカルレプリケーションが開始されます。
PostgreSQL10のロジカルレプリケーションでは、DDLは複製されないため、Subscriber側でもテーブルを作成する必要があります。Publisher側とSubscriber側でテーブル定義を一致させる必要はなく、Subscriber側のテーブルの列がPublisher側のテーブルの列を包含していれば、ロジカルレプリケーションを利用できます。

-- テーブルの作成
=# CREATE TABLE table_a (c int primary key);
-- SUBSCRIPTIONの作成。本コマンド実行時よりロジカルレプリケーションが開始される
=# CREATE SUBSCRIPTION test_sub CONNECTION 'host=... ' PUBLICATION test_pub;
-- テーブルデータを確認。初期データが複製されている。
=# SELECT * FROM table_a;
  c
----
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
(10 rows)
-- 作成したSUBSCRIPTIONを確認
=# SELECT * FROM pg_subscrtions;
  subdbid | subname  | subowner | subenabled |        subconninfo        | subslotname | subsynccommit | subpublications
----------+----------+----------+------------+---------------------------+-------------+---------------+-----------------
    13237 | test_sub |       10 | t          | port=5550 dbname=postgres | test_sub    | off           | {test_pub}
(1 row)

UPDATE、DELETEを複製するためには、通常、テーブルに主キーが必要ですが、主キーがないテーブルでもALTER TABLE REPLICA IDENTIRYをUSING INDEX(主キーインデックス以外のインデックスを指定)または、FULL(すべての列をキーにする)に変更することで、UPDATE、DELETEの複製が可能です。

ロジカルレプリケーションの一時停止と再開

ALTER SUBSCRIPTIONコマンドを利用することで、ロジカルレプリケーションの一時停止、再開をすることができます。一時停止中はPublisherからのデータ受信が停止され、再開後に、前回停止した時点からレプリケーションが再開されます。

-- ロジカルレプリケーションの一時停止
=# ALTER SUBSCRIPTION test_sub DISABLE;
-- ロジカルレプリケーションの再開
=# ALTER SUBSCRIPTION test_sub ENABLE;

ロジカルレプリケーションの終了

ロジカルレプリケーションを終了するには、DROP SUBSCRIPTIONコマンドを利用して、SUBSCRIPTIONを削除します。PUBLICATIONは一つ以上のSUBSCRIPTIONから購読されていると削除できないため、SUBSCRIPTIONから先に削除する必要があることに注意してください。

-- ロジカルレプリケーションの終了(Subscriber側)
=# DROP SUBSCRIPTION test_sub;
-- ロジカルレプリケーションの終了(Publisher側)
=# DROP PUBLICATION test_pub;

ロジカルレプリケーションの衝突

ロジカルレプリケーションを運用する上で様々な点を考慮する必要がありますが、今回はそのうちの一つであるレプリケーション衝突について紹介します。レプリケーション衝突は、受信側で発生する問題で、上流から流れてくる変更情報と受信側で実行されるSQL処理が衝突する問題です。ロジカルレプリケーションの場合、Subscriber側でも更新SQLを実行することが可能ですので、例えば以下のような衝突が発生する可能性があります。

レプリケーション衝突が発生した場合、変更情報を適用するプロセスはエラー終了し、レプリケーションが停止します。そして、一定時間後、再度適用を開始します。PostgreSQL 10のロジカルレプリケーションでは、レプリケーション衝突を検知する専用機能は搭載していないため、データベース管理者が手動で解消する必要があります。Subscriberのテーブルにトリガを仕掛けることも可能ですが、Subscriber側でBEFORE UPDATEトリガは動作しない*4ことに注意してください。

また、レプリケーション衝突の解消には、pg_replication_origin_advance()関数を利用することが可能です。本関数を利用することで、衝突の原因となっている変更情報をスキップすることが可能です。しかし、手順が複雑である、かつ、ロジカルレプリケーションの仕組みを理解していないと必要な変更情報の適用までスキップしてしまう可能性もあるため、推奨できません。現時点では、データベースやテーブルを分けるなどをして、データの衝突が起きないようにレプリケーション設計を行い、利用することを推奨します。

  • *4: 将来のリリースで変更される可能性があります。

まとめ

今回はPostgreSQLのロジカルレプリケーションについて解説をしました。PostgreSQL 10で導入されたばかりということもあり、様々な制約や、実運用上で多少不便な点がありますが、その柔軟性の高さは多くの可能性を秘めており、今後様々なユースケースの活躍が期待できる機能です。また、今年リリース予定のPostgreSQL 11では、TRUNCATEコマンドや2層コミットの情報がレプリケーション対象となる機能が追加される予定です。そして、開発中のPostgreSQL 12では、ロジカル・"ストリーミング"・レプリケーション*5機能も提案されており、今後の進化にも期待したいです。

  • *5: ストリーミングレプリケーションのように変更情報が逐次送信される機能

(澤田 雅彦)



第3回「ロジカルレプリケーション」