Commit At Most Once¶
Commit At Most Once(CAMO)機能の目的は、アプリケーションが複数回コミットするのを防ぐことです。
CAMOがないと、 COMMIT が送信された後にクライアントが接続を失うと、アプリケーションはサーバーから応答を受信しないため、トランザクションがコミットされたかどうかがわかりません。
アプリケーションは、次の2つのオプションのいずれかを簡単に決定できません。
データが2回入力される場合があるため、同じデータでトランザクションを再試行します
トランザクションを再試行せず、データがまったく処理されないリスク
これらはいずれも、価値の高いデータに関する重大なエラーです。
このシチュエーションを回避make1つの方法は、トランザクションが一意インデックスを持つテーブルに少なくとも1つの
INSERT
を含めることですが、これはアプリケーションのデザインに依存し、アプリケーション固有のエラー処理ロジックが必要であるため、すべての場合。
BDRのCAMO機能は、より一般的な解決策を提供し、 INSERT
を必要としません。 bdr.enable_camo またはbdr.commit_scope
によってアクティブ化されると、アプリケーションは、既に割り当てられている場合、トランザクション識別子を含むメッセージを受け取ります。それ以外の場合、トランザクションの最初の
write
ステートメントはその情報をクライアントに送信します。アプリケーションが明示的な
COMMIT を送信する場合、プロトコルは、 COMMIT
が送信される前にアプリケーションがトランザクション識別子の通知を受信することを保証します。サーバーがCOMMITに応答しない場合、アプリケーションはトランザクション識別子を使用して別のBDRノードにトランザクションの最終ステータスを要求することにより、このエラーをハンドルできます。以前のトランザクションのステータスがわかっている場合、アプリケーションはトランザクションをリトライするかどうかを安全に決定できます。
CAMOは2つのモードのいずれかで動作します。
ペアモード
Eager All Node Replicationあり
ペアモードでは、CAMOは同じトップレベルのBDRグループからの2つのBDRマスタノードであるパートナーノードのペアを作成することによって機能します。このオペレーションモードでは、ペアの各ノードは他の同等なで実行された最近のトランザクションの結果を知っており、特に(必要に応じて) COMMIT 中に切断されたトランザクションの結果を知っています。アプリケーションからトランザクションを受信するノードは「オリジン」と呼ばれ、これらのトランザクションを確認するノードは「パートナー」と呼ばれます。ただし、CAMOペアのノードのCAMO構成に違いはありません。ペアは対称です。
Eager All-Node Replicationを使用したCAMO と組み合わせると、CAMOはすべての同等な(つまり、完全なBDRマスタノード)がCAMOパートナーとして機能できるようにします。このモードでは、指定されたCAMOパートナーを構成しないでください。
警告
CAMOで高度なエラーハンドリングを利用するには、ユーザーのアプリケーションを変更する必要があります。保護を得るには、パラメータを有効にするだけでは不十分です。リファレンスクライアントの実装は、要求に応じて顧客に提供されます。
要件¶
CAMOを使用するには、アプリケーションは明示的なCOMMITメッセージを別の要求として発行する必要があります(マルチステートメント要求のパートとしてではありません)。 CAMOは、プロシージャまたは暗黙的なコミットを使用する単一ステートメントのトランザクションから発行されたトランザクションのステータスを提供できません。
構成¶
既存のBDRクラスターがノード node1 および node2
で構成されていると仮定します。両方のノートはbdrdemo
というBDR対応データベースのパートであり、両方とも同じノードグループmygroup
のパートです。ノードを相互にCAMOパートナーとして構成できます。
1.ノードnode1 およびnode2 がmygroup
ノードグループのパートであるBDRクラスターを作成します。
1つのノードでファンクション
bdr.add_camo_pair()を実行します。
SELECT bdr.add_camo_pair(mygroup, node1, node2);
1.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_writeremote_commit_asyncremote_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保護からローカルモードへのスイッチは、コミットがトランザクションを超えるか、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 SYSTEMVACUUM
*パラメータなしのCLUSTER
ALTER TABLE DETACH PARTITION CONCURRENTLYALTER TYPE [enum] ADD VALUEALTER SYSTEMCREATEおよびDROP DATABASECREATEおよびDROP TABLESPACEALTER DATABASE [db] TABLESPACE
CAMOの制限¶
CAMOは、オリジンノードで最近失敗したCOMMITの結果をクエリーするように設計されているため、切断された場合は、CAMOパートナーからトランザクションステータスをすぐに要求するようにアプリケーションをコードします。障害が発生してからステータスを要求するまでの遅延をできるだけ短くします。アプリケーションは、15分以上保存されるCAMOの決定に依存してはなりません。
アプリケーションが、例リスタートの結果として、割り当てられたグローバル識別子を忘れた場合、それを回復する簡単な方法はありません。したがって、アプリケーションは、未処理のトランザクションが終了するのを待ってからシャットダウンすることをお勧めします。
クライアントが適切なチェックを適用するには、CAMOによって保護されたトランザクションを暗黙的なトランザクション制御を持つ単一のステートメントにすることはできません。トランザクション制御プロシージャまたはトランザクションをスタートまたは終了しようとする
DOブロックでCAMOを使用することもできません。CAMOはコミットステータスを解決しますが、コミットで保留中の通知をまだ解決していません。 CAMOおよびEagerレプリケーションオプションでは、
NOTIFYSQLコマンドまたはpg_notify()ファンクションは許可されません。LISTENまたはUNLISTENも許可しません。変更を再生するとき、CAMOトランザクションは他のトランザクションと同じように競合を検出する場合があります。タイムスタンプの競合検出を使用する場合、CAMOトランザクションはオリジンノードの準備のタイムスタンプを使用します。これは、トランザクションがオリジンノード自分自身で可視になる前です。
パフォーマンスへの影響¶
CAMOは、コミット時にメッセージのラウンドトリップを追加することにより、 Postgresレプリケーションプロトコルを拡張します。アプリケーションは、非同期レプリケーションよりもコミットのレイテンシ時間が長くなります。これは、主に関連するノード間のラウンドトリップ時間によって決まります。同時セッションの数を増やすと、並列処理が増えて、適切なトランザクションスループットがヘルプられます。
トランザクションを確認するCAMOパートナーは、トランザクション状態を保存する必要があります。非CAMOオペレーションと比較して、これには、オリジンから適用されるトランザクションごとに追加のシークが必要になる場合があります。
クライアントアプリケーションのテスト¶
クライアントサイドでCAMOを適切に使用することは簡単ではありません。ノードのクラッシュやネットワークの停止などの障害シナリオに対して、 BDRクラスターを使用してアプリケーションの動作をテストすることを強くお勧めします。
CAMOとグループコミット¶
CAMOは現在 CAMOとグループコミット では動作しません。