Oracle GoldenGate自動競合検出および解消機能の紹介(後編)

オラクル散歩道

2022.02.01

1. はじめに

NTTデータ先端技術の千葉です。
前編に引き続き、Oracle GoldenGateの自動競合検出および解消機能(Automatic Conflict Detection and Resolution)(以降Auto CDRと記載)を紹介します。後編では、実機を用いて、Auto CDRの有効化方法と競合解決履歴データの確認方法、運用時の考慮事項を紹介します。

2. 検証環境構成

検証環境の構成を下記に示します。本環境では、データベース、Oracle GoldenGateのプロセスは作成済みとなります。本検証環境では、2台のOracle Linux7.9のサーバー(ホスト名はdb11とdb31)を、Oracle GoldenGateのソース兼ターゲットとして使用しています。以降の手順において、こちらのホスト名が度々登場するため、頭の片隅においておくと理解がしやすいと思います。

検証環境構成

3. Auto CDR動作検証

Auto CDRの動作確認を下記の流れで進めていきます。
下記アスタリスクをつけた箇所がAuto CDR固有の手順となります。

  • テーブル作成
  • サプリメンタルロギング設定
  • パラメータファイル修正
  • Auto CDR有効化(*)
  • プロセス起動
  • 競合発生および解決確認(*)
  • 競合履歴の確認(*)

以降に実行した際のログを記載していますが、冗長な表現にならないよう、出力メッセージは適宜修正しています。予め、ご了承ください。

テーブル作成

sqlplusを起動しPDBに接続後、anglerスキーマを作成し、権限を付与します。(db11とdb31にて実施する事)

SQL> CREATE USER angler;
ユーザーが作成されました。

SQL> PASSWORD angler
anglerに対するパスワードを変更しています。
新規パスワード:
新規パスワードを再入力してください:
パスワードが変更されました。

SQL> GRANT connect, resource, unlimited tablespace TO angler;
権限付与が成功しました。

続いてanglerスキーマにfishテーブルを作成します。(db11とdb31にて実施する事)

SQL> CREATE TABLE angler.fish (
  2      id NUMBER,
  3      name VARCHAR2(100),
  4      CONSTRAINT pk_fish PRIMARY KEY(id)
  5  );
表が作成されました。

サプリメンタルロギング設定

ggsciを起動し、DBLOGINコマンドにてPDBにログインします。本環境では、gg_pdbというエイリアスを使用して、PDBに接続しています。PDB接続後、ADD TRANDATAコマンドを用いて、fishテーブルのサプリメンタルロギングを有効化します。(db11とdb31にて実施する事)

$ cd $GG_HOME
$ ./ggsci
GGSCI 1> DBLOGIN USERIDALIAS gg_pdb
Successfully logged into database.

GGSCI 2> ADD TRANDATA angler.fish
INFO    OGG-15132  Logging of supplemental redo data enabled for table ANGLER.FISH.
INFO    OGG-15133  TRANDATA for scheduling columns has been added on table ANGLER.FISH.
INFO    OGG-15135  TRANDATA for instantiation CSN has been added on table ANGLER.FISH.
INFO    OGG-10471  ***** Oracle Goldengate support information on table ANGLER.FISH *****
Oracle Goldengate support native capture on table ANGLER.FISH.
Oracle Goldengate marked following column as key columns on table ANGLER.FISH: ID.

パラメータファイル修正

Oracle GoldenGateの各プロセスのパラメータファイルを修正します。以降では追加したパラメータのみ記載していますが、本記事の最後に、本検証で使用したすべてのパラメータファイルを添付しています。ご興味のある方はご覧ください。

Primary Extract(キャプチャ)用パラメータ

下記パラメータを追加します。 (db11のpe1.prmとdb31のpe3.prmに追加する事)

# 設定値 説明
1
TABLE angler.fish;    
anglerスキーマのfishテーブルをレプリケーション対象として設定します。
Data Pump用パラメータ

下記パラメータを追加します。 (db11のdp1.prmとdb31のdp3.prmに追加する事)

# 設定値 説明
1
TABLE angler.fish;    
anglerスキーマのfishテーブルをレプリケーション対象として設定します。
Replicat用パラメータ

下記パラメータを追加します。 (db11のrp1.prmとdb31のrp3.prmに追加する事)

# 設定値 説明
1
MAP angler.fish, TARGET angler.fish;
anglerスキーマのfishテーブルをレプリケーション対象として設定します。

Auto CDR有効化

sqlplusを起動し、PDBに接続後、dbms_goldengate_admパッケージのadd_auto_cdrプロシージャを実行します。対象テーブルをAuto CDR対象に設定します。(db11とdb31にて実施する事)

# プロシージャパラメータ設定値 説明
1
schema_name => 'ANGLER'
スキーマを指定します。
2
table_name => 'FISH'
テーブルを指定します。
3
record_conflicts => TRUE
競合が発生した際にデータディクショナリに競合のログを記録する場合は、TRUEを設定します。
SQL> BEGIN
  2      dbms_goldengate_adm.add_auto_cdr(
  3          schema_name => 'ANGLER',
  4          table_name => 'FISH',
  5          record_conflicts => TRUE
  6      );
  7  END;
  8  /
PL/SQLプロシージャが正常に完了しました。

なお、データが格納されている既存テーブルをAuto CDR対象として追加する場合は、existing_data_timestamp => TO_TIMESTAMP(‘任意の日付’, ‘yyyy-mm-dd hh24:mi:ss’)パラメータを追加する必要があります。理由は後述します。

Auto CDRを有効化した事により、非表示のタイムスタンプ列が追加されている事を確認します。 sqlplusのシステム変数COLINVISIBLEにON を設定すると、DESCRIBEコマンド実行時に非表示列を表示させる事ができます。

SQL> SET colinvisible ON
SQL> DESC angler.fish
 名前                                    NULL?    型
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL NUMBER
 NAME                                               VARCHAR2(100)
 CDRTS$ROW (INVISIBLE)                     NOT NULL TIMESTAMP(6)

また、ツースムストンテーブルが作成されている事を確認します。

SQL> DESC angler.dt$_fish;
 名前                                    NULL?    型
 ----------------------------------------- -------- ----------------------------
 ID                                                 NUMBER
 DELTIME$$                                          TIMESTAMP(6)

プロセス起動

ggsciを起動し、Oracle GoldenGateのプロセスを起動します。(db11とdb31にて実施する事)
下記はdb11環境の実行例です

$ ./ggsci
GGSCI 1> START ER *
Sending START request to MANAGER ...
EXTRACT DP1 starting

Sending START request to MANAGER ...
EXTRACT PE1 starting

Sending START request to MANAGER ...
REPLICAT RP1 starting


GGSCI 2> INFO ALL
Program     Status      Group       Lag at Chkpt  Time Since Chkpt
MANAGER     RUNNING
EXTRACT     RUNNING     DP1         00:00:00      00:00:04
EXTRACT     RUNNING     PE1         00:00:00      00:00:03
REPLICAT    RUNNING     RP1         00:00:00      00:00:04

競合発生および解決確認

INSERT ROW EXISTS競合を発生させるため、db11とdb31のそれぞれに同一キーのデータをINSERTします。Oracle GoldenGateがターゲットDBへレプリケーションする前に、INSERTする必要があります。

# db11環境 db31環境
1
SQL> INSERT INTO angler.fish (id, name) VALUES
(1,'セイゴ');
1行が作成されました。

SQL> COMMIT;
コミットが完了しました。
2
SQL> INSERT INTO angler.fish (id, name) VALUES
(1,'フッコ');
1行が作成されました。

SQL> COMMIT;
コミットが完了しました。

db31のINSERT処理が、あとに実行されているため、想定動作としては、db11の更新データが破棄されて、db31の更新データが採用されます。
競合タイプごとの解決動作については、前編にて紹介しています。前編をご覧になっていない場合は、ご確認くださいますと幸いです。

想定通りの結果となっているかどうか、fishテーブルを確認します。(db11とdb31にて実施する事)

SQL> SET LINES 120
SQL> COL name FORM a15
SQL> SELECT id, name, cdrts$row FROM angler.fish;

        ID NAME            CDRTS$ROW
---------- --------------- -----------------------------
         1 フッコ          21-10-02 01:15:00.450450

想定通り、db31のデータが採用されて、db11とdb31のデータが一致している事が確認できました。

競合履歴の確認

競合履歴は、下記データディクショナリビューから確認可能です。

  • dba_apply_error_messages
  • dba_apply_error

db11からdb31へのレプリケーション時の競合履歴は、db31側のデータディクショナリビューに格納されます。
db31からdb11へのレプリケーション時の競合履歴は、db11側のデータディクショナリビューに格納されます。

db11のdba_apply_error_messagesを確認します。

SET LINES 180
SQL> COL local_transaction_id FORM a20
SQL> COL error_message FORM a50
SQL> COL object_owner FORM a12
SQL> COL object_name FORM a11
SQL> COL conflict_type FORM a17
SQL> COL applied_state FORM a13
SQL> SELECT local_transaction_id,
  2         error_message,
  3         object_owner,
  4         object_name,
  5         conflict_type,
  6         applied_state
  7    FROM dba_apply_error_messages;

db11のSQL実行結果を下記表に記載します。

  • ERROR_MESSAGE列の情報から、id=1のデータにおいて競合が発生した事が確認できます。
  • CONFLICT_TYPE列の情報から、競合タイプがINSERT ROW EXISTSである事が確認できます。
  • APPLIED_STATE列の情報から、該当データが採用(WON)または破棄(LOST)された事が確認できます。
LOCAL_TRANSACTION_ID ERROR_MESSAGE OBJECT_OWNER OBJECT_NAME CONFLICT_TYPE APPLIED_STATE
1.33.1008 ORA-26799: 列値ANGLERを持つ表PK_FISH.ANGLERで、一意制約FISH.("ID") = (1)に違反しました
ORA-00001: 一意制約(ANGLER.PK_FISH)に反しています
ANGLER FISH INSERT ROW EXISTS WON

同様に、db31のdba_apply_error_messagesのSQL実行結果を下記表に記載します。

  • ERROR_MESSAGE列の情報から、id=1のデータにおいて競合が発生した事が確認できます。
  • CONFLICT_TYPE列の情報から、競合タイプがINSERT ROW EXISTSである事が確認できます。
  • APPLIED_STATE列の情報から、該当データが採用(WON)または破棄(LOST)された事が確認できます。
LOCAL_TRANSACTION_ID ERROR_MESSAGE OBJECT_OWNER OBJECT_NAME CONFLICT_TYPE APPLIED_STATE
5.11.1141 ORA-26799: 列値ANGLERを持つ表PK_FISH.ANGLERで、一意制約FISH.("ID") = (1)に違反しました
ORA-00001: 一意制約(ANGLER.PK_FISH)に反しています
ANGLER FISH INSERT ROW EXISTS LOST

4. Auto CDR運用の考慮事項

Auto CDRを運用するにあたっての考慮事項をいくつか紹介します。

競合履歴のメンテナンス

前述の通り、競合の履歴は、データディクショナリビューを用いて参照可能ですが、蓄積され続けるため、定期的に削除する運用が必要となります。dbms_apply_admパッケージのプロシージャを実行する事により、競合履歴を任意のタイミングで削除可能です。

競合履歴を全部削除する場合は、dbms_apply_adm.delete_all_errors(apply_name=>'OGG$<Replicat名>')を実行します。
db31の実行例を示します。

SQL> EXECUTE dbms_apply_adm.delete_all_errors(apply_name=>'OGG$rp3')
PL/SQLプロシージャが正常に完了しました。

特定トランザクションの履歴のみを削除する場合は、dbms_apply_adm.delete_error(local_transaction_id=>'<dba_apply_error_messages.local_transaction_id列値 >')を実行します。
db11の実行例を示します。

SQL> EXECUTE dbms_apply_adm.delete_error(local_transaction_id=>'1.33.1008')
PL/SQLプロシージャが正常に完了しました。

なお、ご使用のOracle GoldenGateのバージョンによっては、Replicat起動時に競合履歴が削除される動作となります。競合履歴を保管する場合は、データディクショナリビューのデータを定期的に退避する運用が必要になります。本検証環境のOracle GoldenGate19.1.0.0.210720では、Replicat起動時に競合履歴が削除される動作は確認できませんでした。また、指定した時刻以前の履歴を削除できると便利ですが、残念ながらOracle Database 19c 19.12.0.0.0においては、実装されていませんでした。

ツームストンテーブルのメンテナンス

Auto CDR対象テーブルのデータを削除すると、キー値とタイムスタンプがツームストンテーブルに蓄積されるため、ツームストンテーブルの不要データを削除する運用が必要になります。
削除方法は、dbms_goldengate_admパッケージのpurge_tombstonesプロシージャを実行する事により、指定した時刻以前のデータが削除されます。

SQL> EXECUTE dbms_goldengate_adm.purge_tombstones('21-10-02 02:00:00.000000 GMT');
PL/SQLプロシージャが正常に完了しました。

既存テーブルをAuto CDR対象に追加する際の考慮事項

データが格納されているテーブルをAuto CDRの対象に追加する場合は注意が必要です。前述の通り、dbms_goldengate_adm.add_auto_cdrプロシージャを実行する事により、指定したテーブルをAuto CDR対象に追加可能ですが、プロシージャのデフォルト動作では、非表示のタイムスタンプ列の初期値は、プロシージャ実行時のタイムスタンプが設定されてしまいます。
例えば、add_auto_cdrプロシージャを、db11では、2021-10-02 02:10に実行し、db31では、2021-10-02 02:11に実行した場合は下記が、CDRTS$ROW列の初期値に設定されます。

db11側

SQL> SELECT id, name, cdrts$row FROM angler.fish;

        ID NAME                 CDRTS$ROW
---------- -------------------- ------------------------
         1 アジ                 21-10-02 02:10:27.481142
         2 サバ                 21-10-02 02:10:27.481142
         3 マグロ               21-10-02 02:10:27.481142

db31側

SQL> SELECT id, name, cdrts$row FROM angler.fish;

        ID NAME                 CDRTS$ROW
---------- -------------------- ------------------------
         1 アジ                 21-10-02 02:11:09.289390
         2 サバ                 21-10-02 02:11:09.289390
         3 マグロ               21-10-02 02:11:09.289390

上記の通り、CDRTS$ROW列値が異なる状態にて、db11のid=1のデータをUPDATEすると、競合を誤検出してしまいます。以降に競合誤検出の例を示します。

時刻 db11 db31
10:00:00
SQL> UPDATE angler.fish
  2     SET name = 'スズキ'
  3   WHERE id = 1;
1行が更新されました。

SQL> COMMIT;
コミットが完了しました。

[補足]
上記SQL実行により、非表示のタイムスタンプ列が、02:10:27から10:00:00へ更新される。

SQL> COMMIT;
10:00:02

[競合の誤検出]
db11の更新前タイムスタンプ列値02:10:27とdb31のタイムスタンプ列値02:11:09と異なるため、UPDATE ROW EXISTS競合が発生する。

[競合の解消]
db11の更新後タイムスタンプ列値10:00:00が、db31のタイムスタンプ列値2:11:09よりも新しいため、db31のデータをUPDATEする。

10:00:04
SQL> SELECT * FROM angler.fish WHERE id = 1;
        ID NAME 
---------- ----------
         1 スズキ
SQL> SELECT * FROM angler.fish WHERE id = 1;
        ID NAME 
---------- ----------
         1 スズキ

上記例では、データの不整合は発生しませんが、競合が発生した履歴が、データディクショナリビューに記録されてしまい、混乱を招く可能性があります。

競合の誤検出を防止するため、データ格納済みの既存テーブルをAuto CDRの対象に追加する際は、dbms_goldengate_adm.add_auto_cdrプロシージャのexisting_data_timestampパラメータに、各DBに同じタイムスタンプを明示的に設定する事を推奨します。
db11とdb31に同一値を設定する例(CDRTS$ROW列値は、UTC管理のため、-9:00が表示されます)

SQL> BEGIN
  2      dbms_goldengate_adm.add_auto_cdr(
  3          schema_name => 'ANGLER',
  4          table_name => 'FISH',
  5          record_conflicts => TRUE,
  6          existing_data_timestamp => TO_TIMESTAMP('2021-10-02 00:00:00','yyyy-mm-dd hh24:mi:ss')
  7      );
  8  END;
  9  /
PL/SQLプロシージャが正常に完了しました。

SQL> SELECT id, name, cdrts$row FROM angler.fish;
        ID NAME                 CDRTS$ROW
---------- -------------------- ------------------------
         1 スズキ               21-10-01 15:00:00.000000
         2 サバ                 21-10-01 15:00:00.000000
         3 マグロ               21-10-01 15:00:00.000000

5. おわりに

後編では、Auto CDRの有効化方法と競合解決履歴データの確認方法、運用時の考慮事項を紹介しました。Auto CDRでは、非表示のタイムスタンプ列による時刻ベースの競合解決が可能である点をご理解いただけた事と思います。従来のCDRにおいても列値の大小を比較した競合解決が可能ですが、比較する列がテーブルに存在する必要がありましたため、比較列が存在しないテーブルにおいて、時刻ベースの競合解決を行いたいようなケースにおいて、Auto CDRを検討する価値があると考えます。一方、Auto CDRでは、従来のCDRのような柔軟な競合解決ルールを設定できないため、システム要件によっては採用が難しいケースもあるかと思いますが、方式の選択肢が従来よりも増えた点は、設計する側としては喜ばしい(悩ましいとも言えます)事として、活用してくださいますと幸いです。

6. 付録

本検証にて使用したOracle GoldenGateのパラメータを添付します。

付録 (ZIP : 4.12KB)

7. 番外編~私の散歩道~

荒川中流域で釣れたチヌ

徒歩圏内の荒川中流域で釣れたチヌ(クロダイ)です。サイズは大きくありませんが、河口ではなくても、チヌが釣れるのかと驚いて記念に撮った一枚です。チヌの他にも、シーバス、ウナギ、ハゼ、テナガエビ、コイ、フナなど、さまざまなターゲットがいて、一年を通して楽しめる良い釣り場です。

※文章中の商品名、会社名、団体名は、各社の商標または登録商標です。

Oracle GoldenGate自動競合検出および解消機能の紹介(後編)