EDB Postgres
Distributedは、アクティブ/アクティブまたはマルチマスターのDBMSです。非同期で使用すると、複数の異なるノードから同じ行または関連する行に書き込むと、標準データ型を使用しているときにデータの競合が発生する可能性があります。
競合はエラーではありません。ほとんどの場合、それらはPGDが発生時に検出および解決できるイベントです。それらを解決することは、アプリケーションの性質とデータの意味によって異なるため、PGDがアプリケーションに競合を解決する方法の幅広い選択肢を提供することが重要です。
デフォルトでは、競合は行レベルで解決されます。
2つのノードからの変更が競合する場合、PGDはローカルまたはリモートのタプルを選択し、もう一方を破棄します。たとえば、2つの競合する変更のコミットタイムスタンプを比較し、新しい方を保持する場合があります。このアプローチにより、すべてのノードが同じ結果に収束し、クラスター全体でコミットオーダーのようなセマンティクスが確立されます。
一部のクラスタリングシステムは、分散ロックメカニズムを使用して、データへの同時アクセスを防ぎます。これらは、サーバーが互いに非常に近い場合は合理的に実行できますが、許容可能なパフォーマンスのために非常に低いレイテンシーが重要である地理的に分散したアプリケーションをサポートできません。
分散ロックは本質的に悲観的なアプローチです。
PGDは楽観的なアプローチを提唱しています。これは、可能な場合は競合を回避しますが、特定の種類の競合の発生を許可し、発生したときに解決することです。
紛争の種類
PRIMARY KEYまたはUNIQUEの競合
最も一般的な競合は行の競合です。2つの操作が、単一のノードではできない方法で同じキーを持つ行に影響します。
PGDはそれらのほとんどを検出でき、 update_if_newer
競合リゾルバーを適用できます。
行の競合には次のものがあります。
INSERT 対INSERT
UPDATE 対UPDATE
UPDATE 対DELETE
INSERT 対UPDATE
INSERT 対DELETE
DELETE 対DELETE
ビューbdr.node_conflict_resolvers
は、既知のすべての競合タイプに対して競合解決が現在どのように構成されているかに関する情報を提供します。
INSERT / INSERTの競合
最も一般的な競合であるINSERT /INSERT
は、2つの異なるノードでのINSERT 操作が同じPRIMARY KEY
値(または、 PRIMARY KEY が存在しない場合、単一のUNIQUE
制約と同じ値)を持つタプルを作成する場合に発生します。
PGDは、発信元ノードのタイムスタンプに従って、2つのうちの最後に挿入されたタプルを保持することにより、この状況を処理します。
(ユーザー定義の競合ハンドラーは、この動作をオーバーライドできます。)
この競合はinsert_exists
競合タイプを生成します。これは、デフォルトでは、コミット時間に基づいて新しい行を選択し、その行のみを保持することで解決されます(update_if_newer
リゾルバー)。他のリゾルバーを構成できます。詳細は 列レベルの競合解決の有効化と無効化 を参照。
この競合の種類を解決するには、列レベルの競合解決とユーザー定義の競合トリガーを使用することもできます。
複数のUNIQUE制約に違反するINSERT操作
INSERT /INSERT の競合は、複数のUNIQUE
制約に違反する可能性があります。そのうちの1つがPRIMARY KEY
である可能性があります。新しい行が複数のUNIQUE
制約に違反し、その結果他の複数の行と競合が発生する場合、レプリケーション変更を適用するとmultiple_unique_conflicts
競合が発生します。
このような競合が発生した場合、レプリケーションを続行するには、いくつかの行を削除する必要があります。
multiple_unique_conflicts
のリゾルバー設定に応じて、適用プロセスはエラーで終了するか、入力行をスキップするか、一部の行を削除します。削除により、正しいPRIMARY KEY
で行を保持し、他の行を削除しようとします。
警告
この方法で競合する複数の行がある場合、競合解決の結果が挿入操作を続行する場合、一部のデータは常に削除されます。
競合トリガーを使用して、別の動作を定義することもできます。
UPDATE / UPDATEの競合
異なるノードでの2つの同時UPDATE
操作が同じタプルを変更しますが、そのPRIMARY KEY
は変更しない場合、再生時にUPDATE /UPDATE
の競合が発生する可能性があります。
- これらは、構成と状況に基づいてさまざまな種類の競合を生成できます。テーブルが
行バージョンの競合検出
で構成されている場合、元の(キー)行がローカル行と比較されます。それらが異なる場合、
update_differing 競合が生成されます。 オリジンの競合検出 を使用する場合
、行の原点がチェックされます。
(起点は、現在のローカル行の元のノードです。)それが変更された場合、
update_origin_change 競合が生成されます。他のすべての場合、
UPDATE は通常、競合を生成せずに適用されます。
これらの競合は両方とも、 INSERT / INSERTの競合 で説明されている
insert_exists と同じ方法で解決されます。
PRIMARY KEYでのUPDATEの競合
PGDは現在、 PRIMARY KEY がUPDATE
操作によって変更された競合解決を実行できません。主キーを更新できますが、既存の値との競合が発生しないことを確認する必要があります。
主キーの更新に関する競合は 発散する競合 であり、手動介入が必要です。
Postgresで主キーを更新することは可能ですが、PostgresとPGDの両方に問題があります。
単純なスキーマは、以下を説明する例を提供します。
CREATE TABLE pktest (pk integer primary key, val integer);
INSERT INTO pktest VALUES (1,1);
主キー列を更新できるため、次のSQLは成功します。
UPDATE pktest SET pk=2 WHERE pk=1;
ただし、テーブルに複数の行があるとします。
INSERT INTO pktest VALUES (3,3);
一部の UPDATE 操作は成功します。
UPDATE pktest SET pk=4 WHERE pk=3;
SELECT * FROM pktest;
pk | val
- ---+-----
2 | 1
4 | 3
(2 rows)
他の UPDATE 操作は、制約エラーで失敗します。
UPDATE pktest SET pk=4 WHERE pk=2;
ERROR: duplicate key value violates unique constraint "pktest_pkey"
DETAIL: Key (pk)=(4) already exists
したがって、主キーを更新するPostgresアプリケーションの場合、PGDがなくても、ランタイムエラーを回避するように注意してください。
PGDでは、
UPDATE操作が同時に複数の場所から許可されている場合、状況はより複雑になります。
これら2つの変更を同時に実行すると機能します。
node1: UPDATE pktest SET pk=pk+1 WHERE pk = 2;
node2: UPDATE pktest SET pk=pk+1 WHERE pk = 4;
SELECT * FROM pktest;
pk | val
- ---+-----
3 | 1
5 | 3
(2 rows)
これらの次の2つの変更を同時に実行すると、両方の変更が受け入れられるため、発散エラーが発生します。ただし、他のノードに変更を適用すると、
update_missing 競合が発生します。
node1: UPDATE pktest SET pk=1 WHERE pk = 3;
node2: UPDATE pktest SET pk=2 WHERE pk = 3;
このシナリオでは、各ノードで異なるデータを残します。
node1:
SELECT * FROM pktest;
pk | val
- ---+-----
1 | 1
5 | 3
(2 rows)
node2:
SELECT * FROM pktest;
pk | val
- ---+-----
2 | 1
5 | 3
(2 rows)
:ref:`LiveCompareによるデータ検証 <LiveCompareによるデータ検証>` を使用して、この状況を特定して解決できます。
同時競合は問題を引き起こします。これら2つの変更を同時に実行することは、解決するのは簡単ではありません。
node1: UPDATE pktest SET pk=6, val=8 WHERE pk = 5;
node2: UPDATE pktest SET pk=6, val=9 WHERE pk = 5;
両方の変更がローカルに適用されるため、ノード間で相違が発生します。ただし、ターゲットへの適用は、重複キー値違反エラーで両方のノードで失敗します。このエラーにより、レプリケーションが停止し、手動で解決する必要があります。
conflict_type update_pkey_exists をskip 、update
、またはupdate_if_newer
に設定すると、この重複キー違反エラーを回避でき、レプリケーションは中断しません。このアプローチは、更新の性質によっては、依然として発散につながる可能性があります。
update_pkey_exists をupdate_if_newer
に設定することで、同じ古いキーが同じ新しいキーによって同時に更新されている場合の発散を回避できます。ただし、特定の状況では、
update_if_newer
でも発散が発生します。つまり、2つの異なる行が両方とも同じ新しい主キーに同時に更新される場合。
その結果、特にPGDでは、アプリケーションで主キーの UPDATE
操作を許可しないことを強くお勧めします。アプリケーションの一部が主キーを変更する場合、同時変更を避けるために、
Eager Replicationを使用してこれらの変更を行います。
警告
update_pkey_exists の競合の競合解決の結果が更新の場合、行の1つが常に削除されます。
複数のUNIQUE制約に違反するUPDATE操作
インデックス(またはPRIMARY KEY )に違反すると、PGDは
multiple_unique_conflicts 競合を発生させます。
PGDは、据え置きの一意の制約をサポートしています。トランザクションがソースでコミットできる場合、競合が見つからない限り、ターゲットにクリーンに適用されます。ただし、遅延主キーをREPLICA
IDENTITYとして使用することはできないため、ユースケースはすでにそのことと、複数の一意の制約の使用に関する警告によって制限されています。
UPDATE/DELETEの競合
あるノードは、別のノードが同時に削除する行を更新できます。この場合、再生時にUPDATE
/DELETE の競合が発生する可能性があります。
削除された行がまだ検出可能な場合(削除された行はVACUUM
によって削除されませんでした)、 update_recently_deleted
の競合が生成されます。デフォルトでは、 UPDATE
はスキップされますが、この解像度を構成できます。詳細は 列レベルの競合解決の有効化と無効化 を参照。
ローカルノードのレプリケーションが遅れている場合、データベースはUPDATE
を受け取るまでに削除された行をクリーンアップできます。この場合、PGDは
UPDATE /DELETE conflicts と INSERT / UPDATEの競合 を区別できません。
update_missing 競合を生成します。
DELETE とUPDATE
の競合の別のタイプは、行がローカルで更新された後に来るDELETE
です。この状況では、結果は使用される競合検出の種類によって異なります。デフォルトの
が適用され、行が削除されます。 行バージョンの競合検出 を有効にすると、
delete_recently_updated
の競合が生成されます。この競合タイプのデフォルトの解決策は、 DELETE
を適用して行を削除することです。ただし、これを構成するか、競合トリガーで処理できます。
INSERT / UPDATEの競合
デフォルトの非同期操作モードを使用する場合、ノードは元のINSERT
を受け取る前に行のUPDATE
を受け取る場合があります。これは、3つ以上のノードがアクティブな場合にのみ発生します(
これが発生すると、 update_missing
の競合が生成されます。デフォルトの競合リゾルバーはinsert_or_skip
ですが、代わりにinsert_or_error またはskip を使用できます。
insert-or-actionを行うリゾルバーは、可能な場合(行全体を受け取ったとき)、最初にUPDATE
からのデータに基づいて新しい行をINSERT
しようとします。行の再構築を可能にするには、テーブルにREPLICA IDENTITY FULL
が必要であるか、行にトーストデータが含まれていてはなりません。
- トーストされたデータの詳細については、
TOASTサポート詳細 を参照してください。
INSERT/DELETEの競合
INSERT /UPDATE の競合と同様に、ノードは、まだINSERT
を受け取っていない行でDELETE
オペレーションを受け取る場合もあります。これも3つ以上のノードが設定されている場合にのみ可能です(
PGDは現在、この競合タイプを検出できません。 INSERT
操作は競合タイプを生成せず、 INSERT が適用されます。
DELETE 操作は常にdelete_missing
競合を生成します。デフォルトでは、操作をスキップすることで解決されます。
DELETE/DELETEの競合
DELETE /DELETE
の競合は、2つの異なるノードが同じタプルを同時に削除すると発生します。
このシナリオでは常にdelete_missing
競合が生成されます。これは、デフォルトでは、操作をスキップすることで解決されます。
両方のDELETE
操作は同じ効果があるため、この競合は無害です。そのうちの1つを安全に無視できます。
3つ以上のノードとの競合
1つのノードが行を挿入し、2番目のノードに再生されてそこで更新される場合、3番目のノードは最初のノードからINSERT
を受信する前に、2番目のノードからUPDATE
を受信できます。このシナリオは、 INSERT /UPDATE の競合です。
これらの競合は、 UPDATE
を破棄することで処理されます。これにより、異なるノードで異なるデータが発生する可能性があります。これらは
この競合タイプは、3つ以上のマスターでのみ発生します。少なくとも2つのマスターがアクティブに書き込んでいる必要があります。
また、ノード1からノード3へのレプリケーションラグは、次の一連のアクションを許可できるように十分に高くする必要があります。
1.ノード2はノード1からINSERTを受け取ります
2.ノード2がUPDATEを実行します
3.ノード3はノード2からUPDATEを受信します
4.ノード3はノード1からINSERTを受け取ります
insert_or_error (または場合によっては update_missing
競合タイプのinsert_or_skip
競合リゾルバー)を使用することは、これらの競合の実行可能な緩和戦略です。ただし、このオプションを有効にすると、
INSERT /DELETE の競合の扉が開きます。
1.ノード1はUPDATEを実行します
2.ノード2はDELETEを実行します
3.ノード3はノード2からDELETEを受信します
4.ノード3はノード1からUPDATEを受け取り、それをINSERT
これらが問題である場合は、テーブルまたはデータベースのフリーズ設定をチューニングして、update_recently_deleted
として正しく検出されるようにすることをお勧めします。
別の方法は、 一般的な積極的なEager Replicationの効果 を使用してこれらの競合を防ぐことです。
INSERT /DELETE
の競合は、3つ以上のノードでも発生する可能性があります。このような競合は、
UPDATE がDELETE に置き換えられたことを除いて、 INSERT
/UPDATE と同じです。これにより、 delete_missing
の競合が発生する可能性があります。
PGDは、 update_missing の競合で発生するように、各INSERT
を最近削除されたチェックにすることを選択できます。ただし、これを行うと大多数のユーザーにペナルティが科されるため、現時点では代わりにdelete_missing
をログに記録します。
- 将来のリリースでは、
delete_missing の競合が発生した場合、 LiveCompareによるデータ検証 を使用した再チェックにより、 INSERT /DELETE
の異常を自動的に解決します。アプリケーションは、
bdr.conflict_history_summary
ビューを確認することにより、これらを手動で実行できます。
これらの競合は、2つの主な問題のユースケースで発生する可能性があります。
・ テーブルの主キー識別子を再利用する場合
これらのケースはいずれも一般的ではありません。これらの問題のユースケースが発生した場合、影響を受けるテーブルをレプリケートしないことをお勧めします。
PGDは、レプリケーションを正しく動作させるために識別子の一意性に依存しているため、後者の場合に問題があります。
- 同じ一意の識別子を挿入、削除、および後で再利用するアプリケーションは、問題を引き起こす可能性があります。これは
ABA problem と呼ばれます。
PGDには、行が現在の行、最後の行、またはそれよりずっと古い行であるかを知る方法がありません。
一意の識別子の再利用もビジネスの問題です。アプリケーションは、一意の識別子を再利用する必要はありません。
変更がシステムを通過するまでにかかる時間内に識別子の再利用が発生すると、問題が発生します。通常の操作ではその時間は短いかもしれませんが、ノードがダウンしていると、その間隔が数時間または数日に延長される可能性があります。
アプリケーションで一意の識別子を再利用しないことをお勧めします。その場合は、1年以内に再利用を避けるための措置を講じます。
この問題は、シーケンスまたはUUIDを使用するアプリケーションでは発生しません。
外部キー制約の競合
適用されているリモートトランザクションと既存のローカルデータとの間の競合は、
FOREIGN KEY (FK)制約でも発生する可能性があります。
PGDはsession_replication_role = 'replica'
で変更を適用するため、変更を適用するときに外部キーは再チェックされません。アクティブ/アクティブ環境では、参照元テーブルへの挿入と同時に参照先テーブルで削除が発生すると、この状況によりFK違反が発生する可能性があります。このシナリオは、
INSERT /DELETE の競合に似ています。
シングルマスターPostgresでは、参照先テーブルの値を参照するINSERT
/UPDATE は、行レベルロックを取得する前に DELETE
操作が完了するのを待つ必要があります。 DELETE
が参照値を削除する場合、 INSERT /UPDATE
はFKチェックに失敗します。
マルチマスターPGDでは。ノード間の行レベルのロックはありません。参照元のテーブルのINSERT
は、参照先のテーブルのDELETE
の後ろで待機しないため、両方のアクションを同時に実行できます。したがって、参照元テーブルのあるノードのINSERT
/UPDATE は、別のノードの参照先テーブルのDELETE
と同時に値を使用できます。結果は、参照元テーブルに存在しなくなった参照元テーブルの値です。
実際には、この状況は、 DELETE 操作が参照テーブルでのDELETE
操作とは別のトランザクションで参照テーブルで発生する場合に発生しますが、これは一般的な操作ではありません。
Orders ->
OrderItemsなどの親子関係では、これを行うのは一般的ではありません。
OrderItemを完全に削除するよりも、キャンセル済みとしてマークする可能性が高くなります。参照/ルックアップデータの場合、新しいファクトデータに同じ値を使用すると同時にエントリを完全に削除することは珍しいことです。
ぶら下がっているFKは可能ですが、一般的にこのリスクは非常に低いです。したがって、PGDはこのケースをカバーするための一般的な解決策を課しません。これが発生する状況を理解したら、2つの解決策が可能です。
最初の解決策は、FKの使用を、一般に一度に1つのノードからのみ変更される、まれに変更される、または変更の同時実行がアプリケーションによって仲介される密接に関連するエンティティに制限することです。このアプローチにより、アプリケーションレベルでのFK違反が回避されます。
2番目の解決策は、PGDが提供する関数bdr.ri_fkey_trigger()
およびbdr.ri_fkey_on_del_trigger()
を使用して、このケースから保護するトリガーを追加することです。
BEFORE
トリガーとして呼び出された場合、これらの関数はFOREIGN KEY
情報を使用して、参照列をNULLに設定することにより、FKの異常を回避します。
SET NULL
制約があるかのように。このアプローチでは、1つのトリガーですべてのFKを再チェックするため、FK違反を防ぐには、テーブルごとに1つのトリガーのみを追加する必要があります。
例として、FactとRefDataの2つのテーブルがあるとします。
Factには、RefDataを参照するFKがあります。
Factは参照テーブル、RefDataは参照テーブルです。各テーブルに1つのトリガーを追加する必要があります。
RefDataの参照された行がすでに削除されている場合、Factに列をNULLに設定するトリガーを追加します。
CREATE TRIGGER bdr_replica_fk_iu_trg
BEFORE INSERT OR UPDATE ON fact
FOR EACH ROW
EXECUTE PROCEDURE bdr.ri_fkey_trigger();
ALTER TABLE fact
ENABLE REPLICA TRIGGER bdr_replica_fk_iu_trg;
RefDataテーブルでDELETEが発生したときにFactの列をNULLに設定するトリガーを追加します。
CREATE TRIGGER bdr_replica_fk_d_trg
BEFORE DELETE ON refdata
FOR EACH ROW
EXECUTE PROCEDURE bdr.ri_fkey_on_del_trigger();
ALTER TABLE refdata
ENABLE REPLICA TRIGGER bdr_replica_fk_d_trg;
両方のトリガーを追加すると、外部キーのダングリングが回避されます。
TRUNCATEの競合
TRUNCATE は、すべての行のDELETE
と同様に動作しますが、行ごとの削除ではなくテーブルデータを物理的に削除することにより、このアクションを実行します。その結果、行レベルの競合処理は利用できないため、
TRUNCATE
コマンドは、明確な競合がある場合でも、他のDMLアクションとの競合を生成しません。
その結果、別のDMLがTRUNCATE
への他のノードで同時に実行された場合、リプレイの順序によって異なる変更が発生する可能性があります。
次のいずれかのアクションを実行できます。
TRUNCATE が他の同時DMLと一緒に実行されないことを確認します。
LiveCompareによるデータ検証 に依存して、そのような矛盾を強調してください。
TRUNCATE をWHERE 句のないDELETE
ステートメントに置き換えます。このアプローチは、大きなテーブルではパフォーマンスが低下する可能性があります。
bdr.truncate_locking = 'on' を設定して、 TRUNCATE
コマンドのロック動作を設定します。この設定は、 TRUNCATE
がbdr.ddl_locking
設定に従うかどうかを決定します。すべてのノードが起動している必要があるため、これはTRUNCATE
のデフォルトの動作ではありません。この構成は、すべての場合に可能または望ましいとは限りません。
除外制約の競合
PGDは除外制約をサポートしていないため、作成できません。
既存のスタンドアロンデータベースをPGDデータベースに変換する場合は、すべての除外制約を手動で削除します。
分散非同期システムでは、さまざまなノード上のすべてのトランザクションが完全に分離されるため、制約に違反する行のセットが存在しないことを保証できません。除外制約は、除外制約違反のために再生を任意のノードから他のノードに進めることができない再生デッドロックにつながります。
PGDに除外制約の作成を強制した場合、またはスタンドアロンデータベースをPGDに変換するときに既存の制約を削除しない場合、レプリケーションが破損することが予想されます。再び進行させるには、着信リモートタプルが競合するローカルタプルを削除または変更して、リモートトランザクションを適用できるようにします。
ロールとテーブルスペースの違いによるデータの競合
競合は、ノードがロールなどの異なるグローバル(Postgresシステム全体の)データを持つ場合にも発生する可能性があります。この競合により、オペレーション(主にDDL
)が発生する可能性があります。オペレーションは正常に実行され、1つのノードでコミットできますが、他のノードに適用できません。
たとえば、node1にはfredという名前のユーザーがあり、そのユーザーはnode2で作成されなかったとします。
node1のfredがテーブルを作成する場合、テーブルは所有者がfredに設定されて複製されます。
DDLコマンドをnode2に適用すると、fredという名前のユーザーがないため、
DDLは失敗します。この失敗により、Postgresログにエラーが生成されます。
PGDが実行されているデータベースにユーザーfredを作成して、この競合を解決するには、管理者の介入が必要です。
bdr.role_replication = on を設定して、将来これを解決できます。
ロックの競合とデッドロックの中止
PGDライタプロセスは通常のユーザーセッションとほとんど同じように動作するため、行とテーブルのロックに関する通常のルールに従います。これにより、PGDライタプロセスが、ユーザートランザクションまたは相互に保持されているロックで待機する場合があります。
関連するロックには以下が含まれます。
ユーザーセッションによる明示的なテーブルレベルロック(LOCK TABLE ...
)
ユーザーセッションによる明示的な行レベルロック(SELECT ... FOR UPDATE/FOR SHARE
)
ローカルアクティビティまたは他のノードからのレプリケーションのいずれかによる、行UPDATE
、INSERT 、またはDELETE 操作による暗黙的なロック
PGDライタプロセスは、ユーザートランザクションでデッドロックする可能性があります。ユーザートランザクションは、ライタプロセスが保持するロックを待機しています。その逆も同様です。
2つのライタプロセスが互いにデッドロックする場合もあります。
Postgresのデッドロック検出器は、問題のあるトランザクションの1つに介入して終了します。
PGDライタプロセスが終了した場合、再試行し、通常は成功します。
これらの問題はすべて一時的なものであり、通常、管理者の操作は必要ありません。ライタプロセスがアイドル状態のユーザーセッションのロックの背後で長時間スタックしている場合、管理者はユーザーセッションを終了して、レプリケーションが再び流れるようにすることができます。ただし、これは、ユーザーが別のユーザーセッションに影響を与える長いロックを保持することと変わりありません。
Postgresで log_lock_waits 機能を使用すると、ロック関連のリプレイストールの特定に役立ちます。
発散する競合
発散競合は、異なるノードで同じであるはずのデータが予期せず異なる場合に発生します。発散する競合は発生しないはずですが、執筆時点でそのような競合のすべてを確実に防止できるわけではありません。
行のPRIMARY KEY
を変更すると、すべてのノードが変更を再生する前に別のノードが同じ行のキーを変更した場合、発散競合が発生する可能性があります。主キーの変更を避けるか、指定された1つのノードでのみ変更します。
行データが関係する発散コンフリクトは、一般に、一方のノードのデータを他方のノードと一貫性を保つように手動で調整する管理者のアクションを必要とします。文書化されたとおりにPGDを使用し、安全でないとマークされた設定または機能を回避する限り、このような競合は発生しません。
管理者は、このような競合を手動で解決する必要があります。競合の性質によっては、
bdr.ddl_replication やbdr.ddl_locking
などの高度なオプションの使用が必要になる場合があります。ただし、これらのオプションを不注意に使用すると、事態がさらに悪化し、一般的な指示では対処できない競合が発生する可能性があります。
TOASTサポート詳細
Postgresは、 TOAST
と呼ばれる大きな列にアウトオブラインストレージを使用します。
論理デコード(PGDがその上に構築されます)および論理レプリケーションで処理するTOAST値は、テーブルのメイン行の一部として格納されるインラインデータとは異なります。
TOAST値は、値が変更された場合にのみトランザクションログ(WAL)に記録されます。これは、特に
UPDATE
の競合を処理するときに問題を引き起こす可能性があります。トーストされた列の値を変更しなかったUPDATE
ステートメントは、その列のない行を生成するためです。
の競合がinsert_or_error を使用して解決され、
TOAST列が欠落している場合、PGDはエラーを報告します。
ただし、これよりも微妙な問題は、非同期レプリケーションを使用した同時ワークロードの場合に発生します。
(積極的なトランザクションは影響を受けません。)たとえば、A、B、およびCという3つのノードを持つEDB
Postgres分散クラスターでの次のワークロードを想像してください。
1.ノードAで: txn A1は UPDATE SET col1 = ’toast
data…’を行い、最初にコミットします。
2.ノードBで: txn B1はUPDATE SET other_column = ‘anything
else’;を行います。 A1の後にコミットします。
3.ノードC:ノードAへの接続が遅れます。
4.ノードC: txn
B1が最初に適用され、col1のTOASTed列を見逃しますが、競合することなく適用されます。
5.ノードC: txn A1は(
update_origin_changeで)競合し、スキップされます。
6.ノードCは、A1からのトーストされたデータを永遠に逃します。
PGDを使用する場合、このシナリオは通常問題になりません。
(ビルトイン論理レプリケーションまたはマルチマスターのプレーンなpglogicalを使用する場合です。)PGDは、
TOAST列の変更を最近レプリケートした行にローカルTOASTを検出し、ローカルUPDATE
がt
TOASTを変更します。したがって、PGDは、異なるノード間でトーストされたデータの不整合を防ぎます。この状況により、マルチプルのノードで更新が発生した場合、つまりタプルのオリジンが変更された場合、WALロギングが増加します。
PGD
AlwaysOnアーキテクチャの通常の場合と同様に、すべての更新が単一のノードから行われた場合、追加のWALオーバーヘッドはゼロです。
注釈
メインテーブルで同じことをせずにTOASTテーブルだけで`VACUUM FULL` または`CLUSTER` を実行すると、追加のロギングが機能するために必要なメタデータが削除されます。これは、そのようなステートメントの後の短期間、これらの同時実行の問題に対する保護が存在しないことを意味します。
警告
TOASTの追加のWALロギングは、標準Postgresの`BEFORE UPDATE` トリガーを使用して行われます。このトリガーは、テーブル上のすべての`BEFORE UPDATE` トリガーの中で、トリガー名に基づいてアルファベット順に最後にソートする必要があります。これを簡単にするために`zzzz_bdr_` プレフィックスが付いていますが、その後にソートされる名前でトリガーを作成しないでください。そうしないと、同時実行の問題に対する保護が得られません。
insert_or_error の競合解決のために、 REPLICA IDENTITY FULL
の使用はまだ必要です。
トーストされた列に関連するこれらの問題は、 REPLICA IDENTITY FULL
を持つテーブルには影響しません。この設定では、行全体がキーの一部と見なされるため、トーストされた値が常にキーの一部としてログに記録されます。
PGDは、新しい行を再構築し、キー行から欠落しているデータを埋めることができます。その結果、
REPLICA IDENTITY FULL
を使用すると、WALサイズが大幅に増加する可能性があります。