Oracle GoldenGate Free を docker compose で試してみる

# はじめに
## Oracle GoldenGate とは
Oracle GoldenGate は異種DB間のリアルタイムデータ複製のためのソフトウェア・パッケージです。  
今回利用する Oracle GlodenGate Free には[制限事項](https://docs.oracle.com/cd/F87002_01/uggfe/what-is-goldengate-free.html#GUID-E1429CD9-18E5-4349-8B1C-0B11013DD8A7)がありますが、今回は検証目的ということで Free を利用します。  

## 確認すること
Oracle DB 間でデータ同期ができることを確認します。  
具体的な手順は 21.3 -> 21.3 を記載し、それ以外は差分を記載します。  

- 21.3 -> 21.3
- 23.5 -> 23.5
- 21.3 -> 23.5

# 確認
## 環境
今回は以下の環境を利用しました。  

- Oracle Enterprise Linux 8.10
- CPU: 8v
- Memory: 16GiB
- パッケージ関係は以下

```bash
containerd.io.x86_64                             1.6.32-3.1.el8
docker-buildx-plugin.x86_64                      0.14.0-1.el8
docker-ce.x86_64                                 3:26.1.3-1.el8
docker-ce-cli.x86_64                             1:26.1.3-1.el8
docker-ce-rootless-extras.x86_64                 26.1.3-1.el8
docker-compose-plugin.x86_64                     2.27.0-1.el8
```

## container-registry へのログイン
Free のみの利用でしたらログインは不要ですが、それ以外を利用する場合はログインして規約に同意が必要になります。  
規約への同意は[このページ](https://container-registry.oracle.com/ords/ocr/ba/database/enterprise)の右側から可能ですので、実施してください。  
同意したら`docker login container-registry.oracle.com`を実施してログインしてください。

## 21.3 -> 21.3の検証
### ファイル配置
作業ディレクトリにて以下のファイルを配置します。  
ディレクトリ構成は以下の感じです。  

```
<作業ディレクトリ>
├ compose.yaml
└ startupScript
　 ├ 01_user.sql
　 └ 02_data.sql
```

### compose.yaml
docker compose用の設定ファイルです。  
ulimitの設定と環境変数の設定をしています(Oracle GoldenGateも含まれてます)。  
以下の点にご注意ください。  

- ORACLE_SID と ORACLE_PDB が free のものなっています
    - これは検証の手間を考えて固定したいのですが、 free 側でこれらの環境変数が設定できないため free 側に合わせるようしています
- ulimit 系は [Oracle公式のリポジトリ](https://github.com/oracle/docker-images/tree/main/OracleDatabase/SingleInstance)の起動コマンドに合わせています
- DBのデータの永続化は考えていません、したい場合は以下を実施してください
    -  `/opt/oracle/oradata` に対して volume をマウントするようにしてください
    - マウントするホスト側のディレクトリに対して `chown 54321:54321` を実行してください
- パスワード管理を考慮していません、検証目的以外の場合は適宜 KMS などにキーは格納するようにしてください

<details><summary>compose.yaml</summary>

```yaml
x-ulimits: &ulimits
  nofile:
    soft: 1024
    hard: 65536
  nproc:
    soft: 2047
    hard: 16384
  stack:
    soft: 10485760
    hard: 33554432
  memlock: 3221225472

x-oraenvs: &oraenvs
  - ORACLE_PWD=welcome##1
  - ENABLE_ARCHIVELOG=true
  - INIT_SGA_SIZE=4096
  - INIT_PGA_SIZE=1024
  - ORACLE_SID=FREE
  - ORACLE_PDB=FREEPDB1

services:
  source-db:
    image: container-registry.oracle.com/database/enterprise:21.3.0.0
    container_name: source-db
    ulimits: *ulimits
    environment: *oraenvs
    volumes:
      - ./startupScript:/opt/oracle/scripts/startup
    ports:
      - "1521:1521"
  target-db:
    image: container-registry.oracle.com/database/enterprise:21.3.0.0
    container_name: target-db
    ulimits: *ulimits
    environment: *oraenvs
    ports:
      - "1621:1521"
  ogg:
    image: container-registry.oracle.com/goldengate/goldengate-free:latest
    container_name: ogg
    ports:
      - "8080:80"
```
</details>

### startupScript
初期データ投入用のSQLを配置します。  
導入先は FREEPDB1 になっています。

<details><summary>startupScript/01_init.sql</summary>

```sql
alter session set container = FREEPDB1;
CREATE USER project_manager IDENTIFIED BY welcome##2;
GRANT CONNECT, RESOURCE TO project_manager;
GRANT CREATE SESSION, CREATE TABLE, CREATE VIEW, CREATE PROCEDURE, CREATE SEQUENCE, CREATE TRIGGER, CREATE SYNONYM TO project_manager;
ALTER USER project_manager QUOTA UNLIMITED ON USERS;
```
</details>

<details><summary>startupScript/02_data.sql</summary>

```sql
-- プロジェクト管理データベーススキーマ
alter session set container = FREEPDB1;
alter session set current_schema = project_manager;
-- プロジェクト情報テーブル
CREATE TABLE PROJECTS (
    PROJECT_ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    PROJECT_NAME VARCHAR2(100) NOT NULL,
    START_DATE DATE NOT NULL,
    END_DATE DATE,
    BUDGET NUMBER(15, 2)
);

-- 初期データ
INSERT INTO PROJECTS (PROJECT_NAME, START_DATE, END_DATE, BUDGET) VALUES
('New Website Launch', TO_DATE('2024-08-01', 'YYYY-MM-DD'), TO_DATE('2024-12-31', 'YYYY-MM-DD'), 50000),
('Mobile App Development', TO_DATE('2024-09-15', 'YYYY-MM-DD'), NULL, 150000),
('Marketing Campaign', TO_DATE('2024-10-01', 'YYYY-MM-DD'), TO_DATE('2025-03-31', 'YYYY-MM-DD'), 30000);

-- 従業員情報テーブル
CREATE TABLE EMPLOYEES (
    EMPLOYEE_ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    FIRST_NAME VARCHAR2(50) NOT NULL,
    LAST_NAME VARCHAR2(50) NOT NULL,
    EMAIL VARCHAR2(100),
    PHONE_NUMBER VARCHAR2(20),
    HIRE_DATE DATE
);

-- 初期データ
INSERT INTO EMPLOYEES (FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE) VALUES
('John', 'Doe', 'john.doe@example.com', '123-456-7890', TO_DATE('2022-01-15', 'YYYY-MM-DD')),
('Jane', 'Smith', 'jane.smith@example.com', '123-456-7891', TO_DATE('2023-04-23', 'YYYY-MM-DD')),
('Emily', 'Davis', 'emily.davis@example.com', '123-456-7892', TO_DATE('2021-07-30', 'YYYY-MM-DD')),
('Michael', 'Brown', 'michael.brown@example.com', '123-456-7893', TO_DATE('2020-11-10', 'YYYY-MM-DD'));

-- プロジェクトメンバーテーブル
CREATE TABLE PROJECT_MEMBERS (
    MEMBER_ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    PROJECT_ID NUMBER NOT NULL,
    EMPLOYEE_ID NUMBER NOT NULL,
    ROLE VARCHAR2(50),
    CONSTRAINT FK_PROJECT FOREIGN KEY (PROJECT_ID) REFERENCES PROJECTS(PROJECT_ID),
    CONSTRAINT FK_EMPLOYEE FOREIGN KEY (EMPLOYEE_ID) REFERENCES EMPLOYEES(EMPLOYEE_ID)
);

-- 初期データ
INSERT INTO PROJECT_MEMBERS (PROJECT_ID, EMPLOYEE_ID, ROLE) VALUES
(1, 1, 'Project Manager'),
(1, 2, 'Web Developer'),
(2, 3, 'App Developer'),
(3, 4, 'Marketing Specialist');

-- タスクテーブル
CREATE TABLE TASKS (
    TASK_ID NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    PROJECT_ID NUMBER NOT NULL,
    TASK_NAME VARCHAR2(100) NOT NULL,
    ASSIGNED_TO NUMBER,
    STATUS VARCHAR2(20) DEFAULT 'Pending',
    DEADLINE DATE,
    CONSTRAINT FK_PROJECT_TASK FOREIGN KEY (PROJECT_ID) REFERENCES PROJECTS(PROJECT_ID),
    CONSTRAINT FK_ASSIGNEE FOREIGN KEY (ASSIGNED_TO) REFERENCES EMPLOYEES(EMPLOYEE_ID)
);

-- 初期データ
INSERT INTO TASKS (PROJECT_ID, TASK_NAME, ASSIGNED_TO, STATUS, DEADLINE) VALUES
(1, 'Design Homepage', 2, 'In Progress', TO_DATE('2024-09-15', 'YYYY-MM-DD')),
(1, 'Develop Backend', 2, 'Pending', TO_DATE('2024-10-01', 'YYYY-MM-DD')),
(2, 'Create App Prototype', 3, 'Pending', TO_DATE('2024-11-30', 'YYYY-MM-DD')),
(3, 'Launch Social Media Ads', 4, 'Pending', TO_DATE('2024-12-01', 'YYYY-MM-DD'));
```
</details>

### docker compose の起動
docker compose コマンドを利用して Oracle DB と GoldenGate を起動していきます。

```bash
## 起動
docker compose up -d

## ログ確認
docker compose logs

## oggadminのパスワード確認
###  Password for OGG administrative user 'oggadmin' is '***' という列を見る
docker compose logs ogg | head

## source-dbにデータが入っている(ユーザがいる)こと確認&archivelog modeか確認
docker compose exec -T source-db sqlplus -S / as sysdba << EOF
alter session set container = FREEPDB1;
select username from dba_users where username = 'PROJECT_MANAGER';
select count(*) from PROJECT_MANAGER.PROJECTS;
select count(*) from PROJECT_MANAGER.EMPLOYEES;
select count(*) from PROJECT_MANAGER.PROJECT_MEMBERS;
select count(*) from PROJECT_MANAGER.TASKS;
archive log list
EOF
sqlplus -S project_manager/welcome##2@localhost:1521/FREEPDB1 << EOF
select systimestamp from dual;
EOF

## target-dbにデータが入っていない(ユーザがいない)こと確認&archivelog modeか確認
docker compose exec -T target-db sqlplus -S / as sysdba << EOF
alter session set container = FREEPDB1;
select username from dba_users where username = 'PROJECT_MANAGER';
archive log list
EOF

## archivelog modeではなかったら以下の手順でONにしてください。
docker compose exec -T <source-db or target-db> sqlplus -S / as sysdba << EOF
shutdown immediate;
startup mount;
alter database archivelog;
alter database open;
EOF
```

### GoldenGate でデータ同期設定を入れる
全て起動したら GoldenGate の WebUI(localhost:8080でアクセスできます) に入ってデータ同期の設定を入れます。  
ここからは実際に行った画像と共に確認していきます。  

#### 1. WebUI アクセス
- ユーザ名とパスワード入力
    - oggadminとさっき確認したパスワードを入力してください

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/00fab9b4-04cc-3eb3-46e0-a2f9a5b438df.png)

- connection 作成の選択

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/ed64a20d-47e7-69e2-3f46-d3e0f4d48e22.png)

#### 2. Source DB の Connection 作成
同期元のDBへの接続を作成します。  

- source db connection 作成 - 1つ目
    - 基本情報を入れます

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/9e4d83f5-28ee-bcc5-eacf-f37a65670620.png)

- source db connection 作成 - 2つ目
    - 対象のホスト名など入れます
    - `SYSDBA priviledges available` を選択して開いたウィンドウはSYSとORACLE_PWDに設定したパスワードを入力してください

![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/5fd8570c-66c1-920f-f910-913ffdb2226c.png)

![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/9d83ef0b-3447-f96d-cab7-c7881295f0c8.png)

- source db connection 作成 - 3つ目
    - ggadmin作成します(パスワードは適切に入力してください)

![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/797cc875-4aa3-53e3-d156-33897d7dca1b.png)

- source db connection 作成 - 3つ目
    - ggadminを実際に作成&権限付与します
    - `Run analysis` を選択して出てきた SQL 確認したら `Run SQL` 選択します
    - 成功したら `prepare database result` が表示されます

![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/7c0ed4cd-d14d-6730-d3b3-786137d9d472.png)

![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/ea93bab1-8ca6-f984-2e57-d9c5da981c5d.png)

![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/b52e6192-f76a-de30-17d4-88a94672acd8.png)

- source db connection 作成 - 4つ目
    - 確認して終わりです

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/f7e57b44-dc71-ba87-adf4-3f29fa6ceff9.png)


#### 3. Target DB の Connection 作成
同期先のDBへの接続を作成します。  
設定画面は Source DB とほぼ同じなので割愛します。  
ここまでで以下のようになっていればOKです。  

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/b66a67a9-1694-80df-d9fe-a4a6e639a1a5.png)

#### 4. 複製設定( Pipeline )作成

- pipeline 作成の選択

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/696a0545-4c86-f0ae-8daf-ef6e1927b33e.png)

- pipeline 作成 - 1つ目
  - Recipe 選択します
  - 今回は一方通行の複製を試します

![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/bfbb3008-5dde-c260-c1c4-87d2bb203700.png)

- pipeline 作成 - 2つ目
  - Name などを入力します

![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/283020a1-c44e-5012-0ea5-9643a65112da.png)

- pipeline 作成 - 3つ目
  - 同期元と先を指定します

![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/5f3233b2-5b55-8ef7-80ff-f1853260eeb0.png)

#### 5. 複製設定編集~同期開始まで
Pipeline を作成したので、複製の設定を確認して同期開始します。  

- PDBADMINの同期を外して Save を押します

![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/ca3caaa3-bdd8-b1ad-4c03-b42ed9ae3692.png)

- Start を押して Status が全て Completed になればOKです
  - 認証作成やExtract/Replicatの作成・NWimportでの初期同期をやってくれます
  - 完了したら Runtime 画面に遷移します

![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/84b2d8fe-b005-2d6e-dab8-dc556ff782ac.png)

![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/193a7db5-5fa1-15db-ab88-9c939771882a.png)

### 同期確認
source-db にデータを投入して伝搬されているか確認します。

```bash
## まずはtarget-dbにデータが入っているか確認(source-dbとtarget-dbで件数確認)
sqlplus -S sys/welcome##1@localhost:1521/FREEPDB1 as sysdba << EOF
select username from dba_users where username = 'PROJECT_MANAGER';
select count(*) from PROJECT_MANAGER.PROJECTS;
select count(*) from PROJECT_MANAGER.EMPLOYEES;
select count(*) from PROJECT_MANAGER.PROJECT_MEMBERS;
select count(*) from PROJECT_MANAGER.TASKS;
EOF

sqlplus -S sys/welcome##1@localhost:1621/FREEPDB1 as sysdba << EOF
select username from dba_users where username = 'PROJECT_MANAGER';
select count(*) from PROJECT_MANAGER.PROJECTS;
select count(*) from PROJECT_MANAGER.EMPLOYEES;
select count(*) from PROJECT_MANAGER.PROJECT_MEMBERS;
select count(*) from PROJECT_MANAGER.TASKS;
EOF

## データ伝搬確認
### source-db更新
sqlplus -S sys/welcome##1@localhost:1521/FREEPDB1 as sysdba << EOF
select username from dba_users where username = 'PROJECT_MANAGER';
INSERT INTO PROJECT_MANAGER.EMPLOYEES (FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE) VALUES ('Alice', 'Doe', 'alice.doe@example.com', '444-333-2222', TO_DATE('2022-03-13', 'YYYY-MM-DD'));
commit;

set lines 500 pages 300 tab off
col EMAIL for a50
col TASK_NAME for a50
select EMPLOYEE_ID, EMAIL from PROJECT_MANAGER.EMPLOYEES;
EOF

### target-db確認
sqlplus -S sys/welcome##1@localhost:1621/FREEPDB1 as sysdba << EOF
set lines 500 pages 300 tab off
col EMAIL for a50
col TASK_NAME for a50
select EMPLOYEE_ID, EMAIL from PROJECT_MANAGER.EMPLOYEES;
EOF

## DDL伝搬確認
### source-db更新
sqlplus -S sys/welcome##1@localhost:1521/FREEPDB1 as sysdba << EOF
desc PROJECT_MANAGER.TASKS
alter table PROJECT_MANAGER.TASKS add (priority number(1) default 0);
desc PROJECT_MANAGER.TASKS
EOF

### target-db確認
sqlplus -S sys/welcome##1@localhost:1621/FREEPDB1 as sysdba << EOF
desc PROJECT_MANAGER.TASKS
EOF
```

また Runtime 画面を見ると以下のように同期されていることが確認できます。  

![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609343/9d713bdd-0952-a303-b298-3ece129bb89e.png)

## そのほかのバージョン同士の検証
以下のバージョン間の検証については手順上の差異を記載します。  

- 23.5 -> 23.5
- 21.3 -> 23.5

### 23.5 -> 23.5
#### compose.yaml
compose.yaml の image 部分を以下のようにご修正ください。  
あとは同じ手順で行けます。  

<details><summary>compose.yaml</summary>

```yaml
x-ulimits: &ulimits
  nofile:
    soft: 1024
    hard: 65536
  nproc:
    soft: 2047
    hard: 16384
  stack:
    soft: 10485760
    hard: 33554432
  memlock: 3221225472

x-oraenvs: &oraenvs
  - ORACLE_PWD=welcome##1
  - ENABLE_ARCHIVELOG=true
  - INIT_SGA_SIZE=4096
  - INIT_PGA_SIZE=1024
  - ORACLE_SID=FREE
  - ORACLE_PDB=FREEPDB1

services:
  source-db:
    image: container-registry.oracle.com/database/free:23.5.0.0
    container_name: source-db
    ulimits: *ulimits
    environment: *oraenvs
    volumes:
      - ./startupScript:/opt/oracle/scripts/startup
    ports:
      - "1521:1521"
  target-db:
    image: container-registry.oracle.com/database/free:23.5.0.0
    container_name: target-db
    ulimits: *ulimits
    environment: *oraenvs
    ports:
      - "1621:1521"
  ogg:
    image: container-registry.oracle.com/goldengate/goldengate-free:latest
    container_name: ogg
    ports:
      - "8080:80"
```
</details>

### 21.3 -> 23.5
#### compose.yaml
こちらも source-db の image を変更すれば、あとは同じ手順で行けます。

# まとめ
Oracle GoldenGate Free を用いて以下の Oracle DB 間でデータ同期(+DDL同期)ができることを確認できました。  

- 21.3 -> 21.3
- 23.5 -> 23.5
- 21.3 -> 23.5

検証目的で利用するなら Free は手軽に開始できるのでおすすめかと思います。  
ただ、[はじめに](#はじめに)で記載しましたが Free には制限事項が多いので本番で利用する場合には [Free ではない方](https://docs.oracle.com/en/middleware/goldengate/core/index.html)のご利用を検討が必要そうです。

# 参考
- GoldenGate Free
  - https://docs.oracle.com/en/middleware/goldengate/free/23/index.html
  - https://www.youtube.com/watch?v=45aFXF5mNEA
- GoldenGate Microservice Architecture
  - https://docs.oracle.com/en/middleware/goldengate/core/index.html