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構成に違いはありません。ペアは対称です。

警告

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

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

構成#

構成パラメーターについては、 CAMO コミットスコープリファレンスを参照してください。

確認#

Confirmation Level

CAMO handling

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.

制限事項#

制限事項 の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 値を下回る必要があります。

bdr.is_camo_partner_ready() を使用してCAMOパートナーの現在の準備状況を確認できます。 bdr.node_replication_rates は、キャッチアップ時間の現在の推定値を提供します。

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

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

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

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

例#

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

- - create a CAMO commit scope for a group over
- - a definite pair of nodes
SELECT bdr.create_commit_scope(
    commit_scope_name := example_scope,
    origin_node_group := camo_dc,
    rule := ALL (left_dc) CAMO DEGRADE ON (timeout=500ms) TO ASYNC
);

この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();

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

ただし、接続できる場合は、

bdr.logical_transaction_status() を使用して、トランザクションがどのように発生したかを確認できます。コードは、トランザクションをコミットする直前に、必要な値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パートナーとの連携#

彼らに割り当てられたロール。

bdr.is_camo_partner_connected() を使用すると、 CAMOパートナーノードの接続ステータスを確認できます。

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

設定されたCAMOパートナーの詳細を確認するには、

bdr.get_configured_camo_partner() を使用します。このファンクションは、ローカルノードのCAMOパートナーを結果ます。

ファンクション bdr.wait_for_camo_partner_queue() を使用して、 CAMOパートナーがキューを処理するのを待機できます。このファンクションは、 bdr.wait_for_apply_queue のラッパーです。違いは bdr.wait_for_camo_partner_queue()

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

ノードに障害が発生したときにコミットされていたトランザクションのステータスを確認するには、アプリケーションは

bdr.logical_transaction_status() ファンクションを使用する必要があります。

このファンクションに、チェックするトランザクションのnode_idとtransaction_idを渡します。この機能は、 CAMOペアの一部であるノードでのみ使用できます。

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

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

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

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

接続プールとプロキシ#

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

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

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

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

CAMOの制限#

CAMOの制限については Known issues and limitations でカバーされています。

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

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

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

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

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