Stream triggers

BDRには、ダウンストリーム/ターゲットノードでの追加のデータ処理に使用できる新しいタイプのトリガーが導入されています。

  • コンフリクトトリガー

  • 変身トリガー

これらの種類のトリガーは、ストリーム トリガーと呼ばれます。

ストリームトリガーは、構文がトリガーのように設計されています。これらはPostgreSQL BEFOREトリガーアーキテクチャを活用しており、PostgreSQL BEFOREトリガーと同様のパフォーマンス特性を持つ可能性があります。

通常のPostgreSQLトリガーと同様に、複数のトリガー定義で1つのトリガー関数を使用できます。トリガー関数は、次の形式で定義されたプログラムです。トリガーを作成するために、 CREATE TRIGGER コマンドを使用する必要はありません。代わりに、特別なBDR関数bdr.create_conflict_trigger() およびbdr.create_transform_trigger() を使用してストリームトリガーを作成します。

作成されると、トリガーはカタログ表pg_trigger に表示されます。ストリームトリガーはtgisinternal = true およびtgenabled = 'D' としてマークされ、名前のサフィックスは’_bdrc’または’_bdrt’です。ビュー bdr.triggers は、テーブルに関連するトリガー、実行されているプロシージャの名前、それをトリガーするイベント、およびトリガータイプに関する情報を提供します。

ストリーム トリガーは、通常のSQL処理では有効になっていません。このため、 ALTER TABLE ... ENABLE TRIGGER は、その特定の名前のバリアントとALLのバリアントの両方でストリームトリガーに対してブロックされます。このメカニズムにより、トリガーが通常のSQLトリガーとして実行されなくなります。

これらのトリガーは、ダウンストリームまたはターゲットノードで実行されます。オリジンノードで実行するオプションはありません。ただし、オリジンでのrow_filter 式の使用を検討することをお勧めします。

また、ストリームトリガーの実行中に適用されるDMLは他のBDRノードにレプリケートされず、標準のローカルトリガーの実行をトリガーしません。これは意図的なものです。これを使用して、たとえば、ストリームトリガーによってキャプチャされた変更または競合を、そのノードに固有のクラッシュセーフなテーブルに記録できます。動作例については、

ストリームトリガーの例 を参照してください。

Apply中に実行をトリガー

変換トリガーは、トリガー テーブル内の受信変更ごとに最初に 1 回実行されます。これらのトリガーは、一致するターゲット行を見つけようとする前に起動されるため、非常に幅広い変換を効率的かつ一貫して適用できます。

次に、 UPDATE および DELETE の変更については、ターゲット行を見つけます。ターゲット行がない場合、それらの変更タイプに対してそれ以上の処理は発生しません。

次に、以前にテーブルレベルでレプリカトリガーとして明示的に有効にされた通常のトリガーを実行します。

ALTER TABLE tablename
ENABLE REPLICA TRIGGER trigger_name;

次に、潜在的な競合が存在するかどうかを判断します。その場合、そのテーブルに存在する競合トリガーを呼び出します。

行方不明の列の競合解決

変換トリガーが実行される前に、PostgreSQLは着信タプルをターゲットテーブルの行タイプと一致させようとします。

入力行に存在するがターゲットテーブルにない列は、タイプtarget_column_missing の競合をトリガーします。逆に、ターゲットテーブルに存在するが受信行にない列は、 source_column_missing 競合をトリガーします。これら2つの競合タイプのデフォルトの解決は、それぞれignore_if_null とuse_default_value です。

これは、スキーマのローリング アップグレードのコンテキスト、たとえば、スキーマの新しいバージョンで新しい列が導入される場合に関連します。スキーマの古いバージョンから新しいバージョンに複製する場合、ソース列がありません。新しく導入された列にデフォルト値が設定されるため、 use_default_value ストラテジーが適切です。

ただし、新しいスキーマバージョンのノードから古いスキーマバージョンのノードに複製すると、ターゲットテーブルに列がありません。 ignore_if_null リゾルバーは、ユーザーがアップグレードされたノードの新しい列にNULL以外の値を持つタプルを挿入するとすぐにレプリケーションを中断するため、ローリングアップグレードには適していません。

この例を考慮すると、ローリングスキーマアップグレードの適切な設定は、 target_column_missing の競合が発生した場合にignore リゾルバーを適用するように各ノードを構成することです。

これを行うには、次のクエリを使用します。これは、ノードごとに個別に実行する必要があります。 node1 を実際のノード名に置き換えます。

SELECT bdr.alter_node_set_conflict_resolver(node1,
    target_column_missing, ignore);

データ損失と発散のリスク

競合リゾルバーをignore に設定すると、データの損失とクラスターの発散が発生する可能性があります。

次の例を考えます。テーブルt はノード1および2に存在しますが、その列col はノード1にのみ存在します。

競合リゾルバーがignore に設定されている場合、ノード1にc がnullではない行が存在する可能性があります。その行はノード2に複製され、列c の値は破棄されます(例、(pk=1) )。

次に、列c がノード2のテーブルに追加されると、それは最初に既存のすべての行でNULLに設定され、上記の行が(pk=1, col=NULL) になります。 pk=1 を持つ行はすべてのノードで同一ではないため、クラスターは発散しています。

ノード2にレプリケートされる行にはcol=NULL があるため、デフォルトのignore_if_null リゾルバーはこのリスクの影響を受けません。

この例に基づいて、 ignore リゾルバーが使用されたスキーマのローリングアップグレードの最後に、クラスター全体に対してLiveCompareを実行することをお勧めします。このプラクティスは、相違を検出して修正するのに役立ちます。

行型の用語

これらの行タイプを使用します。

・ SOURCE_OLD が更新前の行、つまりキーです。

  • SOURCE_NEW は、別のノードからの新しい行です。

  • TARGET は、ノードに既に存在する行、つまり競合している行です。

競合トリガー

競合トリガーは、 BDRによって競合が検出されたときに実行されます。彼らは、競合が発生したときに何が起こるかを決定します。

  • トリガー関数が行を返す場合、アクションはターゲットに適用されます。

  • トリガー関数がNULL行を返した場合、アクションはスキップされます。

たとえば、トリガーがDELETE に対して呼び出された場合、DELETE をスキップする場合、トリガーはNULLを返します。 DELETE を続行したい場合は、行の値を返します。SOURCE_OLD またはTARGET が機能します。競合する操作がINSERT またはUPDATE であり、選択された解決が競合する行の削除である場合、トリガーは削除を明示的に実行してNULLを返す必要があります。トリガー関数は、必要に応じて他のSQLアクションを実行できますが、これらのアクションはローカルにのみ適用され、レプリケートされません。

2つ以上のノード間で実際のデータの競合が発生した場合、2つ以上の変更が同時に発生しています。これらの変更を適用すると、競合の解決は各ノードで独立して発生します。これは、競合の解決が各ノードで1回発生し、ノード間に大きな時間差があることを意味します。その結果、競合トリガーの複数の実行間の通信はできません。トリガーが関連するすべてのイベントに対してまったく同じ結果を与えることを保証するのは、競合トリガーの作成者の責任です。そうしないと、データの相違が発生します。テクニカルサポートは、 BDRに付属のisolationtesterツールを使用して、すべての競合トリガーを正式にテストすることをお勧めします。

警告

  • 単一のテーブルに複数の競合トリガーを指定できますが、それらは個別のイベントと一致する必要があります。つまり、各競合は単一の競合トリガーのみと一致する必要があります。 - 同じテーブルの同じイベントに一致する複数のトリガーはお勧めしません。一貫性のない動作が発生する可能性があり、将来のリリースでは許可されません。

同じ競合トリガーが複数のイベントと一致する場合、トリガーで TG_OP 変数を使用して、競合を生成した操作を特定できます。

デフォルトでは、 BDRは行のレプリケーションオリジンの変更を監視することにより競合を検出します。したがって、1つの変更のみが発生している場合でも、競合トリガーを呼び出すことができます。この場合、実際の競合はないため、この競合検出メカニズムは誤検出の競合を生成する可能性があります。競合トリガーは、これらすべてを同じように処理する必要があります。

場合によっては、タイムスタンプの競合検出で競合がまったく検出されません。たとえば、 DELETE がUPDATE の直後に発生する同時実行UPDATE /DELETE では、最初にUPDATE を認識し、次にDELETE を認識しません。競合が見られない場合、競合トリガーは呼び出されません。同じ状況で、行バージョンの競合検出を使用すると、競合が発生し、競合トリガーで処理できます。

トリガー関数は、操作の種類に応じて、競合に関係するデータ行だけでなく、追加の状態情報にアクセスします。

  • INSERT では、競合トリガーはソースからSOURCE_NEW 行およびTARGET 行にアクセスできます。

  • UPDATE では、競合トリガーはソースおよびTARGET 行からSOURCE_OLD およびSOURCE_NEW 行にアクセスできます。

  • DELETE では、競合トリガーはソースからSOURCE_OLD 行およびTARGET 行にアクセスできます。

bdr.trigger_get_row() ファンクションを使用して、 SOURCE_OLD 、SOURCE_NEW 、またはTARGET 行を取得できます(その操作の値が存在する場合)。

競合トリガーへの変更はトランザクションで発生し、 ALTER TABLE の一部のバリアントが処理される方法と同様に、構成変更のレプリケーション中にグローバルDMLロックによって保護されます。

コンフリクトトリガー内で主キーを更新すると、実行タイミングの違いによりユニーク制約違反エラーにつながる場合があります。したがって、競合トリガーで主キーを更新しないでください。

トリガーを変換する

これらのトリガーは、特定のテーブルに対してデータストリームのすべての行に対して実行されることを除き、競合トリガーに似ています。戻り値と公開された変数の動作は似ていますが、変換トリガーはターゲット行が識別される前に実行されるため、 TARGET 行はありません。

BDRの各テーブルに複数の変換トリガーを指定できます。変換トリガーはアルファベット順に実行されます。

変換トリガーは行をフィルター処理し、必要に応じて追加の操作を実行できます。任意の列の値を変更したり、それらをNULL に設定したりできます。戻り値によって、実行されるアクションが決まります。

  • トリガー関数が行を返す場合、ターゲットに適用されます。

  • トリガー関数がNULL 行を返した場合、これ以上実行するアクションはありません。未実行のトリガーは実行されません。

  • トリガー機能は、他のアクションを自由に実行できます。

トリガー関数は、競合に関係する行だけでなく、追加の状態情報にもアクセスできます。

  • INSERT では、変換トリガーはソースからSOURCE_NEW 行にアクセスできます。

  • UPDATE では、変換トリガーはソースからSOURCE_OLD およびSOURCE_NEW 行にアクセスできます。

  • DELETE では、変換トリガーはソースからSOURCE_OLD 行にアクセスできます。

関数 bdr.trigger_get_row() を使用して、 SOURCE_OLD またはSOURCE_NEW の行を取得できます。このタイプのトリガーは、そのようなターゲット行が識別される前に実行されるため、 TARGET 行は使用できません。

変換トリガーは、通常の BEFORE 行トリガーと非常によく似ていますが、次の重要な違いがあります。

  • 変換トリガーは、着信変更ごとに呼び出されます。 BEFORE トリガーはUPDATE に対してまったく呼び出されません。テーブル内に一致する行が見つからない場合、DELETE が変更されます。

  • パーティションテーブルのルーティングが発生する前に、変換トリガーが呼び出されます。

  • 変換トリガーは、通常のSQLトリガーでは使用できないSOURCE_OLD を介してルックアップキーにアクセスします。

ストリームトリガー変数

競合トリガーと変換トリガーはどちらも、トリガー APIが提供する事前定義された変数とBDRが提供する追加情報関数を介して、行とメタデータに関する情報にアクセスします。

PL/pgSQLでは、以下の事前定義された変数を使用できます。

TG_NAME

データ型名。この変数には、実際に起動されたトリガーの名前が含まれています。実際のトリガー名には、トリガーの作成中に指定された名前と比較して、「_bdrt」または「_bdrc」のサフィックス(トリガーのタイプによって異なります)が付いています。

TG_WHEN

データ型テキスト。この変数は、競合と変換の両方のトリガーに対してBEFORE を示します。 bdr.trigger_get_type() 情報関数を呼び出すことで、ストリームのトリガータイプを取得できます。

bdr.trigger_get_type を参照してください。

TG_LEVEL

データ型 text: ROW の文字列。

TG_OP

データ型テキスト: トリガーが起動された操作を識別するINSERT 、UPDATE 、またはDELETE の文字列。

TG_RELID

データ型 oid: トリガーの呼び出しの原因となったテーブルのオブジェクトID。

TG_TABLE_NAME

データ型名: トリガーの呼び出しの原因となったテーブルの名前。

TG_TABLE_SCHEMA

データ型名: トリガーの呼び出しの原因となったテーブルのスキーマの名前。パーティションテーブルの場合、これはルートテーブルの名前です。

TG_NARGS

データ型整数: bdr.create_conflict_trigger() またはbdr.create_transform_trigger() ステートメントでトリガー関数に指定された引数の数。

TG_ARGV[]

テキストのデータ型配列: bdr.create_conflict_trigger() またはbdr.create_transform_trigger() ステートメントからの引数。インデックスは0からカウントされます。無効なインデックス(0未満またはTG_NARGS 以上)はNULL 値になります。

情報機能

bdr.trigger_get_row

この関数は、識別子で指定されたトリガー行の内容をRECORD として返します。この関数は、不適切に呼び出された場合、つまり、オペレーションタイプ(TG_OP)がDELETE の場合にSOURCE_NEW で呼び出された場合、 NULL を返します。

概要

bdr.trigger_get_row(row_id text)

パラメーター

  • row_id —行の識別子。トリガーのタイプと操作に応じて、 SOURCE_NEW 、SOURCE_OLD 、およびTARGET のいずれかです(個々のトリガータイプの説明を参照)。

bdr.trigger_get_committs

このファンクションは、識別子で指定されたトリガー行のコミット・タイムスタンプを戻します。行がフリーズしているか利用できないために利用できない場合、 NULL を返します。行識別子SOURCE_OLD に対しては常にNULL を返します。

概要

bdr.trigger_get_committs(row_id text)

パラメーター

  • row_id —行の識別子。トリガーのタイプと操作に応じて、 SOURCE_NEW 、SOURCE_OLD 、およびTARGET のいずれかになります(個々のトリガータイプの説明を参照)。

bdr.trigger_get_xid

このファンクションは、識別子で指定されたTARGET行のローカルトランザクションIDを返します。行がフリーズしているか利用できないために利用できない場合、 NULL を返します。 SOURCE_OLD およびSOURCE_NEW 行識別子に対して常にNULL を返します。

競合トリガーでのみ使用できます。

概要

bdr.trigger_get_xid(row_id text)

パラメーター

  • row_id —行の識別子。トリガーのタイプと操作に応じて、 SOURCE_NEW 、SOURCE_OLD 、およびTARGET のいずれかになります(個々のトリガータイプの説明を参照)。

bdr.trigger_get_type

この関数は、現在のトリガータイプを返します。これは、 CONFLICT またはTRANSFORM です。ストリームトリガーの外部で呼び出された場合は null を返します。

概要

bdr.trigger_get_type()

bdr.trigger_get_conflict_type

このファンクションは、競合トリガー内で呼び出された場合、現在の競合タイプを返します。それ以外の場合は、NULL を返します。

この関数の可能な戻り値については、 競合タイプのリスト を参照してください。

概要

bdr.trigger_get_conflict_type()

bdr.trigger_get_origin_node_id

このファンクションは、引数として渡されたトリガーrow_idのオリジンに対応するノードIDを返します。オリジンが有効でない場合(つまり、行がローカルで発生した場合)、トリガーの行引数に応じて、ソースまたはターゲットノードのノードIDを返します。行識別子SOURCE_OLD に対しては常にNULL を返します。この関数を使用して、常に信頼できるソースノードを優先するように競合トリガーを定義できます。

概要

bdr.trigger_get_origin_node_id(row_id text)

パラメーター

  • row_id —行の識別子。トリガーのタイプと操作に応じて、 SOURCE_NEW 、SOURCE_OLD 、およびTARGET のいずれかになります(個々のトリガータイプの説明を参照)。

bdr.ri_fkey_on_del_trigger

BEFOREトリガーとして呼び出されると、このファンクションはFOREIGN KEY情報を使用してFKの異常を回避します。

概要

bdr.ri_fkey_on_del_trigger()

行の内容

SOURCE_NEW 、SOURCE_OLD 、およびTARGET の内容は、オペレーション、テーブルのREPLICA IDENTITY設定、およびターゲットテーブルの内容に依存します。

TARGET行は、競合トリガーでのみ使用できます。 TARGET行には、ターゲットテーブルにUPDATE またはDELETE を適用したときに行が見つかった場合にのみデータが含まれます。行が見つからない場合、TARGETはNULL です。

トリガーノート

トリガーの実行順序:

  • トリガーを変換する -ターゲットの受信行ごとに1回実行します。

  • 通常のトリガー1行に1回実行します。

  • 競合トリガー競合が存在する行ごとに1回実行します。

ストリームトリガー操作インターフェース

REPLICA IDENTITY FULL を持つテーブル、またはTOAST が適用される列のないテーブルでのみストリームトリガーを作成できます。

bdr.create_conflict_trigger

このファンクションは、新しい競合トリガーを作成します。

概要

bdr.create_conflict_trigger(trigger_name text,
                            events text[],
                            relation regclass,
                            function regprocedure,
                            args text[] DEFAULT {})

パラメーター

  • trigger_name —新しいトリガーの名前。

  • events —このトリガーを起動するイベントの配列。有効な値は「INSERT 」、「UPDATE 」、および「DELETE 」です。

  • relation —このトリガーを起動するリレーション。

  • function —実行する関数。

  • args —オプション。トリガー関数が実行時に受け取るパラメーターの配列を指定します( TG_ARGV 変数の内容)。

注意事項

このファンクションは、 DDL ステートメントと同じ複製メカニズムを使用します。これは、レプリケーションが ddl filters 構成の影響を受けることを意味します。

このファンクションは、トリガーが作成されるリレーションでグローバルDMLロックを取得します。

このファンクションはトランザクションです。トランザクションのROLLBACK で効果をロールバックできます。変更は現在のトランザクションに表示されます。

通常のPostgreSQLトリガーと同様に、 bdr.create_conflict_trigger ファンクションには、ファンクションのrelation およびEXECUTE 権限が必要です。これは、30619以上のbdr.backwards_compatibility に適用されます。追加のセキュリティルールは、競合トリガーを含むすべてのトリガーにBDRで適用されます。

Security and roles を参照してください。

bdr.create_transform_trigger

このファンクションは、変換トリガーを作成します。

概要

bdr.create_transform_trigger(trigger_name text,
                             events text[],
                             relation regclass,
                             function regprocedure,
                             args text[] DEFAULT {})

パラメーター

  • trigger_name —新しいトリガーの名前。

  • events —このトリガーを起動するイベントの配列。有効な値は「INSERT 」、「UPDATE 」、および「DELETE 」です。

  • relation —このトリガーを起動するリレーション。

  • function —実行する関数。

  • args —オプション。トリガー関数が実行時に受け取るパラメーターの配列を指定します(TG_ARGV 変数の内容)。

注意事項

このファンクションは、 DDL ステートメントと同じ複製メカニズムを使用します。これは、レプリケーションが ddl filters 構成の影響を受けることを意味します。

このファンクションは、トリガーが作成されるリレーションでグローバルDMLロックを取得します。

このファンクションはトランザクションです。トランザクションのROLLBACK で効果をロールバックできます。変更は現在のトランザクションに表示されます。

通常のPostgreSQLトリガーと同様に、 bdr.create_transform_trigger ファンクションには、ファンクションのrelation およびEXECUTE 権限が必要です。追加のセキュリティルールは、 BDRで変換トリガーを含むすべてのトリガーに適用されます。

Security and roles を参照してください。

bdr.drop_trigger

この関数は、既存のストリームトリガー(競合と変換の両方)を削除します。

概要

bdr.drop_trigger(trigger_name text,
                 relation regclass,
                 ifexists boolean DEFAULT false)

パラメーター

  • trigger_name —既存のトリガーの名前。

  • relation —トリガーが定義されている関係。

  • ifexists — true に設定すると、この関数は欠落しているトリガーを無視します。

注意事項

このファンクションは、 DDL ステートメントと同じ複製メカニズムを使用します。これは、レプリケーションが ddl filters 構成の影響を受けることを意味します。

このファンクションは、トリガーが作成されるリレーションでグローバルDMLロックを取得します。

このファンクションはトランザクションです。トランザクションのROLLBACK で効果をロールバックできます。変更は現在のトランザクションに表示されます。

relation の所有者のみがbdr.drop_trigger ファンクションを実行できます。

ストリームトリガーの例

update_if_newer 競合リゾルバーと同様の動作を提供する競合トリガー:

CREATE OR REPLACE FUNCTION update_if_newer_trig_func
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
    IF (bdr.trigger_get_committs(TARGET) >
        bdr.trigger_get_committs(SOURCE_NEW)) THEN
    RETURN TARGET;
    ELSIF
        RETURN SOURCE;
    END IF;
END;
$$;

カウンター列にデルタ変更を適用し、他のすべての列にSOURCE_NEWを使用する競合トリガー。

CREATE OR REPLACE FUNCTION delta_count_trg_func
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
    DELTA bigint;
    SOURCE_OLD record;
    SOURCE_NEW record;
    TARGET record;
BEGIN
    SOURCE_OLD := bdr.trigger_get_row(SOURCE_OLD);
    SOURCE_NEW := bdr.trigger_get_row(SOURCE_NEW);
    TARGET := bdr.trigger_get_row(TARGET);

    DELTA := SOURCE_NEW.counter - SOURCE_OLD.counter;
    SOURCE_NEW.counter = TARGET.counter + DELTA;

    RETURN SOURCE_NEW;
END;
$$;

すべての変更を適用する代わりにログテーブルに記録する変換トリガー。

CREATE OR REPLACE FUNCTION log_change
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
    SOURCE_NEW record;
    SOURCE_OLD record;
    COMMITTS timestamptz;
BEGIN
    SOURCE_NEW := bdr.trigger_get_row(SOURCE_NEW);
    SOURCE_OLD := bdr.trigger_get_row(SOURCE_OLD);
    COMMITTS := bdr.trigger_get_committs(SOURCE_NEW);

    IF (TG_OP = INSERT) THEN
        INSERT INTO log SELECT I, COMMITTS, row_to_json(SOURCE_NEW);
    ELSIF (TG_OP = UPDATE) THEN
        INSERT INTO log SELECT U, COMMITTS, row_to_json(SOURCE_NEW);
    ELSIF (TG_OP = DELETE) THEN
        INSERT INTO log SELECT D, COMMITTS, row_to_json(SOURCE_OLD);
    END IF;

    RETURN NULL; -- do not apply the change
END;
$$;

この例は、信頼できるソースの競合検出 (信頼できるサイト、優先ノード、または Always Wins 解決とも呼ばれます) を実装する競合トリガーを示しています。これは bdr.trigger_get_origin_node_id() 関数を使用して、3つ以上のノードで動作するソリューションを提供します。

CREATE OR REPLACE FUNCTION test_conflict_trigger()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
    SOURCE  record;
    TARGET  record;

    TRUSTED_NODE    bigint;
    SOURCE_NODE     bigint;
    TARGET_NODE     bigint;
BEGIN
    TARGET := bdr.trigger_get_row(TARGET);
    IF (TG_OP = DELETE)
        SOURCE := bdr.trigger_get_row(SOURCE_OLD);
    ELSE
        SOURCE := bdr.trigger_get_row(SOURCE_NEW);
    END IF;

    TRUSTED_NODE := current_setting(customer.trusted_node_id);

    SOURCE_NODE := bdr.trigger_get_origin_node_id(SOURCE_NEW);
    TARGET_NODE := bdr.trigger_get_origin_node_id(TARGET);

    IF (TRUSTED_NODE = SOURCE_NODE) THEN
        RETURN SOURCE;
    ELSIF (TRUSTED_NODE = TARGET_NODE) THEN
        RETURN TARGET;
    ELSE
        RETURN NULL; -- do not apply the change
    END IF;
END;
$$;