Commit At Most Once

Commit At Most Once (CAMO)機能の目的は、アプリケーションが複数回コミットするのを防ぐことです。

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

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

  • データが2回入力される場合があるため、同じデータでトランザクションを再試行します

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

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

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

BDRのCAMO機能は、より一般的な解決策を提供し、 INSERT を必要としません。 bdr.enable_camo またはbdr.commit_scope によってアクティブ化されると、アプリケーションは、既に割り当てられている場合、トランザクション識別子を含むメッセージを受け取ります。それ以外の場合、トランザクションの最初の書き込みステートメントはその情報をクライアントに送信します。アプリケーションが明示的な COMMIT を送信する場合、プロトコルは、 COMMIT が送信される前にアプリケーションがトランザクション識別子の通知を受信することを保証します。サーバーがCOMMITに応答しない場合、アプリケーションはトランザクション識別子を使用して別のBDRノードにトランザクションの最終ステータスを要求することにより、このエラーを処理できます。以前のトランザクションのステータスがわかっている場合、アプリケーションはトランザクションを再試行するかどうかを安全に決定できます。

CAMOは2つのモードのいずれかで動作します。

  • ペアモード

  • Eager All Node Replicationあり

ペアモードでは、 CAMOは同じトップレベルのBDRグループの2つのBDRマスターノードであるパートナーノードのペアを作成することによって機能します。この動作モードでは、ペアの各ノードは他のピアで実行された最近のトランザクションの結果を知っており、特に(必要に応じて) COMMIT 中に切断されたトランザクションの結果を知っています。アプリケーションからトランザクションを受信するノードを「オリジン」、これらのトランザクションを確認するノードを「パートナー」と呼ぶ場合があります。ただし、 CAMOペアのノードのCAMO構成に違いはありません。ペアは対称です。

CAMOはすべてのピア(つまり、完全なBDRマスターノード)がCAMOパートナーとして機能できるようにします。このモードでは、指定されたCAMOパートナーを構成する必要はありません。

警告

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

要件

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

構成

既存のEDB Postgres分散クラスターがノード node1 およびnode2 で構成されていると仮定します。両方のノートはbdrdemo というBDR対応データベースの一部であり、両方とも同じノードグループmygroup の一部です。ノードを相互にCAMOパートナーとして構成できます。

1.ノードnode1 およびnode2 がmygroup ノードグループの一部であるEDB Postgres分散クラスターを作成します。

1.一方のノードで関数bdr.add_camo_pair() を実行します。

SELECT bdr.add_camo_pair(mygroup, node1, node2);

CAMOが提案するCOMMITエラー処理を使用するようにアプリケーションを調整します。

サーバーレベルでCAMOを有効にすることはお勧めしません。必要ない場合でも、すべてのトランザクションのレイテンシーが高くなります。代わりに、セッションまたはトランザクションレベルでCAMOをオンにして、個々のトランザクションに対して選択的に有効にします。

セッションレベルでCAMEを有効にするには:

SET bdr.enable_camo = remote_commit_flush;

トランザクションを開始してからコミットする前に、個々のトランザクションでCAMOを有効にするには:

SET LOCAL bdr.enable_camo = remote_commit_flush;

CAMOを有効にするbdr.enable_camo の有効な値は次のとおりです。

  • off (デフォルト)

  • remote_write

  • remote_commit_async

  • remote_commit_flush またはon

各モードの動作の詳細については、同期レプリケーションモードの 違いのあるノード間の比較 を参照してください。 bdr.enable_camo = off を設定すると、この機能が無効になります。

Eager All-Node Replicationを使用したCAMO

Eager All-Node ReplicationでCAMOを使用するために、どちらのノードも変更する必要はありません。トランザクションの開始後にグローバルコミットスコープを有効にすれば十分です。 bdr.enable_camo を設定する必要はありません。

BEGIN;
SET LOCAL bdr.commit_scope = global;
...
COMMIT;

指定されたとおりにCOMMITエラー処理を使用するようにアプリケーションを調整する必要がありますが、使用可能なBDRノードに自由に接続して、トランザクションのステータスを照会できます。

障害シナリオ

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

受信側でのデータの永続性

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

これは、 CAMOが単一ノード障害から保護できることを意味します。これは、ローカルモードだけでなく、リモート書き込みとの組み合わせにも適しています。

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

ローカルモード

synchronous_replication_availability = 'async' の場合、ノード(つまり、マスター)はそのCAMOパートナーの準備ができているかどうかを検出します。そうでない場合は、一時的にローカルモードに切り替わります。ローカルモードの場合、ノードはCAMOモードに戻るまでトランザクションをローカルにコミットします。

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

CAMOパートナーが準備完了に切り替えるには、接続する必要があり、推定キャッチアップ間隔がbdr.global_commit_timeout を下回る必要があります。 CAMOパートナーの現在の準備ステータスはbdr.is_camo_partner_ready で確認できますが、bdr.node_replication_rates はキャッチアップ時間の現在の見積もりを提供します。

CAMO保護からローカルモードへの切り替えは、コミットがbdr.global_commit_timeout を超えるか、 CAMOパートナーが既にわかっている場合はコミット時に切断された場合にのみCAMOされます。このスイッチは、推定キャッチアップ間隔とは無関係です。 Raftにローカルモードへの切り替えを必要とするようにCAMOペアが構成されている場合、このスイッチには大部分のノードが動作している必要があります(

bdr.add_camo_pair の require_raft

フラグを参照)。これにより、分離されたノードがローカルモードに切り替わることによるスプリットブレインの状況を防ぐことができます。 CAMOペアにrequire_raft が設定されていない場合、オリジンノードはすぐにローカルモードに切り替わります。

CAMOパートナーへのTCP接続のキープアライブとタイムアウトを制御するPostgreSQL設定を使用して、送信ノードでの検出を構成できます。 wal_sender_timeout は、ノードがローカルモードに切り替えるまでCAMOパートナーを待機する時間です。さらに、 bdr.global_commit_timeout 設定は、 CAMOパートナーに到達できないためにCOMMITが発生する可能性のある最大遅延にトランザクションごとの制限を設定します。 wal_sender_timeout よりも低い場合がありますが、これは同期スタンバイにも影響し、応答性と安定性の良い妥協点を見つける必要があります。

ローカルモードからCAMOモードへの切り替えは、接続を開始するCAMOパートナーノードによって異なります。 CAMOパートナーは、少なくとも30秒ごとに再接続を試行します。したがって、接続が再確立された後、 CAMOパートナーがオリジンノードに接続するまでに最大30秒かかる場合があります。 CAMOパートナーで蓄積された遅延により、 CAMO保護モードへの切り替えがさらに遅れます。

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

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

例:対称ノードペア

この例では、相互にCAMOパートナーである2つのBDRノードを使用したセットアップを検討します。これは、BDR4以降で可能な唯一の構成です。

この構成により、両方のノードでCAMO動作が有効になります。したがって、競合が発生しない可能性が高い場合など、複数のノードで同時に書き込むことが許容されるワークロードパターンに適しています。

ローカルモードの場合

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

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

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

ローカルモードなし

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

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

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

アプリケーション使用

概要と要件

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

*トランザクションのCOMMITの結果を確認する必要があり、一時的なエラーの場合、クライアントはトランザクションをリトライする必要があります。

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

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

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

CAMOペアの追加

ファンクションbdr.add_camo_pair() は、 BDRノードの既存のペアを対称CAMOペアとして構成します。

require_raft オプションは、 synchronous_replication_availability がasync に設定されている場合にローカルモードに切り替える方法と時期を制御し、一般的にそのような切り替えを許可します。

概要

bdr.add_camo_pair(node_group text, left_node text, right_node text,
                  require_raft bool)

注釈

left と`right` の名前には特別な意味はありません。

注釈

BDRバージョン4.0以降、対称CAMO構成のみがサポートされています。つまり、ペアの両方のノードが互いのCAMOパートナーとして機能します。

CAMOペアの構成の変更

ファンクションbdr.alter_camo_pair() を使用すると、 require_raft を切り替えることができます 現在、ペアリングのノードを変更することはできません。代わりに、 bdr.remove_camo_pair の後にbdr.add_camo_pair を使用する必要があります。

概要

bdr.alter_camo_pair(node_group text, left_node text, right_node text,
                    require_raft bool)

CAMOペアの削除

関数bdr.remove_camo_pair() は、2つのノードのCAMOペアを削除し、これらの2つのノードでbdr.enable_camo によるCAMOトランザクションの使用を禁止します。

概要

bdr.remove_camo_pair(node_group text, left_node text, right_node text)

注釈

left と`right` の名前には特別な意味はありません。

CAMOパートナー接続ステータス

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

概要

bdr.is_camo_partner_connected()

戻り値

CAMOパートナーが現在ローカルノード上のWAL送信者プロセスに接続しているため、トランザクションデータを受信して確認を送信できるかどうかを示すブール値。

CAMOパートナーの準備

ファンクションbdr.is_camo_partner_ready を使用すると、ペアモードで構成されたCAMOパートナーノードの準備ステータスを確認できます。その下で、これはローカルモードへの/からの切り替えをトリガーします。

概要

bdr.is_camo_partner_ready()

戻り値

CAMOパートナーがタイムリーに( bdr.global_commit_timeout が期限切れになる前に)ローカルノードから発信されたトランザクションを確認すると合理的に期待できるかどうかを示すブール値。

注釈

この関数は、過去または現在の状態を照会します。正の戻り値は、 CAMOパートナーが将来のトランザクションを確認できるかどうかを示しません。

CAMOパートナーを取得する

この機能は、ローカルノードのCAMOパートナー(ペアモードで構成)を表示します。

bdr.get_configured_camo_partner()

CAMOパートナーからの適用キューが消費されるのを待ちます

関数 bdr.wait_for_camo_partner_queue は、デフォルトでCAMOパートナーノードを照会するbdr.wait_for_apply_queue のラッパーです。ローカルノードがCAMOペアの一部でない場合、エラーが発生します。

概要

bdr.wait_for_camo_partner_queue()

CAMOノード間のトランザクションステータス

この機能により、 CAMOトランザクションが完全に解決されるのを待機できます。

bdr.camo_transactions_resolved()

取引状況照会機能

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

bdr.logical_transaction_status(node_id, xid, require_camo_partner)

ペアモードで使用するCAMOでは、 CAMOペアの一部であるノードでのみこの機能を使用します。 Eagerレプリケーションとともに、すべてのノードで使用できます。

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

トランザクションのステータスを照会する前に、この関数は受信キューが消費されて完全に適用されるのを待ちます。これにより、受信したがまだ適用されていないトランザクションに対する早期の否定的な回答が防止されます。

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

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

概要

bdr.logical_transaction_status(node_id OID,
                               xid     OID,
                               require_camo_partner BOOL DEFAULT true)

パラメーター

  • node_id —トランザクションの発生元のBDRノードのノードID。通常、PQパラメータbdr.local_node_id からCOMMITする前にクライアントによって取得されます。

  • xid —オリジンノードのトランザクションID。通常、PQパラメーター transaction_id からCOMMITする前にクライアントが取得します( enable_camo を on 、remote_write 、remote_commit_async 、またはremote_commit_flush に設定する必要があります。 Commit At Most Once settings を参照)

  • require_camo_partner —デフォルトはtrueで、構成チェックを有効にします。これらのチェックを無効にし、 Eager All-Node Replicationによって保護されたトランザクションのステータスを照会するには、 false に設定します。

戻り値

この関数は、次のいずれかの結果を返します。

  • 'committed'::TEXT —トランザクションはコミットされ、 CAMOペアの両方のノードで表示され、最終的に他のすべてのBDRノードに複製されます。クライアントが再試行する必要はありません。

  • 'aborted'::TEXT —トランザクションは中止されました。他のBDRノードにレプリケートされません。クライアントは、トランザクションを再試行するか、失敗をエスカレートする必要があります。

  • 'in progress'::TEXT —トランザクションはこのローカルノードでまだ進行中であり、まだコミットまたは中止されていません。トランザクションはCOMMITフェーズにあり、 CAMOパートナーがコミットを確認または拒否するのを待っている可能性があります。推奨されるクライアントの反応は、オリジンノードから切断し、 CAMOパートナーに再接続して、代わりに照会することです。間にロードバランサーまたはプロキシがあり、クライアントが照会するノードを制御できない場合、クライアントはステータスが'committed' または'aborted' に切り替わるまでのみ繰り返しポーリングできます。

Eager All-Node Replicationの場合、ピアノードはまだコミットまたは中止されていないトランザクションに対してこの結果を生成します。これは、この場合、まだレプリケートされていない(またはオリジンノードで開始されていない)トランザクションでも、ピアBDRノードでin progress の結果が得られる可能性があることを意味します。ただし、クライアントは、オリジンでコミットする前にトランザクションステータスを照会してはなりません。

  • 'unknown'::TEXT —指定されたトランザクションは、将来のものであるか、その特定のノードにまだレプリケートされていないか、過去すぎるためです。そのようなトランザクションのステータスはまだないか、わかっていません。この戻り値は、クライアントによる不適切な使用の兆候です。

クライアントは、エラー時に関数呼び出しを再試行する準備ができている必要があります。

DDLとグローバルロックとの相互作用

CAMOによって保護されたトランザクションには、 DDL操作を含めることができます。ただし、 DDLはグローバルロックを使用します。これにより、ノード間の同期が既に提供されます。詳細については、 DDLロックの詳細 を参照してください。

CAMOとDDLを組み合わせると、待ち時間が長くなり、グローバルデッドロックが発生する可能性も高くなります。したがって、比較的低いbdr.global_lock_timeout を使用することをお勧めします。これにより、 DDLが中止されるため、妥当な時間でデッドロックが解決されます。

非トランザクションDDL

次のDDL操作はトランザクションブロックで許可されていないため、 CAMO保護のメリットを得られません。これらの場合、 CAMOは内部で自動的に無効になります。

  • すべての同時インデックス操作(CREATE 、DROP 、およびREINDEX )

  • REINDEX DATABASE 、REINDEX SCHEMA 、およびREINDEX SYSTEM

  • VACUUM

  • パラメーターなしのCLUSTER

  • ALTER TABLE DETACH PARTITION CONCURRENTLY

  • ALTER TYPE [enum] ADD VALUE

  • ALTER SYSTEM

  • CREATE およびDROP DATABASE

  • CREATE およびDROP TABLESPACE

  • ALTER DATABASE [db] TABLESPACE

CAMOの制限

CAMOは、オリジンノードで最近失敗したCOMMITの結果を照会するように設計されているため、切断された場合は、 CAMOパートナーからトランザクションステータスをすぐに要求するようにアプリケーションをコーディングします。障害が発生してからステータスを要求するまでの遅延をできるだけ短くします。アプリケーションは、15分以上保存されるCAMOの決定に依存してはなりません。

  • 再起動などの結果、アプリケーションが割り当てられたグローバル識別子を忘れた場合、それを回復する簡単な方法はありません。したがって、アプリケーションは、未処理のトランザクションが終了するのを待ってからシャットダウンすることをお勧めします。

  • クライアントが適切なチェックを適用するには、 CAMOによって保護されたトランザクションを暗黙的なトランザクション制御を持つ単一のステートメントにすることはできません。トランザクション制御プロシージャまたはトランザクションを開始または終了しようとするDO ブロックでCAMOを使用することもできません。

CAMOはコミットステータスを解決しますが、コミット時の保留中の通知はまだ解決していません。 CAMOおよびEagerレプリケーションオプションでは、 NOTIFY SQLコマンドまたはpg_notify() ファンクションは許可されません。 LISTEN またはUNLISTEN も許可しません。

  • 変更を再生するとき、 CAMOトランザクションは他のトランザクションと同じように競合を検出する場合があります。タイムスタンプの競合検出を使用する場合、 CAMOトランザクションはオリジンノードの準備のタイムスタンプを使用します。これは、トランザクションがオリジンノード自分自身で可視になる前です。

  • CAMOは現在、トランザクションストリーミングに対応していません。 CAMOの使用を計画している場合は、トランザクションストリーミングを無効にしてください。これは、グローバルにまたはBDRノードグループで構成できます。

    Transaction Streaming Configuration を参照してください。

パフォーマンスへの影響

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

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

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

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

CAMOとグループコミット

CAMOは現在 CAMOとグループコミット では動作しません。