Overview#
デフォルトでは、競合は行レベルで解決されます。 2つのノードからの変更が競合する場合、ローカルまたはリモートのタプルのいずれかが選択され、他方は破棄されます。たとえば、2つの競合する変更のコミットタイムスタンプを比較し、新しい方が保持される場合があります。このアプローチでは、すべてのノードが同じ結果に収束し、クラスター全体でコミットオーダーのようなセマンティクスを確立します。
ただし、少なくとも一部の場合、行レベルではなく列レベルで競合を解決することが適切な場合があります。
列レベルで解決する場合#
テーブルtに2つの整数列aおよびbと、単一の行(1,1)
がある単純な例を考えます。 1つのノードで次を実行します。
UPDATE t SET a = 100
別のノードでは、前述のUPDATE
を受信する前に、次のことを同時に実行します。
UPDATE t SET b = 100
注釈
UPDATE によって変更される属性は、トリガー内の古い行と新しい行を比較することにより決定されます。これは、属性が値を変更しない場合、明示的に設定されていても変更として検出されないことを意味します。たとえば、 UPDATE t SET a = a は、 a をどの行でも変更されたものとしてマークしません。同様に、UPDATE t SET a = 1 は、既に`1` に設定されている行の`a` を変更済みとしてマークしません。
このシーケンスにより、UPDATE-UPDATE 競合が発生します。
update_if_newer 競合解決では、コミットタイムスタンプが比較され、新しい行バージョンが保持されます。
2番目のノードが最後にコミットしたと仮定すると、結果は(1,100)
であり、列aへの変更を効果的に破棄します。
多くのユースケースでは、この動作は望ましく、予想されます。ただし、一部のユースケースでは、これが問題になる場合があります。たとえば、アプリケーションの各部分が別のノードに接続され、共有テーブルの列の専用サブセットを更新するマルチノードクラスターを考えます。その場合、さまざまなコンポーネントが競合し、変更を上書きする可能性があります。
このようなユースケースでは、特定のテーブルの競合を列レベルで解決することがより適切である場合があります。これを行うために、
PGDは各列の最後の変更のタイムスタンプを個別に追跡し、それを使用して最新の値を選択し、基本的にupdate_if_newer
を実行します。
前の例に適用すると、どのノードもそのような行を認識しないにもかかわらず、結果は両方のノードで(100,100)
になります。
列レベルの競合の解決について考えるとき、テーブルを垂直にパーティション化して、各更新が1つのスライスのデータのみに影響を与えるようにすると便利です。このアプローチにより、列のさまざまなサブセットへの変更間の競合が排除されます。実際、垂直パーティショニングは、列レベルの競合解決の実用的な代替品です。
列レベルの競合解決には、テーブルにREPLICA IDENTITY FULL
が必要です。
bdr.alter_table_conflict_detection() ファンクションはそれを確認し、この設定が存在しない場合はエラーで失敗します。
列レベルの競合解決の特別な問題#
列を独立して扱うことにより、すべての変更が同じノードで発生する場合、不可能な方法でコンストレインに違反するのが簡単になります。例、次のようなテーブルを考えます。
CREATE TABLE t (id INT PRIMARY KEY, a INT, b INT, CHECK (a > b));
INSERT INTO t VALUES (1, 1000, 1);
1つのノードが次のことを行うと仮定します。
UPDATE t SET a = 100;
別のノードは同時に次のことを行います。
UPDATE t SET b = 500;
これらの更新はそれぞれ、最初の行で実行されたときに有効であるため、各ノードに渡されます。しかし、他のノードにレプリケートする場合、結果の行はCHECK (a > b)
制約に違反し、問題が手動で解決されるまでレプリケーションは停止します。
CRDTデータ型を使用した列レベルの競合の処理#
デフォルトでは、列レベルの競合解決は、より高いタイムスタンプを持つ値を選択し、他の値を破棄します。ただし、さまざまなより複雑な方法で競合を調整することはできます。たとえば、情報を破棄せずに競合する値をマージできる オペレーションベースのCRDTタイプCmCRDT を使用できます。