Conflicts

EDB Postgres Distributedは、アクティブ/アクティブまたはマルチマスターのDBMSです。非同期で使用すると、複数の異なるノードから同じ行または関連する行に書き込むと、標準データ型を使用しているときにデータの競合が発生する可能性があります。

競合はエラーではありません。ほとんどの場合、それらはPGDが発生時に検出および解決できるイベントです。それらを解決することは、アプリケーションの性質とデータの意味によって異なるため、PGDがアプリケーションに競合を解決する方法の幅広い選択肢を提供することが重要です。

デフォルトでは、競合は行レベルで解決されます。 2つのノードからの変更が競合する場合、PGDはローカルまたはリモートのタプルを選択し、もう一方を破棄します。たとえば、2つの競合する変更のコミットタイムスタンプを比較し、新しい方を保持する場合があります。このアプローチにより、すべてのノードが同じ結果に収束し、クラスター全体でコミットオーダーのようなセマンティクスが確立されます。

列レベルの競合解決の有効化と無効化 で説明されているように、競合処理は構成可能です。 PGDは、 ストリームトリガーリファレンス

で説明されている競合トリガーを使用して、競合を検出し、テーブルごとに異なる方法で処理できます。

CLCD で説明されているように、列レベルの競合検出と解決はPGDで使用できます。

デフォルトでは、すべての競合はbdr.conflict_history に記録されます。競合が発生する可能性がある場合、表の所有者は競合を監視して回避方法を分析するか、アプリケーションタスクとして定期的に処理する計画を立てる必要があります。

LiveCompareによるデータ検証 ツールを使用して、発散を定期的にスキャンすることもできます。

一部のクラスタリングシステムは、分散ロックメカニズムを使用して、データへの同時アクセスを防ぎます。これらは、サーバーが互いに非常に近い場合は合理的に実行できますが、許容可能なパフォーマンスのために非常に低いレイテンシーが重要である地理的に分散したアプリケーションをサポートできません。

分散ロックは本質的に悲観的なアプローチです。 PGDは楽観的なアプローチを提唱しています。これは、可能な場合は競合を回避しますが、特定の種類の競合の発生を許可し、発生したときに解決することです。

競合はどのように発生するか

ノード間の競合は、関連するすべてのトランザクションが同じノードで同時に発生する場合には発生しないイベントのシーケンスの結果として発生します。ノードはトランザクションがコミットした後にのみ変更を交換するため、各トランザクションはコミットしたノードで個別に有効です。他の競合する作業を同時に行った別のノードに適用された場合、無効です。

PGDレプリケーションは本質的に他のノードでトランザクションをリプレイするため、適用中のトランザクションと受信ノードでコミットされたトランザクションの間に競合がある場合、リプレイ操作が失敗する可能性があります。

Postgresにはそれを防ぐためのトランザクション間通信メカニズムがあるため、すべてのトランザクションが単一のノードで実行されている場合、ほとんどの競合は発生しません。これらのメカニズムの例は、 UNIQUE インデックス、 SEQUENCE 操作、行と関係のロック、およびSERIALIZABLE 依存関係の追跡です。これらのメカニズムはすべて、進行中のトランザクション間で通信して、望ましくない同時実行の問題を防ぐ方法です。

PGDには、分散トランザクションマネージャーまたはロックマネージャーがありません。これが、レイテンシーとネットワークパーティションでうまく機能する理由の一部です。その結果、デフォルトの遅延レプリケーションを使用すると、異なるノード上のトランザクションは互いに完全に独立して実行されます。ノード間の独立性が低いと競合を完全に回避できます。そのため、PGDはこれが重要な場合にEager Replicationも提供します。

紛争の種類

PRIMARY KEYまたはUNIQUEの競合

最も一般的な競合は行の競合です。2つの操作が、単一のノードではできない方法で同じキーを持つ行に影響します。 PGDはそれらのほとんどを検出でき、 update_if_newer 競合リゾルバーを適用できます。

行の競合には次のものがあります。

  • INSERT 対INSERT

  • UPDATE 対UPDATE

  • UPDATE 対DELETE

  • INSERT 対UPDATE

  • INSERT 対DELETE

  • DELETE 対DELETE

ビューbdr.node_conflict_resolvers は、既知のすべての競合タイプに対して競合解決が現在どのように構成されているかに関する情報を提供します。

INSERT / INSERTの競合

最も一般的な競合であるINSERT /INSERT は、2つの異なるノードでのINSERT 操作が同じPRIMARY KEY 値(または、 PRIMARY KEY が存在しない場合、単一のUNIQUE 制約と同じ値)を持つタプルを作成する場合に発生します。

PGDは、発信元ノードのタイムスタンプに従って、2つのうちの最後に挿入されたタプルを保持することにより、この状況を処理します。 (ユーザー定義の競合ハンドラーは、この動作をオーバーライドできます。)

この競合はinsert_exists 競合タイプを生成します。これは、デフォルトでは、コミット時間に基づいて新しい行を選択し、その行のみを保持することで解決されます(update_if_newer リゾルバー)。他のリゾルバーを構成できます。詳細は 列レベルの競合解決の有効化と無効化 を参照。

この競合の種類を解決するには、列レベルの競合解決とユーザー定義の競合トリガーを使用することもできます。

PGDグローバルシーケンス を使用すると、このタイプの競合を効果的に解消できます。

複数のUNIQUE制約に違反するINSERT操作

INSERT /INSERT の競合は、複数のUNIQUE 制約に違反する可能性があります。そのうちの1つがPRIMARY KEY である可能性があります。新しい行が複数のUNIQUE 制約に違反し、その結果他の複数の行と競合が発生する場合、レプリケーション変更を適用するとmultiple_unique_conflicts 競合が発生します。

このような競合が発生した場合、レプリケーションを続行するには、いくつかの行を削除する必要があります。 multiple_unique_conflicts のリゾルバー設定に応じて、適用プロセスはエラーで終了するか、入力行をスキップするか、一部の行を削除します。削除により、正しいPRIMARY KEY で行を保持し、他の行を削除しようとします。

警告

この方法で競合する複数の行がある場合、競合解決の結果が挿入操作を続行する場合、一部のデータは常に削除されます。

競合トリガーを使用して、別の動作を定義することもできます。

UPDATE / UPDATEの競合

異なるノードでの2つの同時UPDATE 操作が同じタプルを変更しますが、そのPRIMARY KEY は変更しない場合、再生時にUPDATE /UPDATE の競合が発生する可能性があります。

これらは、構成と状況に基づいてさまざまな種類の競合を生成できます。テーブルが

行バージョンの競合検出

で構成されている場合、元の(キー)行がローカル行と比較されます。それらが異なる場合、 update_differing 競合が生成されます。 オリジンの競合検出 を使用する場合 、行の原点がチェックされます。 (起点は、現在のローカル行の元のノードです。)それが変更された場合、 update_origin_change 競合が生成されます。他のすべての場合、 UPDATE は通常、競合を生成せずに適用されます。

これらの競合は両方とも、 INSERT / INSERTの競合 で説明されている insert_exists と同じ方法で解決されます。

PRIMARY KEYでのUPDATEの競合

PGDは現在、 PRIMARY KEY がUPDATE 操作によって変更された競合解決を実行できません。主キーを更新できますが、既存の値との競合が発生しないことを確認する必要があります。

主キーの更新に関する競合は 発散する競合 であり、手動介入が必要です。

Postgresで主キーを更新することは可能ですが、PostgresとPGDの両方に問題があります。

単純なスキーマは、以下を説明する例を提供します。

CREATE TABLE pktest (pk integer primary key, val integer);
INSERT INTO pktest VALUES (1,1);

主キー列を更新できるため、次のSQLは成功します。

UPDATE pktest SET pk=2 WHERE pk=1;

ただし、テーブルに複数の行があるとします。

INSERT INTO pktest VALUES (3,3);

一部の UPDATE 操作は成功します。

UPDATE pktest SET pk=4 WHERE pk=3;

SELECT * FROM pktest;
 pk | val
- ---+-----
  2 |   1
  4 |   3
(2 rows)

他の UPDATE 操作は、制約エラーで失敗します。

UPDATE pktest SET pk=4 WHERE pk=2;
ERROR:  duplicate key value violates unique constraint "pktest_pkey"
DETAIL:  Key (pk)=(4) already exists

したがって、主キーを更新するPostgresアプリケーションの場合、PGDがなくても、ランタイムエラーを回避するように注意してください。

PGDでは、 UPDATE操作が同時に複数の場所から許可されている場合、状況はより複雑になります。

これら2つの変更を同時に実行すると機能します。

node1: UPDATE pktest SET pk=pk+1 WHERE pk = 2;
node2: UPDATE pktest SET pk=pk+1 WHERE pk = 4;

SELECT * FROM pktest;
 pk | val
- ---+-----
  3 |   1
  5 |   3
(2 rows)

これらの次の2つの変更を同時に実行すると、両方の変更が受け入れられるため、発散エラーが発生します。ただし、他のノードに変更を適用すると、 update_missing 競合が発生します。

node1: UPDATE pktest SET pk=1 WHERE pk = 3;
node2: UPDATE pktest SET pk=2 WHERE pk = 3;

このシナリオでは、各ノードで異なるデータを残します。

  node1:
  SELECT * FROM pktest;
   pk | val
  - ---+-----
    1 |   1
    5 |   3
  (2 rows)

  node2:
  SELECT * FROM pktest;
   pk | val
  - ---+-----
    2 |   1
    5 |   3
  (2 rows)

:ref:`LiveCompareによるデータ検証 <LiveCompareによるデータ検証>`  を使用して、この状況を特定して解決できます。

同時競合は問題を引き起こします。これら2つの変更を同時に実行することは、解決するのは簡単ではありません。

node1: UPDATE pktest SET pk=6, val=8 WHERE pk = 5;
node2: UPDATE pktest SET pk=6, val=9 WHERE pk = 5;

両方の変更がローカルに適用されるため、ノード間で相違が発生します。ただし、ターゲットへの適用は、重複キー値違反エラーで両方のノードで失敗します。このエラーにより、レプリケーションが停止し、手動で解決する必要があります。

conflict_type update_pkey_exists をskip 、update 、またはupdate_if_newer に設定すると、この重複キー違反エラーを回避でき、レプリケーションは中断しません。このアプローチは、更新の性質によっては、依然として発散につながる可能性があります。

update_pkey_exists をupdate_if_newer に設定することで、同じ古いキーが同じ新しいキーによって同時に更新されている場合の発散を回避できます。ただし、特定の状況では、 update_if_newer でも発散が発生します。つまり、2つの異なる行が両方とも同じ新しい主キーに同時に更新される場合。

その結果、特にPGDでは、アプリケーションで主キーの UPDATE 操作を許可しないことを強くお勧めします。アプリケーションの一部が主キーを変更する場合、同時変更を避けるために、 Eager Replicationを使用してこれらの変更を行います。

警告

update_pkey_exists の競合の競合解決の結果が更新の場合、行の1つが常に削除されます。

複数のUNIQUE制約に違反するUPDATE操作

複数のUNIQUE制約に違反するINSERT操作 と同様に、着信 UPDATE が複数のUNIQUE

インデックス(またはPRIMARY KEY )に違反すると、PGDは multiple_unique_conflicts 競合を発生させます。

PGDは、据え置きの一意の制約をサポートしています。トランザクションがソースでコミットできる場合、競合が見つからない限り、ターゲットにクリーンに適用されます。ただし、遅延主キーをREPLICA IDENTITYとして使用することはできないため、ユースケースはすでにそのことと、複数の一意の制約の使用に関する警告によって制限されています。

UPDATE/DELETEの競合

あるノードは、別のノードが同時に削除する行を更新できます。この場合、再生時にUPDATE /DELETE の競合が発生する可能性があります。

削除された行がまだ検出可能な場合(削除された行はVACUUM によって削除されませんでした)、 update_recently_deleted の競合が生成されます。デフォルトでは、 UPDATE はスキップされますが、この解像度を構成できます。詳細は 列レベルの競合解決の有効化と無効化 を参照。

ローカルノードのレプリケーションが遅れている場合、データベースはUPDATE を受け取るまでに削除された行をクリーンアップできます。この場合、PGDは UPDATE /DELETE conflicts と INSERT / UPDATEの競合 を区別できません。 update_missing 競合を生成します。

DELETE とUPDATE の競合の別のタイプは、行がローカルで更新された後に来るDELETE です。この状況では、結果は使用される競合検出の種類によって異なります。デフォルトの

オリジンの競合検出 を使用する場合、競合は検出されず、 DELETE

が適用され、行が削除されます。 行バージョンの競合検出 を有効にすると、 delete_recently_updated の競合が生成されます。この競合タイプのデフォルトの解決策は、 DELETE を適用して行を削除することです。ただし、これを構成するか、競合トリガーで処理できます。

INSERT / UPDATEの競合

デフォルトの非同期操作モードを使用する場合、ノードは元のINSERT を受け取る前に行のUPDATE を受け取る場合があります。これは、3つ以上のノードがアクティブな場合にのみ発生します(

これが発生すると、 update_missing の競合が生成されます。デフォルトの競合リゾルバーはinsert_or_skip ですが、代わりにinsert_or_error またはskip を使用できます。 insert-or-actionを行うリゾルバーは、可能な場合(行全体を受け取ったとき)、最初にUPDATE からのデータに基づいて新しい行をINSERT しようとします。行の再構築を可能にするには、テーブルにREPLICA IDENTITY FULL が必要であるか、行にトーストデータが含まれていてはなりません。

トーストされたデータの詳細については、

TOASTサポート詳細 を参照してください。

INSERT/DELETEの競合

INSERT /UPDATE の競合と同様に、ノードは、まだINSERT を受け取っていない行でDELETE オペレーションを受け取る場合もあります。これも3つ以上のノードが設定されている場合にのみ可能です(

PGDは現在、この競合タイプを検出できません。 INSERT 操作は競合タイプを生成せず、 INSERT が適用されます。

DELETE 操作は常にdelete_missing 競合を生成します。デフォルトでは、操作をスキップすることで解決されます。

DELETE/DELETEの競合

DELETE /DELETE の競合は、2つの異なるノードが同じタプルを同時に削除すると発生します。

このシナリオでは常にdelete_missing 競合が生成されます。これは、デフォルトでは、操作をスキップすることで解決されます。

両方のDELETE 操作は同じ効果があるため、この競合は無害です。そのうちの1つを安全に無視できます。

3つ以上のノードとの競合

1つのノードが行を挿入し、2番目のノードに再生されてそこで更新される場合、3番目のノードは最初のノードからINSERT を受信する前に、2番目のノードからUPDATE を受信できます。このシナリオは、 INSERT /UPDATE の競合です。

これらの競合は、 UPDATE を破棄することで処理されます。これにより、異なるノードで異なるデータが発生する可能性があります。これらは

この競合タイプは、3つ以上のマスターでのみ発生します。少なくとも2つのマスターがアクティブに書き込んでいる必要があります。

また、ノード1からノード3へのレプリケーションラグは、次の一連のアクションを許可できるように十分に高くする必要があります。

1.ノード2はノード1からINSERTを受け取ります

2.ノード2がUPDATEを実行します

3.ノード3はノード2からUPDATEを受信します

4.ノード3はノード1からINSERTを受け取ります

insert_or_error (または場合によっては update_missing 競合タイプのinsert_or_skip 競合リゾルバー)を使用することは、これらの競合の実行可能な緩和戦略です。ただし、このオプションを有効にすると、 INSERT /DELETE の競合の扉が開きます。

1.ノード1はUPDATEを実行します

2.ノード2はDELETEを実行します

3.ノード3はノード2からDELETEを受信します

4.ノード3はノード1からUPDATEを受け取り、それをINSERT

これらが問題である場合は、テーブルまたはデータベースのフリーズ設定をチューニングして、update_recently_deleted として正しく検出されるようにすることをお勧めします。

別の方法は、 一般的な積極的なEager Replicationの効果 を使用してこれらの競合を防ぐことです。

INSERT /DELETE の競合は、3つ以上のノードでも発生する可能性があります。このような競合は、 UPDATE がDELETE に置き換えられたことを除いて、 INSERT /UPDATE と同じです。これにより、 delete_missing の競合が発生する可能性があります。

PGDは、 update_missing の競合で発生するように、各INSERT を最近削除されたチェックにすることを選択できます。ただし、これを行うと大多数のユーザーにペナルティが科されるため、現時点では代わりにdelete_missing をログに記録します。

将来のリリースでは、 delete_missing の競合が発生した場合、

LiveCompareによるデータ検証 を使用した再チェックにより、 INSERT /DELETE

の異常を自動的に解決します。アプリケーションは、 bdr.conflict_history_summary ビューを確認することにより、これらを手動で実行できます。

これらの競合は、2つの主な問題のユースケースで発生する可能性があります。

  • INSERT の後にすぐにDELETE が続きます。キューイングアプリケーションで使用できます

・ テーブルの主キー識別子を再利用する場合

これらのケースはいずれも一般的ではありません。これらの問題のユースケースが発生した場合、影響を受けるテーブルをレプリケートしないことをお勧めします。

PGDは、レプリケーションを正しく動作させるために識別子の一意性に依存しているため、後者の場合に問題があります。

同じ一意の識別子を挿入、削除、および後で再利用するアプリケーションは、問題を引き起こす可能性があります。これは

ABA problem と呼ばれます。

PGDには、行が現在の行、最後の行、またはそれよりずっと古い行であるかを知る方法がありません。

一意の識別子の再利用もビジネスの問題です。アプリケーションは、一意の識別子を再利用する必要はありません。

変更がシステムを通過するまでにかかる時間内に識別子の再利用が発生すると、問題が発生します。通常の操作ではその時間は短いかもしれませんが、ノードがダウンしていると、その間隔が数時間または数日に延長される可能性があります。

アプリケーションで一意の識別子を再利用しないことをお勧めします。その場合は、1年以内に再利用を避けるための措置を講じます。

この問題は、シーケンスまたはUUIDを使用するアプリケーションでは発生しません。

外部キー制約の競合

適用されているリモートトランザクションと既存のローカルデータとの間の競合は、 FOREIGN KEY (FK)制約でも発生する可能性があります。

PGDはsession_replication_role = 'replica' で変更を適用するため、変更を適用するときに外部キーは再チェックされません。アクティブ/アクティブ環境では、参照元テーブルへの挿入と同時に参照先テーブルで削除が発生すると、この状況によりFK違反が発生する可能性があります。このシナリオは、 INSERT /DELETE の競合に似ています。

シングルマスターPostgresでは、参照先テーブルの値を参照するINSERT /UPDATE は、行レベルロックを取得する前に DELETE 操作が完了するのを待つ必要があります。 DELETE が参照値を削除する場合、 INSERT /UPDATE はFKチェックに失敗します。

マルチマスターPGDでは。ノード間の行レベルのロックはありません。参照元のテーブルのINSERT は、参照先のテーブルのDELETE の後ろで待機しないため、両方のアクションを同時に実行できます。したがって、参照元テーブルのあるノードのINSERT /UPDATE は、別のノードの参照先テーブルのDELETE と同時に値を使用できます。結果は、参照元テーブルに存在しなくなった参照元テーブルの値です。

実際には、この状況は、 DELETE 操作が参照テーブルでのDELETE 操作とは別のトランザクションで参照テーブルで発生する場合に発生しますが、これは一般的な操作ではありません。

Orders -> OrderItemsなどの親子関係では、これを行うのは一般的ではありません。 OrderItemを完全に削除するよりも、キャンセル済みとしてマークする可能性が高くなります。参照/ルックアップデータの場合、新しいファクトデータに同じ値を使用すると同時にエントリを完全に削除することは珍しいことです。

ぶら下がっているFKは可能ですが、一般的にこのリスクは非常に低いです。したがって、PGDはこのケースをカバーするための一般的な解決策を課しません。これが発生する状況を理解したら、2つの解決策が可能です。

最初の解決策は、FKの使用を、一般に一度に1つのノードからのみ変更される、まれに変更される、または変更の同時実行がアプリケーションによって仲介される密接に関連するエンティティに制限することです。このアプローチにより、アプリケーションレベルでのFK違反が回避されます。

2番目の解決策は、PGDが提供する関数bdr.ri_fkey_trigger() およびbdr.ri_fkey_on_del_trigger() を使用して、このケースから保護するトリガーを追加することです。 BEFORE トリガーとして呼び出された場合、これらの関数はFOREIGN KEY 情報を使用して、参照列をNULLに設定することにより、FKの異常を回避します。 SET NULL 制約があるかのように。このアプローチでは、1つのトリガーですべてのFKを再チェックするため、FK違反を防ぐには、テーブルごとに1つのトリガーのみを追加する必要があります。

例として、FactとRefDataの2つのテーブルがあるとします。 Factには、RefDataを参照するFKがあります。 Factは参照テーブル、RefDataは参照テーブルです。各テーブルに1つのトリガーを追加する必要があります。

RefDataの参照された行がすでに削除されている場合、Factに列をNULLに設定するトリガーを追加します。

CREATE TRIGGER bdr_replica_fk_iu_trg
    BEFORE INSERT OR UPDATE ON fact
    FOR EACH ROW
    EXECUTE PROCEDURE bdr.ri_fkey_trigger();

ALTER TABLE fact
    ENABLE REPLICA TRIGGER bdr_replica_fk_iu_trg;

RefDataテーブルでDELETEが発生したときにFactの列をNULLに設定するトリガーを追加します。

CREATE TRIGGER bdr_replica_fk_d_trg
    BEFORE DELETE ON refdata
    FOR EACH ROW
    EXECUTE PROCEDURE bdr.ri_fkey_on_del_trigger();

ALTER TABLE refdata
    ENABLE REPLICA TRIGGER bdr_replica_fk_d_trg;

両方のトリガーを追加すると、外部キーのダングリングが回避されます。

TRUNCATEの競合

TRUNCATE は、すべての行のDELETE と同様に動作しますが、行ごとの削除ではなくテーブルデータを物理的に削除することにより、このアクションを実行します。その結果、行レベルの競合処理は利用できないため、 TRUNCATE コマンドは、明確な競合がある場合でも、他のDMLアクションとの競合を生成しません。

その結果、別のDMLがTRUNCATE への他のノードで同時に実行された場合、リプレイの順序によって異なる変更が発生する可能性があります。

次のいずれかのアクションを実行できます。

  • TRUNCATE が他の同時DMLと一緒に実行されないことを確認します。 LiveCompareによるデータ検証 に依存して、そのような矛盾を強調してください。

  • TRUNCATE をWHERE 句のないDELETE ステートメントに置き換えます。このアプローチは、大きなテーブルではパフォーマンスが低下する可能性があります。

  • bdr.truncate_locking = 'on' を設定して、 TRUNCATE コマンドのロック動作を設定します。この設定は、 TRUNCATE がbdr.ddl_locking 設定に従うかどうかを決定します。すべてのノードが起動している必要があるため、これはTRUNCATE のデフォルトの動作ではありません。この構成は、すべての場合に可能または望ましいとは限りません。

除外制約の競合

PGDは除外制約をサポートしていないため、作成できません。

既存のスタンドアロンデータベースをPGDデータベースに変換する場合は、すべての除外制約を手動で削除します。

分散非同期システムでは、さまざまなノード上のすべてのトランザクションが完全に分離されるため、制約に違反する行のセットが存在しないことを保証できません。除外制約は、除外制約違反のために再生を任意のノードから他のノードに進めることができない再生デッドロックにつながります。

PGDに除外制約の作成を強制した場合、またはスタンドアロンデータベースをPGDに変換するときに既存の制約を削除しない場合、レプリケーションが破損することが予想されます。再び進行させるには、着信リモートタプルが競合するローカルタプルを削除または変更して、リモートトランザクションを適用できるようにします。

ロールとテーブルスペースの違いによるデータの競合

競合は、ノードがロールなどの異なるグローバル(Postgresシステム全体の)データを持つ場合にも発生する可能性があります。この競合により、オペレーション(主にDDL )が発生する可能性があります。オペレーションは正常に実行され、1つのノードでコミットできますが、他のノードに適用できません。

たとえば、node1にはfredという名前のユーザーがあり、そのユーザーはnode2で作成されなかったとします。 node1のfredがテーブルを作成する場合、テーブルは所有者がfredに設定されて複製されます。 DDLコマンドをnode2に適用すると、fredという名前のユーザーがないため、 DDLは失敗します。この失敗により、Postgresログにエラーが生成されます。

PGDが実行されているデータベースにユーザーfredを作成して、この競合を解決するには、管理者の介入が必要です。 bdr.role_replication = on を設定して、将来これを解決できます。

ロックの競合とデッドロックの中止

PGDライタプロセスは通常のユーザーセッションとほとんど同じように動作するため、行とテーブルのロックに関する通常のルールに従います。これにより、PGDライタプロセスが、ユーザートランザクションまたは相互に保持されているロックで待機する場合があります。

関連するロックには以下が含まれます。

  • ユーザーセッションによる明示的なテーブルレベルロック(LOCK TABLE ... )

  • ユーザーセッションによる明示的な行レベルロック(SELECT ... FOR UPDATE/FOR SHARE )

  • ローカルアクティビティまたは他のノードからのレプリケーションのいずれかによる、行UPDATE 、INSERT 、またはDELETE 操作による暗黙的なロック

PGDライタプロセスは、ユーザートランザクションでデッドロックする可能性があります。ユーザートランザクションは、ライタプロセスが保持するロックを待機しています。その逆も同様です。 2つのライタプロセスが互いにデッドロックする場合もあります。 Postgresのデッドロック検出器は、問題のあるトランザクションの1つに介入して終了します。 PGDライタプロセスが終了した場合、再試行し、通常は成功します。

これらの問題はすべて一時的なものであり、通常、管理者の操作は必要ありません。ライタプロセスがアイドル状態のユーザーセッションのロックの背後で長時間スタックしている場合、管理者はユーザーセッションを終了して、レプリケーションが再び流れるようにすることができます。ただし、これは、ユーザーが別のユーザーセッションに影響を与える長いロックを保持することと変わりありません。

Postgresで log_lock_waits 機能を使用すると、ロック関連のリプレイストールの特定に役立ちます。

発散する競合

発散競合は、異なるノードで同じであるはずのデータが予期せず異なる場合に発生します。発散する競合は発生しないはずですが、執筆時点でそのような競合のすべてを確実に防止できるわけではありません。

行のPRIMARY KEY を変更すると、すべてのノードが変更を再生する前に別のノードが同じ行のキーを変更した場合、発散競合が発生する可能性があります。主キーの変更を避けるか、指定された1つのノードでのみ変更します。

行データが関係する発散コンフリクトは、一般に、一方のノードのデータを他方のノードと一貫性を保つように手動で調整する管理者のアクションを必要とします。文書化されたとおりにPGDを使用し、安全でないとマークされた設定または機能を回避する限り、このような競合は発生しません。

管理者は、このような競合を手動で解決する必要があります。競合の性質によっては、 bdr.ddl_replication やbdr.ddl_locking などの高度なオプションの使用が必要になる場合があります。ただし、これらのオプションを不注意に使用すると、事態がさらに悪化し、一般的な指示では対処できない競合が発生する可能性があります。

TOASTサポート詳細

Postgresは、 TOAST と呼ばれる大きな列にアウトオブラインストレージを使用します。

論理デコード(PGDがその上に構築されます)および論理レプリケーションで処理するTOAST値は、テーブルのメイン行の一部として格納されるインラインデータとは異なります。

TOAST値は、値が変更された場合にのみトランザクションログ(WAL)に記録されます。これは、特に UPDATE の競合を処理するときに問題を引き起こす可能性があります。トーストされた列の値を変更しなかったUPDATE ステートメントは、その列のない行を生成するためです。

INSERT / UPDATEの競合 で述べたように、 update_missing

の競合がinsert_or_error を使用して解決され、 TOAST列が欠落している場合、PGDはエラーを報告します。

ただし、これよりも微妙な問題は、非同期レプリケーションを使用した同時ワークロードの場合に発生します。 (積極的なトランザクションは影響を受けません。)たとえば、A、B、およびCという3つのノードを持つEDB Postgres分散クラスターでの次のワークロードを想像してください。

1.ノードAで: txn A1は UPDATE SET col1 = ’toast data…’を行い、最初にコミットします。

2.ノードBで: txn B1はUPDATE SET other_column = ‘anything else’;を行います。 A1の後にコミットします。

3.ノードC:ノードAへの接続が遅れます。

4.ノードC: txn B1が最初に適用され、col1のTOASTed列を見逃しますが、競合することなく適用されます。

5.ノードC: txn A1は( update_origin_changeで)競合し、スキップされます。

6.ノードCは、A1からのトーストされたデータを永遠に逃します。

PGDを使用する場合、このシナリオは通常問題になりません。 (ビルトイン論理レプリケーションまたはマルチマスターのプレーンなpglogicalを使用する場合です。)PGDは、 TOAST列の変更を最近レプリケートした行にローカルTOASTを検出し、ローカルUPDATE がt TOASTを変更します。したがって、PGDは、異なるノード間でトーストされたデータの不整合を防ぎます。この状況により、マルチプルのノードで更新が発生した場合、つまりタプルのオリジンが変更された場合、WALロギングが増加します。 PGD AlwaysOnアーキテクチャの通常の場合と同様に、すべての更新が単一のノードから行われた場合、追加のWALオーバーヘッドはゼロです。

注釈

メインテーブルで同じことをせずにTOASTテーブルだけで`VACUUM FULL` または`CLUSTER` を実行すると、追加のロギングが機能するために必要なメタデータが削除されます。これは、そのようなステートメントの後の短期間、これらの同時実行の問題に対する保護が存在しないことを意味します。

警告

TOASTの追加のWALロギングは、標準Postgresの`BEFORE UPDATE` トリガーを使用して行われます。このトリガーは、テーブル上のすべての`BEFORE UPDATE` トリガーの中で、トリガー名に基づいてアルファベット順に最後にソートする必要があります。これを簡単にするために`zzzz_bdr_` プレフィックスが付いていますが、その後にソートされる名前でトリガーを作成しないでください。そうしないと、同時実行の問題に対する保護が得られません。

insert_or_error の競合解決のために、 REPLICA IDENTITY FULL の使用はまだ必要です。

トーストされた列に関連するこれらの問題は、 REPLICA IDENTITY FULL を持つテーブルには影響しません。この設定では、行全体がキーの一部と見なされるため、トーストされた値が常にキーの一部としてログに記録されます。 PGDは、新しい行を再構築し、キー行から欠落しているデータを埋めることができます。その結果、 REPLICA IDENTITY FULL を使用すると、WALサイズが大幅に増加する可能性があります。

競合の回避または許容

ほとんどの場合、競合を回避または許容するようにアプリケーションを設計できます。

競合は、複数のノードで同時に発生している場合にのみ発生します。競合を回避する最も簡単な方法は、1つのノードにのみ書き込むか、一度に1つの特定のノードから特定の方法で特定の行にのみ書き込むことです。

この回避は、多くのアプリケーションで自然に発生します。たとえば、多くのコンシューマーアプリケーションでは、アカウントのデフォルトの請求先住所の変更など、所有ユーザーのみがデータを変更できます。このようなデータ変更で更新の競合が発生することはほとんどありません。

ノードがダウンする直前に変更を加える可能性があるため、変更が失われたように見えます。その後、同じ変更を再度行う可能性があり、異なるノードで2つの更新が発生します。ダウンしたノードが復旧すると、古い変更を他のノードに送信しようとします。データの最終更新が保持されるため、拒否されます。

INSERT /INSERT の競合の場合、 PGDグローバルシーケンス を使用して、このタイプの競合を防止します。

部屋予約アプリケーションなど、オブジェクト間の関係を割り当てるアプリケーションの場合、 update_if_newer を適用しても、許容できるビジネス結果が得られない場合があります。つまり、2人が同じ部屋を予約したことを2人に個別に確認することは役に立ちません。最も簡単な解決策は、 Eager Replicationを使用して、1つの予約のみが成功するようにすることです。アプリケーションによっては、より複雑な方法が可能な場合があります。たとえば、各ノードに100席を割り当て、そのノードのライタがそれらを予約できるようにすることができます。ただし、ローカルで利用できるものがない場合は、ほとんどのシートが予約された後に、分散ロックスキームまたはEager Replicationを使用します。

特定の種類の更新が1つの特定のノードからのみ行われるようにする別の手法は、さまざまな種類のトランザクションをさまざまなノード経由でルーティングすることです。例:

  • 1つのノードで小包を受け取りますが、別のノードを使用して小包を配達します

  • 1つのノードで注文が入力され、2番目のノードで作業が準備され、別のノードで顧客に提供されるサービスアプリケーション

多くの場合、最善の方法は、競合の発生を許可し、PGDの競合解決メカニズムと連携して競合に対処するようにアプリケーションを設計することです。

競合検出

PGDは、競合検出のために次のメカニズムを提供します。

オリジンの競合検出

オリジン競合検出は、トランザクションが発生したノードに記録されたコミットタイムスタンプを使用および依存します。これには、クロックが正しく動作するか、2つのノード間の最速メッセージの許容範囲内にあることが必要です。そうでない場合、競合解決はさらに先のノードを優先する傾向があります。パラメーターbdr.maximum_clock_skew およびbdr.maximum_clock_skew_action を使用して、ノード間のクロックスキューを管理できます。

行の起点は、track_commit_timestamp = on の場合にのみ使用できます。

競合は、レプリケーション起点が変更されたかどうかに基づいて最初に検出されるため、競合とならない可能性のある状況で競合トリガーが呼び出されます。したがって、このメカニズムは誤検出の競合を生成する可能性があるため、正確ではありません。

Origin情報は、行がフリーズするポイントまでのみ利用できます。フリーズされた後に行に到着した更新は競合を発生させないため、すべての場合に適用されます。これは、bdr_init_physical で新しいノードを追加するときの通常のケースであるため、その場合、競合を発生させると、多くの誤検知の結果が発生します。

オフラインだったノードが再接続してデータ変更の送信を開始すると、新しく到着した更新が更新するフリーズされた行よりも古い場合、発散エラーが発生する可能性があります。挿入と削除は、この状況の影響を受けません。

ノードの再起動とダウンしたノードの回復 で説明されているように、長期にわたる停止のためにノードをダウンさせないことをお勧めします。

EDB Postgres Extended ServerおよびEDB Postgres Advanced Serverでは、 PGDはノードがダウンしているときに行のフリーズを抑制します。このメカニズムはこの状況を適切に処理するため、パラメーター設定を変更する必要はありません。

Postgresの他のバリアントでは、この状況を慎重に管理する必要がある場合があります。

フリーズは通常、バキュームされている行が現在のxidのvacuum_freeze_min_age xidより古い場合に発生します。これは、これらのパラメーターに適切に高い値を構成する必要があることを意味します。

  • vacuum_freeze_min_age

  • vacuum_freeze_table_age

  • autovacuum_freeze_max_age

トランザクション率に基づいて値を選択し、データベースノードから競合データを削除する前にダウンタイムの猶予期間を提供します。たとえば、 vacuum_freeze_min_age が5億に設定されている場合、1000 TPSを実行するノードは、競合データが削除されるまでに5.5日強ダウンする可能性があります。 CommitTSデータ構造は、その設定で5 GBのディスク領域を必要とするため、トランザクションレートの低いシステムは、低い設定のメリットを享受できます。

最初に推奨される設定は次のとおりです。

sql#10億= 10GB

autovacuum_freeze_max_age = 1000000000

Vacuum_freeze_min_age = 500000000

autovacuum_freeze_max_ageの90%

Vacuum_freeze_table_age = 900000000

次のことに注意してください。

・ autovacuum_freeze_max_age はノードスタート時にのみ設定できます。

  • vacuum_freeze_min_age を設定できるため、低い値を使用すると行が早期にフリーズされ、競合が無視される可能性があります。個々のテーブルにautovacuum_freeze_min_age およびtoast.autovacuum_freeze_min_age を設定することもできます。

  • CLUSTER またはVACUUM FREEZE コマンドを実行すると、行が早期にフリーズされ、競合が無視される場合があります。

PGDは、行のバージョン管理を使用し、ノードのシステムクロックとは無関係に競合検出を行うオプションを提供します。

行バージョンの競合検出では、3つのことを有効にする必要があります。これらの手順のいずれかが正しく実行されない場合、

オリジンの競合検出 が使用されます。

  • check_full_tuple またはPGDノードグループを有効にします。

  • 行バージョンの競合検出を使用するすべてのテーブルでREPLICA IDENTITY FULL を有効にします。

  • bdr.alter_table_conflict_detection を使用して、テーブルで行バージョンの追跡を有効にします。この関数は、指定した名前の列と、新しい列の値を管理するUPDATE トリガーを追加します。列はINTEGER タイプとして作成されます。

カウンターはUPDATE でのみインクリメントされますが、この手法により、 UPDATE とDELETE の両方の競合検出が可能になります。

このアプローチはLamportタイムスタンプに似ており、競合検出のABA問題を完全に防ぎます。

注釈

行レベルの競合解決は、行のバージョン管理を使用しても 列レベルの競合解決の有効化と無効化 構成に基づいて処理されます。行バージョンが生成される方法は、競合を検出する場合にのみ役立ちます。行のどのバージョンが新しいかについての信頼できる情報としてこれに頼らないでください。

特定のテーブルに使用されている現在の競合解決戦略を判断するには、ビューbdr.tables の列conflict_detection を参照してください。

テーブルの所有者は、特定のテーブルの競合検出の動作を変更できます。

bdr.alter_table_conflict_detection(relation regclass,
                                   method text,
                                   column_name name DEFAULT NULL)
  • relation —新しい競合検出方法を設定するリレーションの名前。

  • method —使用する競合検出方法。

  • column_name —列検出データの格納に使用する列。これはスキップできます。その場合、競合検出方法に基づいて列名が選択されます。 row_origin メソッドは、メタデータストレージ用の追加の列を必要としません。

認識されている競合検出方法は次のとおりです。

  • row_origin —タプルに対して行われた以前の変更の起点( オリジンの競合検出 を参照)。これは、テーブルに追加の列を必要としないサポートされている唯一の方法です。

  • row_version —行バージョン列( 行バージョンの競合検出 を参照)。

  • column_commit_timestamp —列ごとのコミットタイムスタンプ( CLCD で説明)。

  • column_modify_timestamp —列ごとの変更タイムスタンプ( CLCD で説明)。

column_commit_timestamp と column_modify_timestamp の競合検出方法の違いの詳細については、 現在とコミットのタイムスタンプ を参照してください。

この関数は、 DDL ステートメントと同じレプリケーションメカニズムを使用します。これは、レプリケーションが ddl filters 構成の影響を受けることを意味します。

この関数は、列レベルの競合解決が有効になっているリレーションでDML グローバルロックを取得します。

この関数はトランザクションです。トランザクションのROLLBACK を使用して効果をロールバックでき、変更は現在のトランザクションで表示されます。

bdr.backwards_compatibility が30618以下に設定されていない限り、 relation の所有者のみがbdr.alter_table_conflict_detection 関数を実行できます。

警告

競合検出方法を追加の列を使用してメタデータを格納する方法から変更すると、その列が削除されます。

警告

この関数は、警告が`bdr.camo_enable_client_warnings` で無効になっていない限り、 CAMOを無効にして警告を出します。

PGDは、 conflict_type パラメーターとして使用できる次の競合タイプを認識します。

  • insert_exists —着信挿入は、主キーまたは一意のキー/インデックスを介して既存の行と競合します。

  • update_differing —着信更新のキー行がローカル行と異なります。これは、 行バージョンの競合検出 を使用する場合にのみ発生します。

  • update_origin_change —着信更新は、別のノードによって最後に変更された行を変更しています。

  • update_missing —着信更新が、存在しない行を変更しようとしています。

  • update_recently_deleted —着信更新が、最近削除された行を変更しようとしています。

  • update_pkey_exists —着信更新により、 PRIMARY KEY が、変更を適用しているノードに既に存在する値に変更されました。

  • multiple_unique_conflicts —着信行は、ターゲットテーブル内の複数のUNIQUE制約/インデックスと競合します。

  • delete_recently_updated —現在のノードでの行の最新の更新よりも古いコミットタイムスタンプを持つ着信削除、または 行バージョンの競合検出 を使用する場合。

  • delete_missing —着信削除は、存在しない行を削除しようとしています。

  • target_column_missing —ターゲットテーブルには、受信行にある1つ以上の列がありません。

  • source_column_missing —受信行には、ターゲットテーブルに存在する1つ以上の列がありません。

  • target_table_missing —ターゲットテーブルがありません。

  • apply_error_ddl —レプリケートされたDDLコマンドを適用すると、Postgresによってエラーがスローされました。

競合の解決

ほとんどの競合は自動的に解決できます。 PGDは、デフォルトでlast-update-winsメカニズム、より正確にはupdate_if_newer 競合リゾルバーになります。このメカニズムは、競合検出に使用されるのと同じコミットタイムスタンプに基づいて、競合する2つの行のうちの最後に挿入または変更された行を保持します。特定のコーナーケースシナリオでの動作は、 bdr.create_node_group 、またはbdr.alter_node_group に使用される設定によって異なります。

PGDでは、次の関数を使用して、競合解決のデフォルトの動作をオーバーライドできます。

bdr.alter_node_set_conflict_resolver

この関数は、指定されたノードでの競合解決の動作を設定します。

概要

bdr.alter_node_set_conflict_resolver(node_name text,
                                     conflict_type text,
                                     conflict_resolver text)

パラメーター

  • node_name —変更されるノードの名前。

  • conflict_type —設定を適用する競合タイプ( 競合タイプのリスト を参照)。

  • conflict_resolver —指定された競合タイプに使用するリゾルバー( 競合リゾルバーのリスト を参照)。

注意事項

現在、ローカルノードのみを変更できます。関数呼び出しは複製されません。複数のノードの設定を変更する場合は、ノードごとに関数を実行する必要があります。

この関数による構成の変更は、 bdr.create_node_group またはbdr.alter_node_group で指定された競合解決のデフォルトの動作をオーバーライドします。

この関数はトランザクションです。変更をロールバックでき、現在のトランザクションで表示されます。

競合リゾルバーのリスト

PGDではいくつかの競合リゾルバーを使用できますが、処理できる競合タイプのカバレッジは異なります。

  • error —エラーをスローし、レプリケーションを停止します。任意の競合タイプに使用できます。

  • skip —リモート変更の処理をスキップし、次の変更でレプリケーションを続行します。 insert_exists 、update_differing 、update_origin_change 、update_missing 、update_recently_deleted 、update_pkey_exists 、delete_recently_updated 、delete_missing 、target_table_missing 、target_column_missing 、およびsource_column_missing の競合に使用できます。

  • skip_if_recently_dropped —ダウンストリームで最近(1日以内)に削除されたため、ダウンストリームに存在しないテーブルの場合、リモート変更をスキップします。それ以外の場合はエラーをスローします。 target_table_missing 競合タイプに使用できます。 skip_if_recently_dropped 競合リゾルバーは、同じ名前のテーブルが削除された直後に再作成されると、課題を引き起こす可能性があります。その場合、ノードの1つが、テーブルを再作成するためのDDLを認識する前に、再作成されたテーブルのDMLを認識する可能性があります。次に、テーブルが最近削除されたと仮定して、リモートデータを誤ってスキップし、データ損失を引き起こします。したがって、この競合リゾルバーとともにオブジェクト名を削除した直後にオブジェクト名を再利用しないことをお勧めします。

  • skip_transaction —競合を生成したトランザクション全体をスキップします。 apply_error_ddl の競合に使用できます。

  • update_if_newer —リモート行が競合するローカル行よりも後にコミットされた場合(発信元ノードの壁時計によって決定される)を更新します。タイムスタンプが同じ場合、ノードIDがタイブレーカーとして使用され、すべてのノードで同じ行が確実に選択されます(ノードIDが高いほど勝ちます)。 insert_exists 、update_differing 、update_origin_change 、およびupdate_pkey_exists の競合タイプに使用できます。

  • update —常にレプリケートされたアクションを実行します。 insert_exists (INSERT をUPDATE に変換します)、update_differing 、update_origin_change 、update_pkey_exists 、およびdelete_recently_updated (削除を実行します)に使用できます。

  • insert_or_skip —オリジンから送信された利用可能な情報から新しい行を作成し、 INSERT を試みます。完全な行を作成するのに十分な情報がない場合、変更をスキップします。 update_missing およびupdate_recently_deleted 競合タイプに使用できます。

  • insert_or_error — originから送信された利用可能な情報から新しい行を作成して挿入しようとします。完全な行を構築するのに十分な情報がない場合、エラーをスローし、レプリケーションを停止します。 update_missing およびupdate_recently_deleted 競合タイプに使用できます。

  • ignore —欠落しているターゲット列を無視し、処理を続行します。 target_column_missing 競合タイプに使用できます。

  • ignore_if_null —リモート行の余分な列にNULL値が含まれる場合、欠落しているターゲット列を無視します。それ以外の場合、エラーをスローし、レプリケーションを停止します。 target_column_missing 競合タイプに使用できます。

  • use_default_value —欠落している列値をデフォルト(それが列のデフォルトの場合は NULL を含む)で埋めて、処理を続行します。デフォルトの処理中のエラーまたは制約の違反(つまり、 NOT NULL列のNULLデフォルト)は、レプリケーションを停止します。 source_column_missing 競合タイプに使用できます。

insert_exists 、 update_differing 、 update_origin_change 、 update_missing 、 multiple_unique_conflicts 、 update_recently_deleted 、 update_pkey_exists 、 delete_recently_updated 、および delete_missing 競合タイプは、

競合トリガー を使用してユーザー定義ロジックで解決することもできます。

このマトリックスは、競合リゾルバーが処理できる競合タイプを区別するのに役立ちます。

insert exists

update differing

update origin change

update missing

update recently deleted

update pkey exists

delete recently updated

delete missing

target column missing

source column missing

target table missing

multiple unique conflicts

error

X

X

X

X

X

X

X

X

X

X

X

X

skip

X

X

X

X

X

X

X

X

X

X

X

X

skip_if_recently_dropped

X

update_if_newer

X

X

X

X

update

X

X

X

X

X

X

insert_or_skip

X

X

insert_or_error

X

X

ignore

X

ignore_if_null

X

use_default_value

X

conflict_trigger

X

X

X

X

X

X

X

X

X

デフォルトの競合リゾルバー

Conflict type

Resolver

insert_exists

update_if_newer

update_differing

update_if_newer

update_origin_change

update_if_newer

update_missing

insert_or_skip

update_recently_deleted

skip

update_pkey_exists

update_if_newer

multiple_unique_conflicts

error

delete_recently_updated

skip

delete_missing

skip

target_column_missing

ignore_if_null

source_column_missing

use_default_value

target_table_missing

skip_if_recently_dropped

apply_error_ddl

error

競合解決のリスト

競合解決は、競合リゾルバーによって選択された解決の種類を表し、競合を解決するために取られた特定のアクションに対応します。

conflict_resolution パラメーターでは、現在、次の競合解決がサポートされています。

  • apply_remote —リモート(着信)行が適用されました。

  • skip —行の処理がスキップされました(ローカルで変更はありませんでした)。

  • merge —リモート行とローカル行からの情報をマージして、新しい行が作成されました。

  • user —ユーザーコード(競合トリガー)は、ターゲットテーブルに書き込まれた行を生成しました。

競合ログ

マルチマスターの競合の診断と処理を容易にするために、PGDはデフォルトで、すべての競合をbdr.conflict_history テーブルに記録します。次の関数を使用して、この動作をより詳細に変更できます。

bdr.alter_node_set_log_config

ノードの競合ログ構成を設定します。

概要

bdr.alter_node_set_log_config(node_name text,
                              log_to_file bool DEFAULT true,
                              log_to_table bool DEFAULT true,
                              conflict_type text[] DEFAULT NULL,
                              conflict_resolution text[] DEFAULT NULL)

パラメーター

  • node_name —変更されるノードの名前。

  • log_to_file —ノードログファイルに記録するかどうか。

  • log_to_table — bdr.conflict_history テーブルにログを記録するかどうか。

  • conflict_type —ログに記録する競合タイプ。 NULL(デフォルト)はすべてを意味します。

  • conflict_resolution —ログに記録する競合の解決。 NULL(デフォルト)はすべてを意味します。

注意事項

ローカルノードのみ変更できます。関数呼び出しは複製されません。複数のノードの設定を変更する場合は、ノードごとに関数を実行する必要があります。

この関数はトランザクションです。変更をロールバックでき、現在のトランザクションで表示されます。

競合ログ構成のリスト

ビュー bdr.node_log_config は、すべてのロギング構成を表示します。ログ構成の名前、ログに記録する場所、ログに記録する競合の種類と解決がリストされます。

競合をテーブルに記録する

log_to_table がtrueに設定されている場合、競合はテーブルに記録されます。競合ログのターゲットテーブルはbdr.conflict_history です。

このテーブルは、列local_time でレンジパーティション分割されています。テーブルはautopartitionで管理されています。デフォルトでは、毎日新しいパーティションが作成され、過去1か月の競合が維持されます。その後、古いパーティションは自動的に削除されます。自動パーティションは、7〜14個のパーティションを事前に作成します。 bdr_superuserはこれらのデフォルトを変更できます。

PGDによって管理されるすべてのテーブルで生成された競合はこのテーブルに記録されるため、正当なユーザーのみが競合するデータを読み取れるようにすることが重要です。 PGDは、 bdr.conflict_history テーブルでROW LEVEL SECURITYポリシーを定義することにより、これを行います。テーブルの所有者のみが、それぞれのテーブルの競合を読み取ることが許可されています。基になるテーブルにRLSポリシーが定義、有効化、および適用されている場合、所有者でさえ競合を読み取ることはできません。 FORCEオプションで作成されたRLSポリシーは、テーブルの所有者にも適用されます。その場合、基になるテーブルの一部またはすべての行が、所有者でさえ読み取れない場合があります。そのため、PGDは競合ログテーブルにより厳しいポリシーも適用します。

デフォルトのロールbdr_read_all_conflicts は、 bdr_superuser ロールを付与せずに、 bdr.conflict_history テーブルに記録されたすべての競合の詳細を表示する必要があるユーザーに付与できます。

デフォルトのロールbdr_read_all_stats は、bdr.conflict_history_summary というカタログビューにアクセスできます。これにはユーザーデータが含まれていないため、ログに記録された競合を監視できます。

競合レポート

レポートのテーブルに記録された競合を要約できます。レポートにより、アプリケーション所有者は競合を特定、理解、解決し、アプリケーションの変更を導入して競合を防ぐことができます。

SELECT nspname, relname
, date_trunc(day, local_time) :: date AS date
, count(*)
FROM bdr.conflict_history
WHERE local_time > date_trunc(day, current_timestamp)
GROUP BY 1,2,3
ORDER BY 1,2;

 nspname | relname |    date    | count
- --------+---------+------------+-------
 my_app  | test    | 2019-04-05 |     1
(1 row)

LiveCompareによるデータ検証

LiveCompareは、2つのデータベースを比較して同一であることを確認するように設計されたユーティリティプログラムです。

LiveCompareはPGDスタックの一部として含まれ、PGDノードの任意のペアを対象とすることができます。デフォルトでは、レプリケートされたすべてのテーブルを比較し、違いを報告します。 LiveCompareは、PostgresやOracleなどの非PGDデータソースでも動作します。

LiveCompareを使用して、着信行を継続的に監視することもできます。コンテキスト情報を失うことなく停止および開始できるため、都合の良いときに実行できます。

LiveCompareを使用すると、複数のテーブルを同時にチェックできます。いくつかのテーブルまたはテーブル内の行のセクションのみをチェックできるように構成できます。チェックは、最初に行ハッシュ全体を比較することによって実行されます。異なる場合、 LiveCompare は行全体を比較します。 LiveCompareは、有用なサイズのバッチで行を比較することにより、オーバーヘッドを回避します。

違いが見つかった場合、それらは時間をかけて再チェックでき、最終的な一貫性の遅れを考慮に入れることができます。

詳細については、 LiveCompareによるデータ検証 のドキュメントを参照してください。