Conflicts
=========

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

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

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

 :ref:`列レベルの競合解決の有効化と無効化 <列レベルの競合解決の有効化と無効化>` で説明しているように、競合処理は構成可能です。 PGDは、
 :ref:`ストリームトリガーリファレンス <ストリームトリガーリファレンス>` で説明しているように、競合トリガーを使用してテーブルごとに競合を検出し、それらをさまざまな方法で処理できます。

列レベルの競合の検出と解決は、
 :ref:`CLCD <Column-level conflict detection>` で説明しているPGDで利用できます。

デフォルトでは、すべての競合は\ ``bdr.conflict_history``
ログに記録されます。競合の可能性がある場合、テーブル所有者は競合を監視して回避方法を分析するか、アプリケーションタスクとして定期的に処理する計画を立てる必要があります。
 :ref:`LiveCompareによるデータ検証 <LiveCompareによるデータ検証>` ツールは、発散がないか定期的にスキャンすることもできます。

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

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

競合が発生する仕組み
--------------------

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

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

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

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

競合の種類
----------

主キーまたは一意の競合
^^^^^^^^^^^^^^^^^^^^^^

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

行の競合には次のものが含まれます。

-  ``INSERT`` vs ``INSERT``

-  ``UPDATE`` vs ``UPDATE``

-  ``UPDATE`` vs ``DELETE``

-  ``INSERT`` vs ``UPDATE``

-  ``INSERT`` vs ``DELETE``

-  ``DELETE`` vs ``DELETE``

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

INSERT/INSERTの競合
^^^^^^^^^^^^^^^^^^^

最も一般的な競合 ``INSERT`` /``INSERT``
は、2つの異なるノードでの\ ``INSERT`` 操作が、同じ\ ``PRIMARY KEY``
値または\ ``PRIMARY KEY`` が存在しない場合、単一の\ ``UNIQUE``
コンストレインの同じ値を持つタプルを作成する場合に発生します。

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

この競合は\ ``insert_exists``
競合タイプを生成します。これは、デフォルトで解決されます。コミット時間に基づいて、新しい行を選択し、その1つだけ\ ``update_if_newer``
リゾルバー。他のリゾルバーを構成できます。詳細は、
 :ref:`列レベルの競合解決の有効化と無効化 <列レベルの競合解決の有効化と無効化>` を参照してください。

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

 :ref:`PGDグローバルシーケンス <PGDグローバルシーケンス>`  を使用して、このタイプの競合を効果的に排除できます。

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

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

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

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

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

UPDATE/UPDATEの競合
^^^^^^^^^^^^^^^^^^^

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

これらは、構成と状況に基づいて、さまざまな種類の競合を生成する可能性があります。テーブルが :ref:`行バージョンの競合検出 <行バージョンの競合検出>` で構成されている場合、元のキー行がローカル行と比較されます。それらが異なる場合、\ ``update_differing``
競合が生成されます。 :ref:`オリジンの競合検出 <オリジンの競合検出>` 
を使用する場合、行の原点がチェックされます。オリジンは、現在のローカル行が発生したノードです。これが変更されると、
``update_origin_change`` 競合が生成されます。その他の場合、\ ``UPDATE``
は通常、競合を生成せずに適用されます。

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

主キーのUPDATEの競合
^^^^^^^^^^^^^^^^^^^^

PGDは、現在、 ``PRIMARY KEY`` が\ ``UPDATE``
オペレーションによって変更される競合解決を実行できません。主キーを更新できますが、既存の値と競合しないことを確認する必要があります。

主キーの更新の競合は :ref:`多様な競合 <多様な競合>` であり、手動介入が必要です。

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

単純なスキーマは、次のことを説明する例を提供します。

.. code:: sql

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

主キー列の更新が可能であるため、次のSQLは成功します。

.. code:: sql

   UPDATE pktest SET pk=2 WHERE pk=1;

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

.. code:: sql

   INSERT INTO pktest VALUES (3,3);

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

.. code:: sql

   UPDATE pktest SET pk=4 WHERE pk=3;

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

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

.. code:: sql

   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つの変更を同時に実行すると、動作します。

.. code:: sql

   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``
競合が発生します。

.. code:: sql

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

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

.. code:: sql

   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つの変更を同時に実行することは、解決するのが簡単ではありません。

.. code:: sql

   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を使用してこれらの変更を行います。

..  Warning::
   `update_pkey_exists` 競合の競合解決の結果が更新された場合、常にいずれかの行が削除されます。

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

 :ref:`複数のUNIQUE制約に違反するINSERT操作 <複数のUNIQUE制約に違反するINSERT操作>` と同様に、受信した\ ``UPDATE`` が複数の\ ``UNIQUE``
インデックスまたは\ ``PRIMARY KEY``
に違反すると、PGDは\ ``multiple_unique_conflicts`` 競合を発生させます。

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

UPDATE/DELETEの競合
^^^^^^^^^^^^^^^^^^^

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

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

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

競合する\ ``DELETE`` および\ ``UPDATE``
の別のタイプは、行がローカルに更新された後に来る\ ``DELETE``
です。この状況では、結果は使用される競合検出の種類によって異なります。デフォルトの
 :ref:`オリジンの競合検出 <オリジンの競合検出>`  を使用する場合、競合は検出されないため、 ``DELETE``
が適用され、行が削除されます。 :ref:`行バージョンの競合検出 <行バージョンの競合検出>` を有効にすると、
``delete_recently_updated``
競合が生成されます。この競合タイプのデフォルトの解決策は、\ ``DELETE``
を適用し、行を削除することです。ただし、これを構成するか、競合トリガーで処理できます。

INSERT / UPDATEの競合
^^^^^^^^^^^^^^^^^^^^^

デフォルトの非同期オペレーションモードを使用している場合、ノードは元の\ ``INSERT``
を受信する前に行の\ ``UPDATE``
を受信する場合があります。これは、3つ以上のノードがアクティブな場合にのみ発生する可能性があります
 :ref:`3つ以上のノードとの競合 <3つ以上のノードとの競合>` を参照してください。

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

トーストされたデータの詳細については、
 :ref:`TOASTサポートの詳細 <TOASTサポートの詳細>` を参照してください。

INSERT/DELETEの競合
^^^^^^^^^^^^^^^^^^^

``INSERT`` /``UPDATE`` 競合と同様に、ノードは\ ``INSERT``
をまだ受け取っていない行で\ ``DELETE``
オペレーションを受信する場合があります。これも、3つ以上のノードがセットアップされている場合にのみ可能です
 :ref:`3つ以上のノードとの競合 <3つ以上のノードとの競合>` を参照してください。

PGDは現在この競合タイプを検出できません。 ``INSERT``
オペレーションは競合タイプを生成せず、 ``INSERT`` が適用されます。

``DELETE`` オペレーションは常に\ ``delete_missing``
競合を生成します。これは、デフォルトでオペレーションをスキップすることにより解決されます。

DELETE/DELETEの競合
^^^^^^^^^^^^^^^^^^^

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

このシナリオでは、常に\ ``delete_missing``
競合が生成されます。これは、デフォルトで、オペレーションをスキップすることにより解決されます。

両方の\ ``DELETE``
操作は同じ効果があるため、この競合は無害です。それらのいずれかを無視しても安全です。

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

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

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

この競合タイプは、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``
として正しく検出されるようにすることをお勧めします。

 :ref:`Eager Replicationの効果一般 <Eager Replicationの効果一般>` を使用して、これらの競合を防ぐこともできます。

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

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

将来のリリースでは、\ ``delete_missing``
競合が発生したときに :ref:`LiveCompareによるデータ検証 <LiveCompareによるデータ検証>` を使用した再チェックにより、\ ``INSERT``
/``DELETE`` 異常を自動的に解決します。アプリケーションは、
``bdr.conflict_history_summary``
ビューを確認することにより、これらを手動で実行できます。

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

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

-  テーブルの主キー識別子が再利用される場合

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

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

同じ一意の識別子を挿入、削除、および後で再利用するアプリケーションは、問題を発生する可能性があります。これは、
 `ABA problem <https://en.wikipedia.org/wiki/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つの解決策が考えられます。

最初の解決策は、一般的に一度に1つのノードのみから変更される、変更の頻度が低い、または変更のコンカレンシーがアプリケーションを介する場合に、密接に関連するエンティティにFKの使用を制限することです。このアプローチでは、アプリケーションレベルでのFK違反を回避します。

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

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

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

.. code:: sql

   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に設定するトリガーを追加します。

.. code:: sql

   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アクションとの競合を生成しません。

その結果、\ ``TRUNCATE``
への他のノードで別のDMLが同時に実行される場合、再生の順序により発散的な変更が発生する可能性があります。

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

-  ``TRUNCATE``
   が他のコンカレントDMLとともに実行されていないことを確認します。
   :ref:`LiveCompareによるデータ検証 <LiveCompareによるデータ検証>` を利用して、このような不一致を強調表示します。

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

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

除外コンストレインの競合
^^^^^^^^^^^^^^^^^^^^^^^^

PGDは除外コンストレインをサポートしておらず、その作成を防止します。

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

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

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

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

競合は、ノードにロールのようなグローバルPostgresシステム全体のデータが異なる場合にも発生する可能性があります。この競合により、操作主に\ ``DDL``
が発生し、1つのノードで正常に実行してコミットできますが、他のノードへの適用に失敗する場合があります。

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

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

ロックの競合とデッドロックアボート
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

関連するロックには次のものが含まれます。

-  ユーザーセッションによる明示的なテーブルレベルのロック\ ``LOCK TABLE ...``

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

-  ローカルアクティビティまたは他のノードからのレプリケーションからの行\ ``UPDATE``
   、\ ``INSERT`` 、または\ ``DELETE`` 操作のための暗黙的ロック

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

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

Postgresの :ref:`log_lock_waits <Monitoring through SQL>` 機能を使用すると、ロック関連の再生ストールを特定するのに役立ちます。

多様な競合
^^^^^^^^^^

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

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

行データを含む多様な競合では、一般に、一方のノードのデータを他方のノードと一貫性があるように手動で調整する管理者のアクションが必要です。文書に従ってPGDを使用し、安全でないとマークされた設定または機能を回避する限り、このような競合は発生しません。

管理者は、このような競合を手動で解決する必要があります。競合の性質によっては、
``bdr.ddl_replication`` や\ ``bdr.ddl_locking``
などの詳細オプションの使用が必要になる場合があります。ただし、これらのオプションを不注意に使用すると、状況をさらに悪化させ、汎用命令では対処できない競合が発生する可能性があります。

TOASTサポートの詳細
^^^^^^^^^^^^^^^^^^^

Postgresは、 `TOAST <https://www.postgresql.org/docs/current/storage-toast.html>`_ 
と呼ばれる大きな列にアウトオブラインストレージを使用します。

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

TOAST値は、値が変更された場合にのみトランザクションログWALに記録されます。トーストされた列の値を変更しなかった\ ``UPDATE``
ステートメントは、その列のない行を生成するため、特にUPDATEの競合を処理する場合に問題が発生する可能性があります。
 :ref:`INSERT / UPDATEの競合 <INSERT / UPDATEの競合>` で述べたように、PGDは、 ``insert_or_error``
を使用して\ ``update_missing``
競合が解決され、欠落しているTOAST列がある場合にエラーを報告します。

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

1. ノードAで txn A1はUPDATE SETcol1 = ’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を使用する場合、このシナリオは通常問題になりません。
PGDは、マルチマスターにビルトイン論理レプリケーションまたはプレーンpglogicalのいずれかを使用する場合です。
PGDは、 TOAST列変更を最近レプリケートし、ローカル\ ``UPDATE``
がそうでない行にローカル\ ``UPDATE`` を検出すると、
TOAST列の独自のロギングを追加します。 t TOASTを変更するしたがって、
PGDは、異なるノード間でトーストされたデータの不一貫性を防ぎます。この状況により、マルチプルのノードで更新が発生した場合、つまりタプルのオリジンが変更された場合、WALログの増加が発生します。通常、PGD
AlwaysOnアーキテクチャの場合と同様に、すべての更新が単一のノードから行われる場合、追加のWALオーバーヘッドはゼロです。

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

..  Warning::
   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`` 競合の場合、
 :ref:`PGDグローバルシーケンス <PGDグローバルシーケンス>` を使用してこのタイプの競合を防止します。

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

特定の種類の更新が特定のノードからのみ発生するようにする別の手法は、異なるノードを介して異なる種類のトランザクションをルーティングすることです。例

-  1つのノードで小包を受信するが、別のノードを使用して小包を配送する

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

多くの場合、最良のコースは、競合の発生を許可し、競合に対処するためにPGDの競合解決メカニズムと連携するようにアプリケーションをデザインすることです。

競合検出
--------

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

-  :ref:`オリジンの競合検出 <オリジンの競合検出>`  デフォルト

-  :ref:`行バージョンの競合検出 <行バージョンの競合検出>` 

-  :ref:`Column-level conflict detection <Column-level conflict detection>` 

オリジンの競合検出
^^^^^^^^^^^^^^^^^^

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

行オリジンは、\ ``track_commit_timestamp = on``
の場合にのみ使用できます。

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

元の情報は、行がフリーズされた時点まででのみ利用できます。凍結された後に行に到着する更新は競合を発生させないため、すべての場合に適用されます。これは\ ``bdr_init_physical``
によって新しいノードを追加する場合の通常のケースであるため、競合が発生すると、その場合に多くの偽陽性の結果が発生します。

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

 :ref:`Node restart and down node recovery <Node restart and down node recovery>` で説明しているように、長時間の停止のためにノードをダウンしたままにしないことをお勧めします。

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億に設定されている場合、1000TPSを実行するノードは、競合データが削除されるまで5.5日強の間ダウンする可能性があります。
CommitTSデータ構造は、その設定で5
GBのディスク上の領域を必要とするため、トランザクションレートの低いシステムはより低い設定からメリットを得ることができます。

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

.. code:: sql

   #  1 billion = 10GB

   autovacuum_freeze_max_age = 1000000000

   vacuum_freeze_min_age = 500000000

   #  90% of autovacuum_freeze_max_age

   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つのことを有効にする必要があります。これらの手順のいずれかが正しく実行されない場合、
 :ref:`オリジンの競合検出 <オリジンの競合検出>` が使用されます。

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

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

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

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

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

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

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

bdr.alter_table_conflict_detection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

テーブルの所有者は、特定のテーブルの競合検出がどのように動作するかを変更できます。

概要
^^^^

.. code:: sql

   bdr.alter_table_conflict_detection(relation regclass,
                                      method text,
                                      column_name name DEFAULT NULL)

パラメーター
^^^^^^^^^^^^

-  ``relation`` —新しい競合検出方法を設定する関係の名前。

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

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

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

-  ``row_origin`` —タプルに加えられた以前の変更のオリジン
   :ref:`オリジンの競合検出 <オリジンの競合検出>` を参照してください。これは、テーブルに追加の列を必要としないサポートされている唯一の方法です。

-  ``row_version`` —行バージョン列 :ref:`行バージョンの競合検出 <行バージョンの競合検出>` を参照してください。

-  ``column_commit_timestamp`` —列ごとのコミットタイムスタンプ
   :ref:`CLCD <Column-level conflict detection>` を参照してください。

-  ``column_modify_timestamp`` —列ごとの変更タイムスタンプ
   :ref:`CLCD <Column-level conflict detection>` で説明

注
^^

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

このファンクションは、 ``DDL``
ステートメントと同じレプリケーションメカニズムを使用します。これは、レプリケーションが :ref:`ddl filters <Replication sets>` 構成の影響を受けることを意味します。

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

このファンクションはトランザクションです。トランザクションの\ ``ROLLBACK``
を使用してエフェクトをロールバックできます。変更は現在のトランザクションに可視されます。

``bdr.backwards_compatibility`` が30618以下に設定されていない限り、
``relation`` の所有者のみが\ ``bdr.alter_table_conflict_detection``
ファンクションを実行できます。

..  Warning::
   追加の列を使用してメタデータを保存する方法から競合検出方法を変更すると、その列がドロップされます。

..  Warning::
   このファンクションは、`bdr.camo_enable_client_warnings` で警告が無効になっていない限り、 CAMOを無効にし、警告を発します。

競合タイプのリスト
^^^^^^^^^^^^^^^^^^

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

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

-  ``update_differing``
   —着信更新のキー行はローカル行とは異なります。これは、
   :ref:`行バージョンの競合検出 <行バージョンの競合検出>` を使用する場合にのみ発生する可能性があります。

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

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

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

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

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

-  ``delete_recently_updated`` —現在のノードまたは
   :ref:`行バージョンの競合検出 <行バージョンの競合検出>` を使用する場合の行の最新の更新よりも古いコミットタイムスタンプを含む着信削除。

-  ``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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

この機能は、特定のノードでの競合解決の動作を設定します。

.. _概要-1:

概要
^^^^

.. code:: sql

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

.. _パラメーター-1:

パラメーター
^^^^^^^^^^^^

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

-  ``conflict_type`` —設定を適用する競合タイプ
   :ref:`競合タイプのリスト <競合タイプのリスト>` を参照してください。

-  ``conflict_resolver`` —指定された競合タイプに使用するリゾルバー
   :ref:`競合リゾルバーのリスト <競合リゾルバーのリスト>` を参照してください。

.. _注-1:

注
^^

現在、ローカルノードのみを変更できます。ファンクション呼び出しはレプリケートされません。複数のノードの設定を変更する場合は、各ノードでファンクションを実行する必要があります。

このファンクションによる構成変更は、 ``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``
   競合リゾルバーは、同じ名前のテーブルがドロップされた後すぐに再作成される場合、課題を引き起こす可能性があります。その場合、ノードのいずれかが、テーブルを再作成する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``
   —オリジンから送信された利用可能な情報から新しい行を構築し、挿入しようとします。完全な行を構築するために利用できる十分な情報がない場合、エラーをスローし、レプリケーションを停止します。
   ``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``
競合タイプは、
 :ref:`競合トリガー <競合トリガー>` を使用するユーザー定義ロジックでも解決できます。

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

.. raw:: latex

    \begin{landscape}

.. csv-table::
  :header: "",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
  :widths: 6,4,4,4,4,4,4,4,4,4,4,4,4
  :align: left
  :class: longtable

  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

.. raw:: latex

    \end{landscape}


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

.. csv-table::
  :header: Conflict type,Resolver
  :widths: 50,50
  :align: left
  :class: longtable

  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 (see note),skip_if_recently_dropped
  apply_error_ddl,error


..  note target_table_missing::
   この競合タイプは、コミュニティPostgresqlでは検出されません。ターゲットテーブルが存在しない場合、エラーが発生し、レプリケーションを停止します。 EDB Postgresサーバーは、欠落しているターゲットテーブルを検出して処理し、リゾルバーを呼び出すことができます。 !!!

競合解決のリスト
^^^^^^^^^^^^^^^^

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

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

-  ``apply_remote`` —リモート着信行が適用されました。

-  ``skip``
   —行の処理はスキップされましたローカルでの変更は行われませんでした。

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

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

競合ログ
--------

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

bdr.alter_node_set_log_config
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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

.. _概要-2:

概要
^^^^

.. code:: sql

   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)

.. _パラメーター-2:

パラメーター
^^^^^^^^^^^^

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

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

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

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

-  ``conflict_resolution`` —ログへの競合の解決。
   NULLデフォルトはすべてを意味します。

.. _注-2:

注
^^

ローカルノードのみを変更できます。ファンクション呼び出しはレプリケートされません。複数のノードの設定を変更する場合は、各ノードでファンクションを実行する必要があります。

このファンクションはトランザクションです。変更はロールバックできます。それらは現在のトランザクションに表示されます。

競合ログ構成のリスト
^^^^^^^^^^^^^^^^^^^^

``bdr.node_log_config``
ビューには、すべてのロギング構成が表示されます。ここには、ロギング構成の名前、ログの場所、およびログに記録される競合のタイプと解決がリストされています。

テーブルへの競合のロギング
^^^^^^^^^^^^^^^^^^^^^^^^^^

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

このテーブルは列\ ``local_time``
でレンジパーティション化されています。テーブルはオートパーティションで管理されています。デフォルトでは、新しいパーティションは毎日作成され、過去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``
と呼ばれるカタログビューにアクセスできます。これにはユーザーデータが含まれず、ログに記録される競合の監視ができます。

競合報告
^^^^^^^^

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

.. code:: sql

   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は、有用なサイズのバッチで行を比較することにより、オーバーヘッドを回避します。

違いが見つかった場合は、時間の経過とともに再チェックできるため、最終的な一貫性の遅延が発生します。

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