サブスクライバーノードでローカルにデータが変更された場合でも、データが更新されるという点では、論理レプリケーションは通常のDML操作と同じように振る舞います。
到着したデータが制約に違反すると、レプリケーションは停止します。
これは、コンフリクトと呼ばれます。
UPDATEあるいはDELETE操作をレプリケーションする場合は、存在しないデータもコンフリクトとみなされますが、エラーにはならずそのような操作は単にスキップされます。
次のコンフリクトの場合、追加のログが出力され、統計情報が収集されます(pg_stat_subscription_statsビューに出力されます)。
insert_exists #
NOT DEFERRABLEな一意性制約に違反する行を挿入しています。
競合するキーのオリジンやコミットタイムスタンプの詳細をログ出力するためには、サブスクライバーでtrack_commit_timestampを有効にする必要があります。
この場合、コンフリクトが手動で解決されるまで、エラーが報告されます。
update_origin_differs #
以前別のオリジンによって変更された行を更新しています。
このコンフリクトは、サブスクライバーでtrack_commit_timestampを有効にしている場合にのみ検出されます。
現在のところ、更新はローカル行のオリジンに関係なく常に適用されます。
update_exists #
行の更新された値がNOT DEFERRABLEな一意性制約に違反しています。
競合するキーのオリジンやコミットタイムスタンプの詳細をログ出力するためには、サブスクライバーでtrack_commit_timestampを有効にする必要があります。
この場合、コンフリクトが手動で解決されるまで、エラーが報告されます。
パーティションテーブルを更新するときに、更新した行が別のパーティション制約を満たし、その結果行が別のパーティションに挿入される場合、新しい行がNOT DEFERRABLEな一意性制約に違反すると、insert_existsコンフリクトが検出される可能性があることに注意してください。
update_missing #更新対象の行が見つかりませんでした。 このシナリオでは更新は単純にスキップされます。
delete_origin_differs #
以前別のオリジンによって変更された行を削除しています。
このコンフリクトは、サブスクライバーでtrack_commit_timestampを有効にしている場合にのみ検出されます。
現在のところ、削除はローカル行のオリジンに関係なく常に適用されます。
delete_missing #削除対象の行が見つかりませんでした。 このシナリオでは削除は単純にスキップされます。
multiple_unique_conflicts #
挿入または更新行が、複数のNOT DEFERRABLEな一意性制約に違反しています。
このコンフリクトは、サブスクライバーでtrack_commit_timestampを有効にしている場合にのみ検出されます。
この場合、コンフリクトが手動で解決されるまで、エラーが報告されます。
排他制約違反など、他にもコンフリクトのシナリオが存在することに注意してください。 現在のところ、ログにはこれらのコンフリクトに関する詳細情報は出力されません。
論理レプリケーションコンフリクトのログフォーマットは以下の通りです。
LOG: conflict detected on relation "schemaname.tablename": conflict=conflict_typeDETAIL:detailed_explanation. {detail_values[; ... ]}. wheredetail_valuesis one of:Key(column_name[, ...])=(column_value[, ...])existing local row[(column_name[, ...])=](column_value[, ...])remote row[(column_name[, ...])=](column_value[, ...])replica identity{(column_name[, ...])=(column_value[, ...]) | full [(column_name[, ...])=](column_value[, ...])}
ログには次の情報が提供されます。
LOG
schemaname.tablenameは、コンフリクトに関係するローカルリレーションを識別します。
conflict_typeは発生したコンフリクトの種類です(例:insert_exists、update_exists)。
DETAIL
detailed_explanationには、既存のローカル行を変更したトランザクションのオリジンとトランザクションID、そしてコミットタイムスタンプ(利用可能であれば)が含まれます。
Keyセクションには、insert_exists、update_existsまたはmultiple_unique_conflictsコンフリクトの一意性制約に違反したローカル行のキー値が含まれます。
existing local rowセクションにはローカル行が含まれます。
ただし、update_origin_differsまたはdelete_origin_differsコンフリクトではローカル行のオリジンがリモートと異なる場合、insert_exists、update_existsまたはmultiple_unique_conflictsではキー値がリモート行と競合する場合です。
remote rowセクションには、競合の原因となったリモート挿入または更新操作による新しい行が含まれます。
更新操作の場合、変更されなかった列やTOASTされた列の値はNULLとなることに注意してください。
replica identityセクションには、更新または削除対象となったローカル行の検索に使用されたレプリカアイデンティティが含まれます。
ローカルリレーションにREPLICA IDENTITY FULLが指定されている場合は、行全体の値が含まれることがあります。
column_nameは列名です。
existing local row, remote row、およびreplica identity fullの場合、ユーザがテーブルのすべての列に対するアクセス権限を持っていない場合にのみ列名が記録されます。
列名が存在する場合は、対応する列の値と同じ順に表示されます。
column_valueは列の値です。
長い列の値は64バイトに切り詰められます。
multiple_unique_conflictsコンフリクトの場合は、複数のdetailed_explanationおよびdetail_valuesが生成され、それぞれが異なる一意性制約に関連付けられたコンフリクト情報を詳述することに注意してください。
論理レプリケーション操作は、サブスクリプションを所有するロールの権限を使用して実行されます。
対象テーブルで権限違反が起こると、レプリケーション競合が発生します。
これは、サブスクリプション所有者が従う、対象テーブルで有効な行レベルセキュリティと同じですが、レプリケーションされているINSERT、UPDATE、DELETEまたはTRUNCATEをポリシーが通常拒否するかどうかには関係ありません。
行レベルセキュリティに対するこの制限は、PostgreSQLの将来のバージョンで解除される可能性があります。
コンフリクトはエラーを生じさせ、レプリケーションを停止させます。 コンフリクトはユーザが手動で解消しなければなりません。 コンフリクトの詳細は、サブスクライバーのサーバログに出力されます。
この問題を解決するには、データを変更するか、サブスクライバーに対する権限を変更して、既存の変更でコンフリクトしないようにするか、既存のトランザクションと競合するデータをスキップします。 コンフリクトよってエラーが発生した場合、レプリケーションは処理を続行せず、論理レプリケーションワーカーは次のようなメッセージをサブスクライバーのサーバログに送信します。
ERROR: conflict detected on relation "public.test": conflict=insert_exists DETAIL: Key already exists in unique index "t_pkey", which was modified locally in transaction 740 at 2024-06-26 10:47:04.727375+08. Key (c)=(1); existing local row (1, 'local'); remote row (1, 'remote'). CONTEXT: processing remote data for replication origin "pg_16395" during "INSERT" for replication target relation "public.test" in transaction 725 finished at 0/14C0378
制約とレプリケーションの起点名に違反する変更を含むトランザクションのLSNは、サーバログ(LSN 0/14C0378とレプリケーション起点pg_16395)から見つけることができます。
競合を発生させたトランザクションは、終了LSN(LSN 0/14C0378)でALTER SUBSCRIPTION ... SKIPを使用してスキップできます。
終了LSNは、パブリッシャーでトランザクションがコミットまたは準備されたLSNにすることができます。
あるいは、pg_replication_origin_advance()関数を呼び出して、トランザクションをスキップすることもできます。
この関数を使用する前に、ALTER SUBSCRIPTION ... DISABLEを使用してサブスクリプションを一時的に無効にするか、
disable_on_errorオプションを使用します。
次に、pg_replication_origin_advance()関数をnode_name(pg_16395)と終了LSNの次のLSN(0/14C0379)と共に使用します。
現在の起点の位置は、pg_replication_origin_status システムビューで確認できます。
トランザクション全体をスキップすることは、いかなる制約にも違反しない可能性のある変更をスキップすることを含むことに注意してください。
これは容易にサブスクライバーを不整合にする可能性があります。
オリジンやコミットタイムスタンプのようなコンフリクト行に関する詳細は、ログのDETAIL行で確認できます。
しかし、これらの情報は、サブスクライバーでtrack_commit_timestampが有効な場合にのみ表示されます。
この情報はローカルの変更を保持するか、リモートの変更を採用するかを決定する際に使用できます。
例えば上記ログのDETAILは、既存の行がローカルで変更されたことを示しています。
ユーザは手動でリモート変更優先を実行できます。
streamingモードがparallelの場合、失敗したトランザクションの終了LSNはログに書き込まれないことがあります。
その場合、ストリーミングモードをonまたはoffに変更し、再度同じコンフリクトを起こすことで、失敗したトランザクションの終了LSNをサーバのログに書き込むようにする必要があるかもしれません。
終了LSNの使用方法については、ALTER SUBSCRIPTION ... SKIPを参照してください。