Oracle GoldenGate自動競合検出および解消機能の紹介(後編)
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> COMMIT; |
|
10:00:02 |
[競合の誤検出] [競合の解消] |
|
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のような柔軟な競合解決ルールを設定できないため、システム要件によっては採用が難しいケースもあるかと思いますが、方式の選択肢が従来よりも増えた点は、設計する側としては喜ばしい(悩ましいとも言えます)事として、活用してくださいますと幸いです。
7. 番外編~私の散歩道~
徒歩圏内の荒川中流域で釣れたチヌ(クロダイ)です。サイズは大きくありませんが、河口ではなくても、チヌが釣れるのかと驚いて記念に撮った一枚です。チヌの他にも、シーバス、ウナギ、ハゼ、テナガエビ、コイ、フナなど、さまざまなターゲットがいて、一年を通して楽しめる良い釣り場です。
※文章中の商品名、会社名、団体名は、各社の商標または登録商標です。