論理レプリケーションにおけるコンフリクトの対処方法(ALTER SUBSCRIPTION ... SKIPコマンド) - PostgreSQL 15でコミットされた機能の先行紹介:技術者Blog
PostgreSQLインサイド

大墨 昂道

富士通株式会社
ソフトウェアプロダクト事業本部 データマネジメント事業部

はじめに

大墨
私の前回のブログ「論理レプリケーションにおけるコンフリクトの対処方法」では、論理レプリケーションのコンフリクトの説明と、この問題に対する対処方法として、pg_replication_origin_advance関数を使用した手法について書きました。このブログでは、別の対処方法として、 PostgreSQL 15でコミットされた新機能であるALTER SUBSCRIPTION ... SKIPコマンドを使用する方法について説明します。

背景

大墨
私の前回のブログでは、論理レプリケーションのコンフリクトとは何か、どのようにして起こるのか、そしてPostgreSQL 15でコミュニティーが開発した新しい機能が、解決にどのように役立つのかを説明しました。それと並行して、コンフリクトの対処について別の議論が行われました。OSSコミュニティーはプロジェクトの達成に積極的に取り組み、PostgreSQLのコードベースにALTER SUBSCRIPTION ... SKIPコマンドを実装しました。

参照先のドキュメントで説明されているように、この機能はリモートトランザクションのすべての変更適用をスキップするように動作します。これはpg_replication_origin_advance関数の機能に似ているように見えるかもしれません。そこで、このブログではALTER SUBSCRIPTION ... SKIPコマンドに焦点を当てて、相違点を明らかにしたいと思います。
私の前回のブログでは、他の背景や関連する新機能について説明しました。まだ読まれていないのなら、まずそちらのブログを読むことをお勧めします。ALTER SUBSCRIPTION ... SKIPコマンドは、私の前回のブログでpg_replication_origin_advance関数に使用したものと同様のシナリオで説明していきます。

  • 免責事項
    PostgreSQL 15の公式リリース前のため、この機能はまだリリースされておらず、コミュニティーはこのブログに関連する設計を変更したり、完全に元に戻したりする可能性があることをご承知おきください。

目次

ALTER SUBSCRIPTION ... SKIPコマンド

大墨
このコマンドの目的は、サブスクライバーにおいて、指定されたLSNでエラー終了し、コンフリクトが発生しているトランザクションをスキップすることです。ユーザーは、 SKIPオプション('lsn' skip_option)によってfinish LSNを指定することで、失敗したトランザクションを指定できます。適用ワーカーはパブリッシャーから変更を受け取り、ノード上でそれをスキップするかどうかを判断します。このコマンドの入力パラメーターには、エラーコンテキストに出力されたfinish LSNをそのまま指定できます。そのため、pg_replication_origin_advance関数の引数とは異なり、サーバーログでエクスポートされたLSNの編集は必要ありません。その意味で、ALTER SUBSCRIPTION ... SKIPコマンドを使用するほうがシンプルです。では以下のデモを見てみましょう。

トランザクションをスキップすることによるコンフリクトの解決(ALTER SUBSCRIPTION ... SKIPコマンドを使う場合)

大墨
サブスクライバー側をコンフリクトの状態にしてから、ALTER SUBSCRIPTION ... SKIPコマンドによって解決するシナリオを見てみましょう。

  1. パブリッシャー側:1つのテーブル(tab)とパブリケーション(mypub)を作成

postgres=# CREATE TABLE tab (id integer);
CREATE TABLE
postgres=# INSERT INTO tab VALUES (5);
INSERT 0 1
postgres=# CREATE PUBLICATION mypub FOR TABLE tab;
CREATE PUBLICATION

最初に、初期テーブル同期を行うために1レコードを挿入しています。

  1. サブスクライバー側:一意性制約を持つテーブル(tab)とサブスクリプション(mysub)を作成

postgres=# CREATE TABLE tab (id integer UNIQUE);
CREATE TABLE
postgres=# CREATE SUBSCRIPTION mysub CONNECTION '…' PUBLICATION mypub WITH (disable_on_error = true);
NOTICE:  created replication slot "mysub" on publisher
CREATE SUBSCRIPTION

disable_on_errorオプションが有効なサブスクリプションを作成しました。このサブスクリプション定義により、バックグラウンドで初期テーブル同期が行われ、問題なく成功します。その結果、値5がサブスクライバーに挿入されます。

  1. パブリッシャー側:テーブルの同期後に3つのトランザクション(Txn1、Txn2、Txn3)を順次実行

postgres=# BEGIN; -- Txn1
BEGIN
postgres=*# INSERT INTO tab VALUES (1);
INSERT 0 1
postgres=*# COMMIT;
COMMIT
postgres=# BEGIN; -- Txn2
BEGIN
postgres=*# INSERT INTO tab VALUES (generate_series(2, 8));
INSERT 0 7
postgres=*# COMMIT;
COMMIT
postgres=# BEGIN; -- Txn3
BEGIN
postgres=*# INSERT INTO tab VALUES (9);
INSERT 0 1
postgres=*# COMMIT;
COMMIT
postgres=# SELECT * FROM tab;
 id
----
  5
  1
  2
  3
  4
  5
  6
  7
  8
  9
(10 rows)

パブリッシャー側では、これらのトランザクションを正常に実行できました。ただし、Txn2には初期テーブル同期時にすでに挿入されていたデータと重複するデータ(5)が含まれます。したがってサブスクライバー側では、これがテーブルの一意性制約に違反してエラーになります。

  1. サブスクライバー側:現在のステータスを確認

postgres=# SELECT subname, subenabled FROM pg_subscription;
 subname | subenabled
---------+------------
 mysub   | f
(1 row)

postgres=# SELECT * FROM tab;
 id
----
  5
  1
(2 rows)

では、サブスクライバーの現在のステータスを確認してみましょう。「disable_on_error」オプション の動作に従い、サブスクリプション(mysub)はエラーによって自動的に無効(f)になりました。また、Txn1(および初期テーブル同期によってレプリケートされたデータ)だけがサブスクライバーにレプリケートされています。ここでは、コンフリクトのためにTxn2とTxn3の結果は見られません。Txn3は、コンフリクトを解決した後で再生されます。

  1. サブスクライバー側のログ:このコンフリクトのエラーメッセージとdisable_on_errorオプションのログを確認

ERROR:  duplicate key value violates unique constraint "tab_id_key"
DETAIL:  Key (id)=(5) already exists.
CONTEXT:  processing remote data for replication origin "pg_16389" during "INSERT" for replication target relation "public.tab" in transaction 730 finished at 0/1566D10
LOG:  logical replication subscription "mysub" has been disabled due to an error

サブスクライバーのサーバーログで、ALTER SUBSCRIPTION ... SKIPコマンドの引数となるfinish LSN(0/1566D10)を取得できます。これを利用して、以下のようにTxn2をスキップします。

  1. サブスクライバー側:ALTER SUBSCRIPTION ... SKIPコマンドを実行してから、サブスクリプションを有効化

postgres=# ALTER SUBSCRIPTION mysub SKIP (lsn = '0/1566D10');
ALTER SUBSCRIPTION
postgres=# SELECT subname, subskiplsn, subenabled FROM pg_subscription;
 subname | subskiplsn | subenabled
---------+------------+------------
 mysub   | 0/1566D10  | f
(1 row)

postgres=# ALTER SUBSCRIPTION mysub ENABLE;
ALTER SUBSCRIPTION
postgres=# SELECT * FROM tab;
 id
----
  5
  1
  9
(3 rows)

postgres=# SELECT subname, subskiplsn, subenabled FROM pg_subscription;
 subname | subskiplsn | subenabled
---------+------------+------------
 mysub   | 0/0        | t
(1 row)

ここで、skip LSN(0/1566D10)を設定してから、サブスクリプションを再アクティブ化しました。すぐにTxn2のトランザクション全体がスキップされて、Txn2の値が無いことと、Txn3のレプリケートされた値が確認できました。これに伴い、変更のスキップに成功した後、システムカタログpg_subscriptionのsubskiplsn列に格納されていたskip LSN(0/1566D10)がクリアされました。

  1. サブスクライバー側のログ:正常終了したコマンドのログメッセージを確認

LOG:  start skipping logical replication transaction finished at 0/1566D10
CONTEXT:  processing remote data for replication origin "pg_16389" during "BEGIN" in transaction 730 finished at 0/1566D10
LOG:  done skipping logical replication transaction finished at 0/1566D10
CONTEXT:  processing remote data for replication origin "pg_16389" during "COMMIT" in transaction 730 finished at 0/1566D10

サーバーログからもコマンドの正常終了を確認できます。

ALTER SUBSCRIPTION ... SKIPコマンドの高度な保護

大墨
この機能には、コンフリクト処理に固有の内部的な保護と検査の機構があります。その結果、コンフリクト時に失敗していないトランザクションをスキップするリスクがなくなります。
これを、私の前回のブログ「論理レプリケーションにおけるコンフリクトの対処方法」で説明したpg_replication_origin_advance関数の誤った使い方の例と比較してみてください。そこで私は、この関数がコンフリクトと関係のない成功したトランザクションをスキップすることがある点に触れました。しかし、このALTER SUBSCRIPTION ... SKIPコマンドでは、それは起きません。これは、以下の内部チェックにより実現されます。

まず、指定されたLSNは、サブスクライバー上でデータが既にレプリケートされた場所を指す起点LSNよりも、大きな値である必要があります。

さらに、この機能では、引数のfinish LSNと、サブスクライバーがパブリッシャーから取得する最初のトランザクションのfinish LSNが完全に一致する必要があります。そうでなければ、(サブスクリプションを有効にした後)PostgreSQLは失敗したトランザクションを再度適用しようとし、同じコンフリクトを即座に引き起こします。PostgreSQL 15では、このような失敗は起こりません。エラーメッセージ中にfinish LSNが出力される改善が行われているため、ユーザーは正確なfinish LSNを取得でき、それを指定するだけでよいからです。

また、コンフリクトのないskip LSNを設定し、トランザクションの適用に成功すると、警告メッセージの出力と共にskip LSNがクリアされるだけです。実際にはスキップされません。この動作は、受信するトランザクションが問題のないものであることが判明し、ユーザーが誤ってLSNを設定した可能性があるため、理にかなっています。コンフリクトが発生していない場合に、ユーザーが誤ってLSNを設定しても対処すべきことはありません。新たなコンフリクトが生じた際に、この手段を採用すると決めるなどの適切なタイミングで再度skip LSNを設定すればよいです。
これらの内部処理の詳細に興味を持たれた場合は、以下のコミットログを参照してください。

まとめ

大墨
論理レプリケーションのコンフリクトに関して、コミュニティーはこの新しい便利な方法を実現するために多くの時間と労力を費やしてきました。よりシンプルで安全な方法を享受する選択があることは素晴らしいことです。
加えて、既にコミュニティー内において様々な角度から、この機能を強化するためのいくつかのアイデアが提案されています。例えば、リレーションやスキップするコマンド(INSERT、DELETE、・・・)を指定するなど、他のタイプのskip_optionをALTER SUBSCRIPTION ... SKIPコマンドに追加するというアイデアがあります。さらに、finish LSNのようなエラー関連情報をシステムカタログに格納したり、サブスクライバーがスキップしたデータをサーバーログや一部のテーブルに記録したりするアイデアも提案されています。
これらの機能強化はどれも魅力的であり、PostgreSQLの今後のバージョンでどのような新機能が追加されるのか楽しみです。

2022年7月22日公開

オンデマンド(動画)セミナー

    • PostgreSQLに関連するセミナー動画を公開中。いつでもセミナーをご覧いただけます。
      • 【事例解説】運送業務改革をもたらす次世代の運送業界向けDXプラットフォームの構築
      • ハイブリッドクラウドに最適なOSSベースのデータベースご紹介

本コンテンツに関するお問い合わせ

お電話でのお問い合わせ

Webでのお問い合わせ

当社はセキュリティ保護の観点からSSL技術を使用しております。

ページの先頭へ