おらくるのいる生活

OracleのDBAとしての、障害対応やらパフォーマンス・チューニングやらの日々を綴っています

Oracle Golden Gateを使ってみた 応用編(1)

今回からはOracle Golden Gateを使ってみた 応用編です。

インストールや基本設定はOracle Golden Gateを使ってみた 基本編(1)から(3)をご参照ください。

bismarc256.hateblo.jp

応用編では双方向レプリケーションの設定と検証を行います。双方向レプリケーションで重要なのは競合の解消です。

以下、競合についてマニュアルから抜粋です。

Oracle Fusion Middleware Oracle GoldenGateの管理, 19c (19.1.0)

Oracle GoldenGateは非同期ソリューションであるため、個別のシステム上にある同一のデータセットに対して(ほぼ)同時に変更が行われた場合、競合が発生する可能性があります。競合は、同時変更のタイミングが次の非同期状態のいずれかにつながる場合に発生します。

  • Replicatが、一意性整合性制約(PRIMARY KEY制約やUNIQUE制約など)に違反する挿入操作または更新操作を適用すると、一意性競合が発生します。この競合タイプの例としては、2つの異なるデータベースから2つのトランザクションが発生し、各トランザクションが同じ主キー値で表に行を挿入する場合があげられます。
  • Replicatが、同じ行への別の更新と競合する更新を適用すると、更新競合が発生します。更新競合が発生するのは、異なるデータベースから生じた2つのトランザクションがほぼ同時に同じ行を更新した場合です。証跡レコードに格納されている古い値(変更前の値)とターゲット・データベース内の同じ行の現行値との間に差異があると、Replicatは更新競合を検出します。
  • 2つのトランザクションが異なるデータベースから生じ、一方が行を削除するのと同時にもう一方が同じ行を更新または削除すると、削除競合が発生します。この場合、その行は存在せず、更新も削除もできません。主キーが存在しないため、Replicatは行を見つけられません。

ここで「(ほぼ)同時」と言っているのは一方のDBでDMLを発行し、それがもう一方のDBに反映される前にもう一方でDMLを発行したケースになります。

FLUSHSECSで指定した時間(デフォルト1秒)が経過するか、Extractバッファがいっぱいになればフラッシュされる仕組みですし、基本編(3)で検証した感じでは秒単位のタイムラグは普通にあるようなので、個人的な感覚では「(ほぼ)同時」と言うより、「だいたい同時」ですが…。

とは言え、検証では更新量が少ないので実運用では違うかも知れません。

 

では、競合解消の設定を行っていきます。

Oracle GoldenGateには競合検出および解決を自動で行う機能(Conflict Detection and Resolution:CDR)が備わっているので、これを使用します。

CDRには以下の基本ルーチンが備わっています。

  1. INSERTの一意性競合を解決します。
  2. 行は存在するが、1つ以上の列の変更前イメージがデータベースの現行値と異なる場合に発生する、UPDATEの「データが見つからない」競合を解決します。
  3. 行が存在しない場合に発生する、UPDATEの「データが見つからない」競合を解決します。
  4. 行は存在するが、1つ以上の列の変更前イメージがデータベースの現行値と異なる場合に発生する、DELETEの「データが見つからない」競合を解決します。
  5. 行が存在しない場合に発生する、DELETEの「データが見つからない」競合を解決します。

以上が「どのような」競合を解消するかですが、「どのように」解消するかにはUSEMAX解決、USEMIN解決、USEMAXSEQ解決、USEMINSEQ解決、OVERWRITE解決、DISCARD解決、USEDELTA解決およびIGNORE解決があります。

この8つの解決方法を上記5つのパターンそれぞれに組み合わせて設定してゆく事になりますが、ここでは実運用で使用する可能性が高そうなパターンのみ検証します。

検証で使用する解決方法を簡単に説明すると以下の通りです。

  • USEMAX解決:トレイル・レコードの解決列と、ターゲットDBデータの解決列の値を比較し、大きい方を優先する。
  • OVERWRITE解決:トレイル・レコードのデータでターゲットDBデータを上書きする。
  • DISCARD解決:ターゲットDBの現在の値を保持し、トレイル・レコード内のデータを破棄ファイルに書き込む。

実運用で使いそうな具体例で説明すると、後から発行されたDMLを優先する場合、DMLが発行された時刻を記録するTIMESTAMP列を解決列として使用し、USEMAX解決を行います。

競合発生時に一方のDBの更新を優先し、他方のDBの更新を無効にする場合は優先DB側でDISCARDを設定し、もう一方のDBでOVERWRITEを設定します。

尚、ReplicatがBATCHSQLモードで動作する場合、競合解決は実行されません。

 

では、前準備としてDBMS_GOLDENGATE_ADMパッケージのADD_AUTO_CDRプロシージャを使用して、最新タイムスタンプの競合検出および解決を双方のDBで構成します。ADD_AUTO_CDR_COLUMN_GROUPプロシージャでDMLが発行された時刻を記録するTIMESTAMP列を追加します。

SQL> conn / as sysdba
Connected.
SQL> BEGIN
  2    DBMS_GOLDENGATE_ADM.ADD_AUTO_CDR(
  3      schema_name => 'ggtest',
  4      table_name  => 'tab2');
  5  END;
  6  /
PL/SQL procedure successfully completed.

SQL> SELECT TABLE_OWNER,TABLE_NAME,TOMBSTONE_TABLE,ROW_RESOLUTION_COLUMN FROM ALL_GG_AUTO_CDR_TABLES ORDER BY TABLE_OWNER, TABLE_NAME;

TABLE_OWNER     TABLE_NAME      TOMBSTONE_TABLE ROW_RESOLUTION_COLUMN
--------------- --------------- --------------- -------------------------
GGTEST          TAB2            DT$_TAB2        CDRTS$ROW

SQL> desc tab2
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 COL1                                      NOT NULL NUMBER
 COL2                                               VARCHAR2(100)
 COL3                                               TIMESTAMP(6)

SQL> desc DT$_TAB2
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 COL1                                               NUMBER
 DELTIME$$                                          TIMESTAMP(6)

トゥームストーン・テーブルとしてDT$_TAB2が作成されました。

元々のTAB2には上記の通りCOL1、COL2、COL3の3つのカラムがあり、COL1は主キーです。DT$_TAB2には主キーのCOL1と、DML発行時刻を記録するTIMESTAMPが作成されています。

#TOMBSTONEは普通名詞で墓石の事なんですが、どうしてこんな名前なんでしょう…

 

テーブルだけでなく、同名のインデックスも作成されていますね。

SQL> select segment_name,segment_type,TABLESPACE_NAME from user_segments;

SEGMENT_NAME         SEGMENT_TYPE       TABLESPACE_NAME
-------------------- ------------------ ------------------------------
DT$_TAB2             TABLE              USERS
DT$_TAB2             INDEX              USERS
SYS_C007538          INDEX              USERS
SYS_C007613          INDEX              USERS
SYS_C007837          INDEX              USERS
TAB1                 TABLE              USERS
TAB2                 TABLE              USERS
TAB3                 TABLE              USERS

8 rows selected.

SQL> select table_name,index_name,INDEX_TYPE from ind order by 1;

TABLE_NAME           INDEX_NAME           INDEX_TYPE
-------------------- -------------------- ---------------------------
DT$_TAB2             DT$_TAB2             NORMAL
TAB1                 SYS_C007837          NORMAL
TAB2                 SYS_C007538          NORMAL
TAB3                 SYS_C007613          NORMAL

場合によってはテーブルにinsertトリガー、updateトリガーを使用してinsert、update時刻を記録するカラムを持つ設計になっているので、その場合はそのカラムがそのまま利用可能です。
但し、主キーはUPDATEROWEXISTSのUSEMAXには使えません。

 

次に、ggsciからDBに接続して表のすべての列の無条件のサプリメンタル・ロギングを有効にします。これも双方のDBで実行します。

$ ./ggsci
GGSCI (mitsdb01) 1> DBLOGIN USERID ggadmin,password welcome1
Successfully logged into database.

GGSCI (mitsdb01 as GGADMIN@ggs) 2> add TRANDATA ggtest.tab2 allcols

2022-03-25 10:07:56  INFO    OGG-15132  Logging of supplemental redo data enabled for table GGTEST.TAB2.

2022-03-25 10:07:56  INFO    OGG-15133  TRANDATA for scheduling columns has been added on table GGTEST.TAB2.

2022-03-25 10:07:56  INFO    OGG-15134  TRANDATA for all columns has been added on table GGTEST.TAB2.

2022-03-25 10:07:56  INFO    OGG-15135  TRANDATA for instantiation CSN has been added on table GGTEST.TAB2.

2022-03-25 10:07:56  INFO    OGG-10471  ***** Oracle Goldengate support information on table GGTEST.TAB2 *****
Oracle Goldengate support native capture on table GGTEST.TAB2.
Oracle Goldengate marked following column as key columns on table GGTEST.TAB2: COL1.

ここまでで事前準備が済んだので、次回はパラメータ設定と競合解消の検証を行います。