Commit At Most Once
===================

コミットスコープの種類 ``CAMO``

概要
----

Commit At Most Once
CAMO機能の目的は、アプリケーションが複数回コミットしないようにすることです。

CAMOがないと、 ``COMMIT``
が送信された後にクライアントが接続を失うと、アプリケーションはサーバーから応答を受け取らない場合があるため、トランザクションがコミットしたかどうかがわかりません。

アプリケーションは、次の2つのオプションの間を簡単に決定できません。

-  場合によっては、データが2回入力される可能性があるため、同じデータでトランザクションを再試行します。

-  トランザクションを再試行せず、データがまったく処理されないリスク

これらはいずれも、高値のデータに関する重大なエラーです。

この状況を回避する1つの方法は、トランザクションの一意のインデックスを持つテーブルに少なくとも1つの\ ``INSERT``
を含めることです。ただし、これはアプリケーションのデザインに依存し、アプリケーション固有のエラー処理ロジックが必要であるため、すべての場合に効果的ではありません。

PGDのCAMO機能は、より一般的なソリューションを提供し、\ ``INSERT``
を必要としません。 ``bdr.commit_scope``
によってアクティブ化されると、アプリケーションはトランザクション識別子が既に割り当てられている場合を含むメッセージを受信します。それ以外の場合、トランザクションの最初のwriteステートメントはその情報をクライアントに送信します。

アプリケーションが明示的な\ ``COMMIT``
を送信する場合、プロトコルは、\ ``COMMIT``
が送信される前にアプリケーションがトランザクション識別子の通知を受信することを保証します。サーバーが\ ``COMMIT``
に応答しない場合、アプリケーションはトランザクション識別子を使用して別のPGDノードからトランザクションの最終ステータスを要求することにより、このエラーを処理できます。以前のトランザクションステータスがわかっている場合、アプリケーションはトランザクションを再試行するかどうかを安全に決定できます。

CAMOは、同じPGDグループからの2つのPGDノードであるパートナーノードのペアを作成することにより機能します。この操作モードでは、ペアの各ノードは、他のピアで実行された最近のトランザクションの結果を知っており、特にニーズに応じて、\ ``COMMIT``
中に切断されたトランザクションの結果を知っています。アプリケーションからトランザクションを受信するノードは「オリジン」と呼ばれ、これらのトランザクションを確認するノードは「パートナー」と呼ばれます。ただし、
CAMOペアのノードのCAMO構成に違いはありません。ペアは対称です。

..  Warning::
   CAMOでは、高度なエラー処理を利用するには、ユーザーのアプリケーションを変更する必要があります。パラメーターを有効にするだけでは保護を得るには十分ではありません。リファレンスクライアント実装は、要求に応じてお客様に提供されます。 !!!

..  Note::
   `CAMO` コミットスコープ種類のほとんどは、追加の`DEGRADE ON` 句を備えた`GROUP COMMIT (transaction_tracking = true, commit_decision = partner)` のエイリアスです。 !!!

要件
----

CAMOを使用するには、アプリケーションはマルチステートメント要求の一部としてではなく、別の要求として明示的な\ ``COMMIT``
メッセージを発行する必要があります。
CAMOは、プロシージャーまたは暗黙的なコミットを使用する単一ステートメントトランザクションから発行されたトランザクションのステータスを提供できません。

構成
----

CAMOの構成は、 :ref:`コミットスコープ <コミットスコープ>` を介して発生します。

確認
----

.. csv-table::
  :header: Confirmation&nbsp;Level,CAMO Handling
  :widths: 10,30
  :align: left
  :class: longtable

  `received`,"Not applicable, only uses the default `VISIBLE`."
  `replicated`,"Not applicable, only uses the default `VISIBLE`."
  `durable`,"Not applicable, only uses the default `VISIBLE`."
  `visible` (default),Confirms the transaction after all of its changes are flushed to disk and it's visible to concurrent transactions.

制限事項
--------

 :ref:`Limitations <Limitations>` セクションのCAMOセクションを参照してください。

障害シナリオ
------------

さまざまな構成では、さまざまな障害シナリオが発生します。

レシーバー側のデータの永続性
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

デフォルトでは、PGLライタはリモートノードからトランザクションを適用するときに\ ``bdr.synchronous_commit = off``
モードで動作します。これはCAMOにも当てはまります。つまり、トランザクションは、
CAMOパートナーのディスクに到達する前に、オリジンノードに確認されます。クラッシュまたはハードウェア障害が発生した場合、確認されたトランザクションはCAMOパートナーで自分自身では回復できない場合があります。これは、
CAMOパートナーノードが復旧するとトランザクションを再配布するため、
CAMO起点ノードが動作している限り問題ではありません。

これは、
CAMOが単一ノードの障害から保護できることを意味します。これは、ローカルモードおよびリモート書き込みと同様に、またはそれと組み合わせて正しいです。

CAMOペアの両方のノードの停止をカバーするには、
``bdr.synchronous_commit = local``
を使用して、コミット前の確認の前にフラッシュを強制できます。これは、リモート書き込みモードでもローカルモードでも動作せず、レイテンシーの影響を受けやすいコミットパスにあるCAMOパートナーのI/O要件により、パフォーマンスに影響を与えます。

非同期モード
^^^^^^^^^^^^

コミットスコープで\ ``DEGRADE ON ... TO ASYNC``
句が使用される場合、ノードはCAMOパートナーの準備ができているかどうかを検出します。そうでない場合、一時的に非同期ローカルモードに切り替わります。このモードの場合、ノードはCAMOモードに戻すまで、ローカルにトランザクションをコミットします。

これでは、
COMMITステータスを取得することはできませんが、一貫性よりも可用性を選択できます。このモードは、単一ノードの障害を許容できます。
CAMOペアの両方のノードに障害が発生した場合、可用性を維持するために一致しないコミット決定を選択する可能性があり、データの不一貫性が発生します。

CAMOパートナーが準備ができているように切り替えるには、接続する必要があり、推定キャッチアップインターバルは\ ``TO ASYNC``
の\ ``timeout`` 値を下回る必要があります。
 :ref:`bdr.is_camo_partner_ready() <System functions>` を使用してCAMOパートナーの現在の準備状況を確認できます。
 :ref:`bdr.node_replication_rates <bdr.node_replication_rates>` は、キャッチアップ時間の現在の推定値を提供します。

CAMO保護から非同期モードへのスイッチは、コミットが\ ``TO ASYNC``
の\ ``timeout``
値を超えるため、またはCAMOパートナーが既に知られている場合、コミット時に切断されるため、実際のCAMOトランザクションによってのみトリガーされます。このスイッチは、推定キャッチアップインターバルとは無関係です。
CAMOペアが、現在のノードが\ ``enable_proxy_routing``
ノードグループオプションで構成されたグループの書き込みリードであることを必要とするように構成されている場合。構文については、
 :ref:`コミットスコープ <コミットスコープ>` を参照してください。これにより、分離されたノードが非同期モードに切り替わらないことによるスプリットブレイン状況を防ぐことができます。
CAMOグループに\ ``enable_proxy_routing``
が設定されていない場合、オリジンノードはすぐに非同期モードに切り替わります。

非同期モードからCAMOモードへの切り替えは、接続を開始するCAMOパートナーノードに依存します。
CAMOパートナーは、少なくとも30秒ごとに再接続を試行します。したがって、接続が再確立された後、
CAMOパートナーが元のノードに接続するまでに最大30秒かかる場合があります。
CAMOパートナーに蓄積された遅延は、
CAMO保護モードへの切り替えをさらに遅延させます。

通常のCAMOオペレーション中とは異なり、非同期モードでは、コミットオーバーヘッドの追加はありません。これは、
CAMOペアが通常処理できるよりも多くのトランザクションをノードが継続的に処理できるため、問題になる可能性があります。
CAMOパートナーが最終的に再接続してトランザクションを適用した場合でも、このような状況では遅延が増加するだけであり、
CAMO保護の再確立を妨げます。トランザクションのスループットを人為的に調整するために、PGDは :ref:`bdr.camo_local_mode_deploy <bdr.camo_local_mode_deploy>` 設定を提供します。これにより、ローカルモードで\ ``COMMIT``
を任意の時間遅延できます。予想されるワークロード中に通常のCAMOモードでコミット時間を測定し、それに応じてこの遅延を構成することをお勧めします。デフォルトは5ミリ秒で、これは非同期ネットワークと比較的迅速なCAMOパートナーの応答を反映しています。

アーキテクチャと可用性要件を考慮して、非同期モードを許可するかどうかの選択を検討します。次の例は、詳細を提供します。

例
^^

この例では、互いにCAMOパートナーである2つのPGDノードを使用したセットアップを検討します。

edb_notranlate_0
このCAMOコミットスコープが正当であるためには、グループ内のノードの数が正確に2である必要があります。複数のノードで構成されるグループでALLまたはANY
2を使用すると、非定量化グループ式が明確なペアに解決されないため、エラーになりますノードの。

非同期モードの場合
^^^^^^^^^^^^^^^^^^

非同期モードが許可されている場合、単一障害点はありません。
1つのノードに障害が発生した場合

-  他のノードは、障害が発生したノードで\ ``COMMIT``
   中に切断されたすべてのトランザクションのステータスを判断できます。

-  新しい書き込みトランザクションが許可されます。
   2番目のノードにも障害が発生した場合、その時点でコミットされていたトランザクションの結果は不明です。

非同期モードなし
^^^^^^^^^^^^^^^^

非同期モードが許可されない場合、各ノードはトランザクションをコミットするために他のノードを必要とします。つまり、各ノードは単一障害点です。
1つのノードに障害が発生した場合

-  他のノードは、障害が発生したノードで\ ``COMMIT``
   中に切断されたすべてのトランザクションのステータスを判断できます。

-  ノードが復旧するまで、新しい書き込みトランザクションは防止されます。

アプリケーションの使用
----------------------

概要と要件
^^^^^^^^^^

CAMOは、再試行ループとクライアント側での特定のエラー処理に依存しています。それには3つの側面があります。

-  トランザクションの\ ``COMMIT``
   の結果を確認する必要があり、一時的なエラーが発生した場合、クライアントはトランザクションを再試行する必要があります。

-  ``COMMIT``
   の前に、クライアントはノードIDとトランザクションID両方32ビット整数で構成されるトランザクションのグローバル識別子を取得する必要があります。

-  トランザクションの\ ``COMMIT``
   を試行中に現在のサーバーに障害が発生した場合、アプリケーションはCAMOパートナーに接続し、そのトランザクションのステータスを取得し、応答に応じて再試行する必要があります。

アプリケーションは、 ``COMMIT``
中に切断された場合にトランザクションステータスを確認する目的でのみグローバルトランザクション識別子を保存する必要があります。特に、アプリケーションは別の永続レイヤーを必要としません。アプリケーションに障害が発生した場合、データベースの情報のみを再起動します。

これを説明するために、Cのような擬似コードで書かれたCAMO対応クライアントアプリケーションの再試行ループの例を示します。接続情報を提供する2つのDSN
``origin_dsn`` および\ ``partner_dsn``
を想定しています。これらは通常、\ ``bdr.create_node``
への最初の呼び出しに使用されるDSNと同じであり、 ``bdr.node_summary``
、\ ``interface_connstr`` 列でルックアップできます。

::

   PGconn *conn = PQconnectdb(origin_dsn);

プロセスは、オリジンノードへの接続を開始します。ループに入ります。

::

   loop {
       PQexec(conn, "BEGIN");

トランザクションを開始し、変更の入力を開始します。

::

       PQexec(conn, "INSERT INTO ...");
       ...

完了したら、ローカルノードIDとトランザクションIDの記録を作成する必要があります。どちらもパラメーターとして使用できます。

::

       node_id = PQparameterStatus(conn, "bdr.local_node_id");
       xid = PQparameterStatus(conn, "transaction_id");

これで、試してコミットする準備が整いました。

::

       PQexec(conn, "COMMIT");
       if (PQresultStatus(res) == PGRES_COMMAND_OK)
           return SUCCESS;

結果が\ ``PGRES_COMMAND_OK``
の場合、問題はなく、次に進む準備ができています。しかし、そうでない場合は、
CAMOを使用してトランザクションを完了まで追跡する必要があります。最初に尋ねる必要がある質問は、接続が悪かったかということです。

::

       else if (PQstatus(res) == CONNECTION_BAD)
       {

接続が悪い場合は、
CAMOパートナーノードをチェックして、トランザクションがそこに到達したかどうかを確認できます。

::

           conn = PQconnectdb(partner_dsn);
           if (!connectionEstablished())
               panic();

パートナーノードに接続できない場合、できることはあまりありません。したがって、私たちはパニックになるか、同様の行動をとるべきです。

しかし、接続できれば、
 :ref:`bdr.logical_transaction_status() <System functions>` を使用して、トランザクションがどのように発生したかを知ることができます。トランザクションをコミットする直前に、必要な値node_idおよびxid-トランザクションIDを記録しました。

::

           sql = "SELECT bdr.logical_transaction_status($node_id, $xid)";
           txn_status = PQexec(conn, sql);
           if (txn_status == "committed")
               return SUCCESS;
           else
               continue;   // to retry the transaction on the partner
       }

トランザクションがコミットされたと報告する場合、このトランザクションは成功したと言えます。これ以上のアクションは必要ありません。一方、コミットされたことを報告しない場合は、ループを続行して、パートナーノードでトランザクションを再試行できるようにする必要があります。

::

       else
       {
           if (isPermanentError())
               return FAILURE;
           else
           {
               sleep(increasing_retry_delay);

               continue;
           }
       }
   }

トランザクションのステータスが成功でなかった場合、または接続が悪かった場合、問題が永続的なエラーであったかどうかを確認します。その場合、トランザクションの失敗を報告する必要があります。それ以外の場合、再試行は可能であり、コードは再試行のたびに増加する期間スリープしてから、トランザクションを再試行する必要があります。

CAMOパートナーとの連携
^^^^^^^^^^^^^^^^^^^^^^

ファンクション :ref:`bdr.is_camo_partner_connected() <System functions>` を使用すると、ペアモードで構成されたCAMOパートナーノードの接続ステータスを確認できます。現在、
Eager Replicationで使用されるCAMOに相当するものはありません。

CAMOパートナーの準備ができていることを確認するには、ファンクション
 :ref:`bdr.is_camo_partner_ready <bdr.is_camo_partner_ready>` 
を使用します。その下で、これはローカルモードへの、またはローカルモードからのスイッチをトリガーします。

設定されたCAMOパートナーの詳細を確認するには、
 :ref:`bdr.get_configured_camo_partner() <System functions>` を使用します。これにより、ローカルノードのCAMOパートナーが返されます。

ファンクション :ref:`bdr.wait_for_camo_partner_queue() <System functions>` 
を使用して、camoパートナーがキューを処理するのを待機できます。このファンクションは
 :ref:`bdr.wait_for_apply_queue <bdr.wait_for_apply_queue>` のラッパーであり、違いは :ref:`bdr.wait_for_camo_partner_queue() <System functions>` 

デフォルトではCAMOパートナーノードを照会します。ローカルノードがCAMOペアの一部でない場合、エラーを返します。

ノードに障害が発生したときにコミットされていたトランザクションのステータスを確認するには、アプリケーションは
 :ref:`bdr.logical_transaction_status <bdr.logical_transaction_status>`  ファンクションを使用する必要があります。

このファンクションに、チェックするトランザクションのnode_idおよびtransaction_idを渡します。ペアモードでCAMOを使用する場合、
CAMOペアの一部であるノードでのみこの機能を使用できます。 Eager
Replicationと併せて、すべてのノードで使用できます。

すべての場合、コミットが発行されてから15分以内にファンクションを呼び出す必要があります。
CAMOパートナーはこのようなメタ情報を定期的にパージする必要があるため、古いトランザクションに正しい回答を提供できません。

トランザクションのステータスを照会する前に、このファンクションは、受信キューが消費され完全に適用されるまで待機します。このメカニズムは、受信されたがまだ適用されていないトランザクションの早期の否定的な回答を防ぎます。

その名前にもかかわらず、それは常に読み取り専用操作であるわけではありません。ステータスが不明な場合、
CAMOパートナーはトランザクションをコミットするか中止するかを決定し、その決定をローカルに保存して、今後の一貫性を保証します。

クライアントは、オリジンでコミットする前にこのファンクションを呼び出してはなりません。そうしないと、トランザクションが強制的にロールバックされる可能性があります。

接続プールとプロキシ
^^^^^^^^^^^^^^^^^^^^

CAMOクラスターをデザインするときは、接続プールとプロキシの効果を考慮してください。プロキシは、コミットグループ内のすべてのノード、つまりCAMOペアの両方のノード、またはEager
All-Node
Replicationの場合はすべてのPGDノードにトランザクションを自由に分散することができます。

アプリケーションが適切なノードIDを取得するように注意してください。セッションプーリングを使用する場合、クライアントは同じノードに接続されたままであるため、ノードIDはクライアントセッションの有効期間を通じて一定のままです。ただし、より詳細なトランザクションプーリングでは、クライアントはすべてのトランザクションのノードIDを取得する必要があります次の例のように。

PGDノードに直接接続されていないクライアントは、フェールオーバーまたはスイッチオーバーに気付かない場合があります。ただし、
``bdr.local_node_id``
パラメーターを使用して、現在接続しているノードを判断することができます。
COMMIT中の切断という重要な状況では、プロキシはその切断をエラーとしてCAMOプロトコルを適用するクライアントに適切に転送する必要があります。

``received`` モードのCAMOの場合、
CAMOペア間を切り替える可能性のあるプロキシは、
``bdr.wait_for_camo_partner_queue``
ファンクションを使用して、古い読み取りを防ぐ必要があります。

CAMOの制限
----------

CAMOの制限については、 :ref:`Durability limitations <Limitations>` ページでカバーされています。

パフォーマンスへの影響
----------------------

CAMOは、コミット時にメッセージのラウンドトリップを追加することにより、Postgresレプリケーションプロトコルを拡張します。アプリケーションのコミットレイテンシーは非同期レプリケーションの場合よりも高く、これは主に、関係するノード間の往復時間によって決まります。同時セッションの数を増やすと、並列処理が増加して、合理的なトランザクションスループットを取得できます。

トランザクションを確認するCAMOパートナーは、トランザクションの状態を保存する必要があります。非CAMOオペレーションと比較して、これは、オリジンから適用される各トランザクションの追加シークが必要になる場合があります。

クライアントアプリケーションのテスト
------------------------------------

クライアント側でCAMOを適切に使用することは簡単ではありません。ノードのクラッシュやネットワークの停止などの障害シナリオに対して、PGDクラスターでアプリケーションの動作をテストすることを強くお勧めします。
