Oracle GoldenGate自動競合検出および解消機能の紹介 その1

# はじめに
データレプリケーション製品のOracle GoldenGateでは、アクティブ・アクティブ型の双方向レプリケーションがサポートされています。本記事にて機能概要を紹介します。

# Oracle GoldenGate双方向構成について
双方向構成の環境では、同一データセットのデータベースが存在し、業務アプリケーションは、任意のデータベースを更新する事が可能です。ソースDBにて更新されたトランザクションデータは、Oracle GoldenGateによって、ターゲットDBにレプリケーションされます。結果として各システムのデータベースデータは同一の内容になります。
![img01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3330820/5f4b4bda-be6d-b770-7756-23211fc3666d.png)


上記双方向構成においては、Primary SystemからSecondary Systemへのレプリケーション方向(上記図の青色)では、Primary SystemがソースDB、Secondary SystemがターゲットDBとなります。下記の流れでOracle GoldenGateによるレプリケーションが行われます。

1. Primary ExtractプロセスがソースDBから更新データを抽出し、LocalTrailファイルへ出力する。
2. DataPumpプロセスがLocalTrailファイルを読み取り、Secondary SystemのCollectorプロセスへ送信する。
3. Collectorプロセスが、RemoteTrailファイルへ出力する。
4. Replicatプロセスが、RemoteTrailファイルを読み取り、ターゲットDBを更新する。

逆にSecondary SystemからPrimary Systemへのレプリケーション方向(上記図の黄色)では、Secondary SystemがソースDB、Primary SystemがターゲットDBとなります。

業務アプリケーション等によるソースDBへの更新処理とOracle GoldenGateによるターゲットDBへの更新処理は非同期で行われるため、業務アプリケーションの処理時間影響は少ないですが、非同期で処理されるが故に、双方向構成で各データベースの同一データが、ほぼ同時に更新されてしまうと、データ競合の問題が発生します。競合の具体的な例を下記に示します。

| 時刻 | Primary System | Secondary System |
|:-|:-|:-|
|10:00:00   |UPDATE fish SET name = 'サバ' WHERE id = 1;<BR>COMMIT;|   |
|10:00:01   |   |UPDATE fish SET name = 'アジ' WHERE id = 1;<BR>COMMIT;    |
|10:00:05   |GoldenGateが、上記更新データ(サバ)をSecondary Systemへレプリケーションする。   |   |
|10:00:06   |   |GoldenGateが、上記更新データ(アジ)をPrimary Systemへレプリケーションする。   |
|10:00:07   |Primary Systemのデータが、サバからアジに更新される。<BR><BR>SELECT name FROM fish WHERE id = 1;<BR>NAME<BR>-------<BR>アジ   |Secondary Systemのデータが、アジからサバに更新される。<BR><BR>SELECT name FROM fish WHERE id = 1;<BR>NAME<BR>----------<BR>サバ   |

fishテーブルのid=1のデータが、ほぼ同時に更新された結果、競合が発生し、各データベースのデータは異なる状態となってしまいました。双方向構成の環境を運用する場合は、競合を解消するための仕組みを検討する必要があります。Oracle GoldenGateでは、競合を解消するための機能が、下記二つの方式にて提供されています。

* Conflict Detection and Resolution(以降CDRと表記) Oracle GoldenGate 11g 11.2以降から使用可能
* Automatic Conflict Detection and Resolution(以降Auto CDRと表記) Oracle GoldenGate 12c 12.3.0.1以降から使用可能

CDRは、競合の検出と解消するためのルールを柔軟な設計が可能です。Auto CDRは、競合の検出と解消ルールが自動化されており、比較的お手軽に導入する事が可能です。本記事では、Auto CDRを取り上げて紹介します。

# Auto CDRの紹介
### システム要件
Auto CDRを使用するためのシステム要件については、Oracle GoldenGate製品マニュアルに記載があります。Oracle GoldenGate 19.1の場合、マニュアル「Oracle DatabaseのためのOracle GoldenGateの使用」の「 [自動競合検出および解決の要件](https://docs.oracle.com/cd/F22974_01/oracle-db/automatic-conflict-detection-and-resolution2.html) 」をご参照願います。

### Auto CDR有効化による定義変更
競合が発生する可能性のあるテーブル単位に、Auto CDRを有効化する必要があります。Auto CDRを有効化するとテーブル単位に下記が追加されます。

* 対象テーブルに非表示のタイムスタンプ列が追加される(列名は、CDRTS\\$ROW)。
* ツームストンテーブルが追加される(テーブル名は、DT\\$_<テーブル名>)。

実際にAuto CDRを有効化したfishテーブルの定義を、sqlplusを使用して確認した結果を下記に示します。sqlplusのシステム変数COLINVISIBLEをONに設定すると、非表示列の確認が可能です。
補足: fishテーブルのキー列はID列です。

```
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)
```
次にfishテーブルにデータを追加した際の動作を示します。
fishテーブルのCDRTS\\$ROW列に、INSERT実行時のタイムスタンプが格納されます。(UPDATE時も同様)
補足: CDRTS\\$ROWに格納される時刻データは、UTCで管理されます。
```
SQL> INSERT INTO angler.fish (id, name) VALUES (1,'セイゴ');
1行が作成されました。

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

SQL> SELECT id, name, cdrts$row FROM angler.fish;
        ID NAME       CDRTS$ROW
---------- ---------- ------------------------
         1 セイゴ     21-10-02 05:42:53.492774
```
DELETE処理を実行するとツームストンテーブルに、削除したキー列値とタイムスタンプが記録されます。
実際にfishテーブルのデータをDELETEした場合の動作を示します。
補足: ツームストンテーブルのDELTIME\\$\\$列もUTC管理です。
```
SQL> DELETE FROM angler.fish WHERE id = 1;
1行が削除されました。

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

SQL> SELECT * FROM angler.dt$_fish;
        ID DELTIME$$                  
---------- ------------------------
         1 21-10-02 08:28:58.396096
```
Auto CDRでは、上記の非表示列(CDRTS\\$ROW)およびツームストンテーブルのタイムスタンプ列(DELTIME\\$\\$)の情報を使用して、競合の検出と解消が行われます。ちなみにツームストン(tombstone)は、「墓石」という意味だそうです。削除された情報が墓石に刻まれているイメージでしょうか。私の場合、プロレス技のツームストン・パイルドライバが思い浮かびました。
### 競合検出と解消方法
Auto CDRにより、競合がどのように検出されて、またどのように解消されるのかを競合タイプごとに示します。
#### 競合タイプ: INSERT ROW EXISTS
* 競合の検出<BR>ターゲットDBへのINSERT実行時に、同一キーのデータが存在した場合
* 競合の解消<BR>ソースDBとターゲットDBのCDRTS\\$ROW列値を比較し、新しいほうのデータが採用される

下記にINSERT ROW EXISTS競合と解消の例を示します。
| 時刻 | Primary System | Secondary System |
|:-|:-|:-|
|10:00:00   |INSERT INTO fish (id, name) VALUES (1, 'サバ');<BR>COMMIT;<BR><BR>[補足]<BR>CDRTS\\$ROW列が、システム時刻の10:00:00で更新される。   |   |
|10:00:01   |   |INSERT INTO fish (id, name) VALUES (1, 'アジ');<BR>COMMIT;<BR><BR>[補足]<BR>CDRTS\\$ROW列が、システム時刻の10:00:01で更新される。   |
|10:00:02   |   |Primary SystemからのレプリケーションデータをSecondary Systemへ適用する際に下記が発生する。<BR><BR>[競合の検出]<BR>同一キーのデータがSecondary Systemに存在するため、INSERT ROW EXISTS競合が発生する。<BR><BR>[競合の解消]<BR>Secondary SystemのCDRTS\\$ROWが新しいため、Primary SystemのINSERTデータは破棄される。   |
|10:00:03   |Secondary SystemからのレプリケーションデータをPrimary Systemへ適用する際に下記が発生する。<BR><BR>[競合の検出]<BR>同一キーのデータがPrimary Systemに存在するため、INSERT ROW EXISTS競合が発生する。<BR><BR>[競合の解消]<BR>Secondary SystemのCDRTS\\$ROWが新しいため、Primary SystemのデータをUPDATEする。<BR><BR>UPDATE fish SET name='アジ' WHERE id=1;   |   |
|10:00:04   |SELECT name FROM fish WHERE id = 1;<BR>NAME<BR>----------<BR>アジ   |SELECT name FROM fish WHERE id = 1;<BR>NAME<BR>----------<BR>アジ   |

上記の通り、データの内容は一致した状態となり、データ不整合は発生しませんが、結果として、Primary Systemに登録されたデータが破棄されてしまいます。実際のシステム運用において、上記の動作が許容できるか慎重に検討する必要があります。許容できない場合は、Primary SystemとSecondary Systemのキーの値が、重複しないような設計が必要になります。

#### 競合タイプ: UPDATE ROW EXISTS
* 競合の検出<BR>ターゲットDBへのUPDATE実行時に、ソースDBの更新前CDRTS\\$ROW列値とターゲットDBのCDRTS\\$ROW列値が異なる場合
* 競合の解消<BR>ソースDBの更新後CDRTS\\$ROW列値とターゲットDBのCDRTS\\$ROW列値を比較し、新しいほうのデータが採用される

下記にUPDATE ROW EXISTS競合と解消の例を示します。
| 時刻 | Primary System | Secondary System |
|:-|:-|:-|
|10:00:00   |UPDATE fish SET name = 'サバ' WHERE id = 1;<BR>COMMIT;<BR><BR>[補足]<BR>CDRTS\\$ROW列が、システム時刻の10:00:00で更新される。(更新前のCDRTS\\$ROW列は、9:50:00とする)   |   |
|10:00:01   |   |UPDATE fish SET name = 'アジ' WHERE id = 1;<BR>COMMIT;<BR><BR>[補足]<BR>CDRTS\\$ROW列が、システム時刻の10:00:01で更新される。(更新前のCDRTS\\$ROW列は、9:50:00とする)   |
|10:00:02   |   |Primary SystemからのレプリケーションデータをSecondary Systemへ適用する際に下記が発生する。<BR><BR>[競合の検出]<BR>Primary Systemの更新前CDRTS\\$ROW(9:50:00)とSecondary SystemのCDRTS\\$ROW(10:00:01)が一致しないため、UPDATE ROW EXISTS競合が発生する。<BR><BR>[競合の解消]<BR>Secondary SystemのCDRTS\\$ROWが新しいため、Primary SystemのUPDATEデータは破棄される。   |
|10:00:03   |Secondary SystemからのレプリケーションデータをPrimary Systemへ適用する際に下記が発生する。<BR><BR>[競合の検出]<BR>Secondary Systemの更新前CDRTS\\$ROW(9:50:00)とPrimary SystemのCDRTS\\$ROW(10:00:00)が一致しないため、UPDATE ROW EXISTS競合が発生する。<BR><BR>[競合の解消]<BR>Secondary SystemのCDRTS\\$ROWが新しいため、Primary SystemのデータをUPDATEする。<BR><BR>UPDATE fish SET name = 'アジ' WHERE id = 1;   |   |
|10:00:04   |SELECT name FROM fish WHERE id = 1;<BR>NAME<BR>----------<BR>アジ   |SELECT name FROM fish WHERE id = 1;<BR>NAME<BR>----------<BR>アジ   |

上記の通り、データの内容は一致した状態となり、データ不整合は発生しませんが、結果として、Primary Systemに登録されたデータが破棄されてしまいます。実際にシステム運用において、上記の動作が許容できるか慎重に検討する必要があります。許容できない場合は、アクティブ/アクティブ型の双方向レプリケーションの構成を見直し、片方向レプリケーション構成を検討するなどの対応をとる必要があります。

#### 競合タイプ: UPDATE ROW MISSING
* 競合の検出<BR>ターゲットDBへのUPDATE実行時に、更新対象行が存在しない場合
* 競合の解消<BR>ソースDBの更新後CDRTS\\$ROW列値とターゲットDBのDELTIME\\$\\$列値(ツームストンテーブル)を比較し、新しいほうのデータが採用される

#### 競合タイプ: DELETE ROW EXISTS
* 競合の検出<BR>ターゲットDBへのDELETE実行時に、ソースDBの更新前CDRTS\\$ROW列値とターゲットDBのCDRTS\\$ROW列値が異なる場合
* 競合の解消<BR>ソースDBのDELTIME\\$\\$列値(ツームストンテーブル)とターゲットDBのCDRTS\\$ROW列値を比較し、新しいほうのデータが採用される

下記にUPDATE ROW MISSING競合および、DELETE ROW EXISTS競合の例を示します。
Primary System のUPDATE処理がSecondary Systemにレプリケーションされる際に、UPDATE ROW MISSING競合が発生します。またSecondary SystemのDELETE処理がPrimary Systemにレプリケーションされる際に、DELETE ROW EXISTS競合が発生します。

| 時刻 | Primary System | Secondary System |
|:-|:-|:-|
|10:00:00   |UPDATE fish SET name = 'サバ' WHERE id = 1;<BR>COMMIT;<BR><BR>[補足]<BR>CDRTS\\$ROW列が、システム時刻の10:00:00で更新される。(更新前のCDRTS\\$ROW列は、9:50:00とする)   |   |
|10:00:01   |   |DELETE FROM fish WHERE id = 1;<BR>COMMIT;<BR><BR>[補足]<BR>ツームストンテーブルにキー情報(id=1)とタイムスタンプ(10:00:01)が登録される。   |
|10:00:02   |   |DELETE FROM fish WHERE id = 1;<BR>COMMIT;<BR><BR>[補足]<BR>ツームストンテーブルにキー情報(id=1)とタイムスタンプ(10:00:01)が登録される。Primary SystemからのレプリケーションデータをSecondary Systemへ適用する際に下記が発生する。<BR><BR>[競合の検出]<BR>id=1のデータがSecondary Systemに存在しないため、UPDATE ROW MISSING競合が発生する。<BR><BR>[競合の解消]<BR>Secondary SystemのDELTIME\\$\\$が新しいため、Primary SystemのUPDATEデータは破棄される。   |
|10:00:03   |Secondary SystemからのレプリケーションデータをPrimary Systemへ適用する際に下記が発生する。<BR><BR>[競合の検出]<BR>Secondary Systemの更新前CDRTS\\$ROW(9:50:00)とPrimary SystemのCDRTS\\$ROW(10:00:00)が一致しないため、DELETE ROW EXISTS競合が発生する。<BR><BR>[競合の解消]<BR>Secondary SystemのDELTIME\\$\\$が、Primary SystemのCDRTS\\$ROWよりも新しいため、Primary SystemのデータをDELETEする。<BR><BR>DELETE FROM fish WHERE id = 1;   |   |
|10:00:04   |SELECT * FROM fish;<BR>レコードが選択されませんでした。   |SELECT * FROM fish;<BR>レコードが選択されませんでした。   |

上記の通り、データの内容は一致した状態となり、データ不整合は発生しませんが、結果として、Primary Systemに登録されたデータが破棄されてしまいます。実際のシステム運用において、上記の動作が許容できるか慎重に検討する必要があります。許容できない場合は、片方向レプリケーション構成を検討など、アクティブ/アクティブ型の双方向レプリケーションの構成を見直す必要があります。

#### 競合タイプ: DELETE ROW MISSING
* 競合の検出<BR>ターゲットDBへのDELETE実行時に、削除対象行が存在しない場合
* 競合の解消<BR>不整合は発生しないため、解消するための処理は行われない

下記にDELETE ROW MISSING競合の例を示します。
| 時刻 | Primary System | Secondary System |
|:-|:-|:-|
|10:00:00   |DELETE FROM fish WHERE id = 1;<BR>COMMIT;<BR><BR>補足<BR>ツームストンテーブルにキー情報(id=1)とタイムスタンプ(10:00:00)が登録される。   |   |
|10:00:01   |   |DELETE FROM fish WHERE id = 1;<BR>COMMIT;<BR><BR>補足<BR>ツームストンテーブルにキー情報(id=1)とタイムスタンプ(10:00:01)が登録される。   |
|10:00:02   |   |Primary SystemからのレプリケーションデータをSecondary Systemへ適用する際に下記が発生する。<BR><BR>[競合の検出]<BR>id=1のデータがSecondary Systemに存在しないため、DELETE ROW MISSING競合が発生する。   |
|10:00:03   |Secondary SystemからのレプリケーションデータをPrimary Systemへ適用する際に下記が発生する。<BR><BR>[競合の検出]<BR>id=1のデータがPrimary Systemに存在しないため、DELETE ROW MISSING競合が発生する。   |   |
|10:00:04   |SELECT * FROM fish;<BR>レコードが選択されませんでした。   |SELECT * FROM fish;<BR>レコードが選択されませんでした。   |

# おわりに
Auto CDRの概要と、競合タイプごとにOracle GoldenGateがどのように競合を解消するのかを紹介しました。初めてご覧になった方には理解が難しかったかもしれませんが、Auto CDRにおける競合解決は、"タイムスタンプが古いデータは破棄されて、タイムスタンプの新しいデータが採用される"、とご理解いただくのが良いと思います。
次回は、実機を用いて、Auto CDRの有効化方法と競合解決履歴データの確認方法、運用時の考慮事項を紹介します。