Oracle Golden Gate を試す（ターゲット側にオートインクリメントのカラムがある場合）

[前回の記事 STEP4 Oracle Golden Gate を使ってみる](https://qiita.com/blueskyarea/items/031a09802f1ad08da346)

ローカルの仮想環境に Oracle Golden Gate の環境を構築し、シンプルな同期処理が可能であることは確認できましたが、どこまで柔軟な同期処理が出来るのか、今回は実験的に以下のような同期処理が可能であるかを確認してみます。

### ケース：ターゲット側にオートインクリメントのカラムがある場合、同期処理時にターゲット側で番号を採番させることは可能か？

#### Oracle Golden Gate を試してみる手順
１．サービスの起動など
２．テスト用テーブルの設計
３．テスト用テーブルの作成
４．Oracle Golden Gate の設定、各プロセス開始
５．同期テスト

##### １．サービスの起動など（ソース側、ターゲット側両方で実行する）
Listener や Oracleインスタンスが仮想環境の起動時にスタートアップする設定になっていない場合、仮想環境を起動する度にスタートしてあげる必要があります。
この作業はソース、ターゲット両方の仮想環境で実行します。

```:仮想環境へログイン
# ソース側
vagrant ssh myora1
# ターゲット側
vagrant ssh myora2
```

```:ソース、ターゲット両方の仮想環境で実行
# ユーザ変更
$ sudo su - oracle

# Listener起動
$ lsnrctl start LISTENER

# Oracleインスタンス 起動
$ sql / as sysdba
SQL> startup

# PDB open
SQL> alter pluggable database all open;
SQL> exit
```

##### ２．テスト用テーブルの設計
今回は以下のようなテーブルを用意したいと思います。
ソース側とターゲット側のテーブル構成はほとんど同じで、主な違いは2点で、テーブル名が違うことと、主キーである order_id カラムがターゲット側ではオートインクリメントのカラムとして定義します。
Oracle Golden Gate で同期処理を行う時に、オートインクリメントのカラムが機能するのかどうかを確かめます。

ソース側 テーブル名: ORDER_X

|  カラム名  |  データ型  |  説明  |
| ---- | ---- | ---- |
|  order_id  |  NUMBER  |このテーブルの主キーで注文をユニークに管理する|
|  item_id  |  NUMBER  |注文を受け付けた商品のID|
|  units  | NUMBER |注文を受け付けた商品の数|
|  created_at  |  DATE  |注文を受け付けた日時|
|  updated_at  |  DATE  |注文を更新した日時。現在時刻を DEFAULT 値とする|
|  version  |  NUMBER  |このレコードが更新された回数。登録時は１で更新される度に１ずつ増える|
|  num_sync  |  NUMBER  |同期処理が行われた回数。登録時は０で、同期される度に１ずつ増える|

ターゲット側 テーブル名: ORDER_Y

|  カラム名  |  データ型  |  説明  |
| ---- | ---- | ---- |
|  order_id  |  AUTO_INCREMENT  |オートインクリメント。このテーブルの主キーで注文をユニークに管理する|
|  item_id  |  NUMBER  |注文を受け付けた商品のID|
|  units  | NUMBER |注文を受け付けた商品の数|
|  created_at  |  DATE  |注文を受け付けた日時|
|  updated_at  |  DATE  |注文を更新した日時|
|  version  |  NUMBER  |このレコードが更新された回数。登録時は１で更新される度に１ずつ増える|
|  num_sync  |  NUMBER  |同期処理が行われた回数。登録時は０で、同期される度に１ずつ増える|

##### ３．テスト用テーブルの作成

```:ソース側(myora1)
# Oracle PDB ログイン
$ sql c##gguser/oracle@//localhost:1521/ORCLPDB1

SQL> CREATE TABLE ORDER_X (order_id NUMBER, item_id NUMBER, units NUMBER, created_at DATE, updated_at DATE, version NUMBER, num_sync NUMBER, primary key(order_id));
Table created.

SQL> DESC ORDER_X;
Name                                      Null?    Type
----------------------------------------- -------- ----------------------------
ORDER_ID                                           NUMBER
ITEM_ID                                            NUMBER
UNITS                                              NUMBER
CREATED_AT                                         DATE
UPDATED_AT                                         DATE
VERSION                                            NUMBER
NUM_SYNC                                           NUMBER

SQL> exit
```

```:ターゲット側(myora2)
# Oracle PDB ログイン
$ sql c##gguser/oracle@//localhost:1521/ORCLPDB1

SQL> CREATE TABLE ORDER_Y (order_id NUMBER GENERATED BY DEFAULT AS IDENTITY, item_id NUMBER, units NUMBER, created_at DATE, updated_at DATE, version NUMBER, num_sync NUMBER, primary key(order_id));
Table created.

SQL> alter table ORDER_Y add constraint ORDER_Y_PK primary key (ORDER_ID);

SQL> DESC ORDER_Y;
Name                                      Null?    Type
----------------------------------------- -------- ----------------------------
ORDER_ID                                  NOT NULL NUMBER
ITEM_ID                                            NUMBER
UNITS                                              NUMBER
CREATED_AT                                         DATE
UPDATED_AT                                         DATE
VERSION                                            NUMBER
NUM_SYNC                                           NUMBER

SQL> select constraint_name, constraint_type from user_constraints where table_name = 'ORDER_Y';
CONSTRAINT_NAME   CONSTRAINT_TYPE
SYS_C007567       C
ORDER_Y_PK        P

SQL> exit
```

ORDER_Y テーブル側の ORDER_ID はオートインクリメントのため、NOT NULL 制約が自動的に付与されています。

##### ４．Oracle Golden Gate の設定、各プロセス開始

```:ターゲット側(myora2)
# ggsci 起動
$ /gg/ggsci

# repli2 編集
GGSCI (myora2) x> edit params repli2

# 以下のように設定する
REPLICAT repli2
USERID c##gguser@ORCLPDB1, PASSWORD oracle
MAP ORCLPDB1.c##gguser.ORDER_X, TARGET ORCLPDB1.c##gguser.ORDER_Y COLMAP(ITEM_ID=ITEM_ID, UNITS=UNITS, CREATED_AT=CREATED_AT, UPDATED_AT=UPDATED_AT, VERSION=VERSION, NUM_SYNC=@COMPUTE(NUM_SYNC + 1));
```

ORDER_X から ORDER_Y に同期させるようにマッピングしています。
ORDER_ID 以外のカラムを明示的にマッピングしています。ORDER_ID 以外のカラムが同期され、ORDER_ID カラムは自動採番にて値が入るようになることを期待しています。
また、NUM_SYNC のカラムには、元の値に１を加算した値が入ることを期待しています。

では、ターゲット側の Golden Gate のプロセスを起動していきます。

```:ターゲット側(myora2)
# Manager 起動
GGSCI (myora2) x> start manager
Manager started.

# Replicat 起動
GGSCI (myora2) x> start repli2

# 起動プロセス確認
GGSCI (myora2) x> info all
Program     Status      Group       Lag at Chkpt  Time Since Chkpt

MANAGER     RUNNING
REPLICAT    RUNNING     REPLI2      00:00:00      187:31:09

# 終了
exit
```

ソース側の Golden Gate のプロセスを起動していきます。

```:ソース側(myora1)
# ggsci 起動
$ /gg/ggsci

# Manager 起動
GGSCI (myora1) x> start manager
Manager started.

# Capture 起動
GGSCI (myora1) x> start capture1

# Pump 起動
GGSCI (myora1) x> start pump1

# 起動プロセス確認
GGSCI (myora1) x> info all
Program     Status      Group       Lag at Chkpt  Time Since Chkpt

MANAGER     RUNNING
EXTRACT     RUNNING     CAPTURE1    00:00:00      187:31:56
EXTRACT     RUNNING     PUMP1       00:00:00      187:33:13

# 終了
exit
```

##### ５．同期テスト
それではソース側のテーブルにデータを挿入し、ターゲット側に同期されるかを確認していきます。

```:ソース側(myora1)
# Oracle PDB ログイン
$ sql c##gguser/oracle@//localhost:1521/ORCLPDB1

SQL> SELECT * FROM ORDER_X WHERE ORDER_ID = 100;

no rows selected

SQL> INSERT INTO ORDER_X (ORDER_ID, ITEM_ID, UNITS, CREATED_AT, UPDATED_AT, VERSION, NUM_SYNC) VALUES (100, 200, 3, sysdate, sysdate, 1, 0);

1 row created.

SQL> commit;

Commit complete.

SQL> SELECT * FROM ORDER_X WHERE ORDER_ID = 100;
  ORDER_ID   ITEM_ID   UNITS CREATED_AT   UPDATED_AT     VERSION   NUM_SYNC
       100       200       3 19-SEP-21    19-SEP-21            1          0
```

```:ターゲット側(myora2)
# Oracle PDB ログイン
$ sql c##gguser/oracle@//localhost:1521/ORCLPDB1

SQL> SELECT * FROM ORDER_Y;
no rows selected
```

残念ながら同期処理はされていませんでした。
ログを確認してみると、"ORDER_ID is missing" という ERROR が発生しています。
つまり、"ORDER_ID" もマッピングしなければならないようです。

```
$ /gg/ggserr.log
ERROR   OGG-00918  Oracle GoldenGate Delivery for Oracle, repli2.prm:  Key column ORDER_ID is missing from map.
```

ORDER_ID のカラムについても、マッピングし同期させるように REPLICAT の設定を変更してみます。

```:ターゲット側(myora2)
# ggsci 起動
$ /gg/ggsci

# repli2 編集
GGSCI (myora2) x> edit params repli2

# 以下のように編集する
REPLICAT repli2
USERID c##gguser@ORCLPDB1, PASSWORD oracle
MAP ORCLPDB1.c##gguser.ORDER_X, TARGET ORCLPDB1.c##gguser.ORDER_Y COLMAP(ORDER_ID=ORDER_ID, ITEM_ID=ITEM_ID, UNITS=UNITS, CREATED_AT=CREATED_AT, UPDATED_AT=UPDATED_AT, VERSION=VERSION, NUM_SYNC=@COMPUTE(NUM_SYNC + 1));

# Replicat 起動
GGSCI (myora2) x> start repli2
```

```:ターゲット側(myora2)
# Oracle PDB ログイン
$ sql c##gguser/oracle@//localhost:1521/ORCLPDB1

SQL> SELECT * FROM ORDER_Y;
  ORDER_ID   ITEM_ID   UNITS CREATED_AT   UPDATED_AT     VERSION   NUM_SYNC
       100       200       3 19-SEP-21    19-SEP-21            1          1
```

ORDER_ID のカラムをマッピングした後は、同期されていることが確認できました。

#### 結果
以上のテスト結果から、以下のことが確認できました。

１．ターゲット側のオートインクリメントのカラムに対して、マッピングの設定をしなければ、自動的に番号が採番されることを期待していたが、出来なかった。
⇒マッピングの設定をしないと、エラーになってしまう。同期処理の時に、オートインクリメントのカラムはターゲット側で自動採番させることは不可能かもしれない。Oracle ドキュメントを探しても、そのような方法は見当たらなかった。

２．同期処理の時に、簡単な計算（今回は+1の加算処理をした上で、ターゲット側のカラムに反映させることには成功した。