Stream Triggers

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

  • コンフリクトトリガー

  • 変身トリガー

これらのタイプのトリガーは、ストリームトリガーと呼ばれます。

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

通常のPostgreSQLトリガーと同様に、1つのトリガーファンクションをマルチプルのトリガー定義で使用できます。トリガーファンクションは、単にこのフォームで定義されたプログラムです:CREATE FUNCTION ... RETURNS TRIGGER 。実際のトリガーを作成するために、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 に設定されている場合、 c がnullではない行がノード1に存在する可能性があります。その行はノード2に複製され、列c の値は破棄されます(例、(pk=1) )。

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

ノード2に複製される行にはcol=NULL があるため、デフォルトのignore_if_null リゾルバはこのリスクの影響を受けないことに注意してください。

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

行型の用語

このドキュメントでは、次の行タイプを使用します。

  • 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 行にアクセスできます

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

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

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行トリガーと非常によく似ていますが、次の重要な違いがあります。

  • 変換トリガーは、着信変更ごとに呼び出されます。テーブルでマッチング行が見つからない場合、 UPDATE および DELETE の変更に対して BEFORE トリガーは呼び出されません。

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

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

ストリームトリガー変数

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

PL/pgSQLには、次の事前定義された変数が存在します。

TG_NAME

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

TG_WHEN

データ型テキスト。これは、 Conflict と Transform の両方のトリガーに対して BEFORE と表示します。ストリームトリガータイプは、 bdr.trigger_get_type() 情報ファンクション(下記参照)を呼び出すことで取得できます。

TG_LEVEL

データ型テキスト。 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回実行

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

ストリームトリガーは、 REPLICA IDENTITY FULL または TOAST able列のないテーブルでのみ作成できます。

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 に対するTRIGGER 権限およびファンクションに対するEXECUTE 権限が必要です。これは、30619以上のbdr.backwards_compatibility に適用されます。追加のセキュリティルールは、競合トリガーを含むすべてのトリガーにBDRで適用されます。

security chapter on triggers を参照してください。

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トリガーと同様に、 権限ファンクションには、 relation および EXECUTE 権限が必要ファンクション。追加のセキュリティルールは、 BDRで変換トリガーを含むすべてのトリガーに適用されます。

security chapter on triggers を参照してください。

bdr.drop_trigger

このファンクションは、既存のストリームトリガー(競合と変換の両方)を削除します。

概要

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

パラメーター

  • trigger_name - 既存のトリガーの名前

  • relation -どのリレーションが定義されたトリガーです

  • ifexists - true true に設定すると、このコマンドは欠落しているトリガーを無視します

注意事項

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

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

このファンクションはトランザクションです。効果はトランザクションの ROLLBACK でロールバックでき、変更は現在のトランザクションで可視です。

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

ストリームトリガーの例

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;
$$;

以下の例は、信頼できるソースの競合検出を実装する競合ノード。これは、 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;
$$;