[Oracle Cloud] リフレッシュ可能PDBを使って、SE2でデータ同期

# はじめに
Oracle Cloudで**Standard Edition**を利用した災対環境の構築方法について考えた。ついでに、災対側のデータベースは、日中帯に読み書き可能な状態にしたい。

当初、基本スタンバイを応用した方式を考えたが、一時的に読み書き可能にしようとすると、(Flashback Database使えないし)実装が複雑になってしまうようなぁ、と思っていた。が、19cを前提に考えると、SE2でも3つまでPDBを作成可能だし、リフレッシュ可能PDBを利用することでスマートに実装できるのではないかと思いいたる。

なお、[ライセンスガイド](https://docs.oracle.com/cd/F19136_01/dblic/Licensing-Information.html#GUID-0F9EB85D-4610-4EDF-89C2-4916A0E7AC87)を見る限り、Cloudであれば「リフレッシュ可能PDBスイッチオーバー」も利用できるようだ。ただのリフレッシュ可能PDBは[これ](https://www.doag.org/formes/pubfiles/9483257/2017-DB-Thomas_Petrik-SE2_goes_Enterprise___verborgene_Schaetze_in_Oracle_12_2-Manuskript.pdf)を信用する限り、SE2で普通に使える機能らしい。

※ DR用途として、リフレッシュ可能PDBを使おうと思った場合、最大の難点はソースPDBにアクセスできないとRead / Writeモードに変更できないこと。(正確には、refresh modeをnoneにするときに、ソースPDBにアクセスできないとエラーになって停止できない。ただし、read onlyでの起動はできる。)19cであれば、SEでも3PDBまで作成できるため、ソースがアクセス不能になってもCLONEによってread/writeなPDBを作成できる

※ 2020/8/22追記：refresh中にソースDBに障害が発生した場合、更新中のテーブルが破損してしまう状況を確認。(障害復旧後に、再度refreshすれば破損は解消された) そのため、DR用途に使う場合、refresh PDBを複数用意して、シリアライズにマニュアルするようにした方がよさそう。

試した構成は、以下のような感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456818/5361b205-cc55-94db-4f6f-364c9dfd83ca.png)

まず、プライマリとスタンバイそれぞれでCDBを作成し、スタンバイ側にリフレッシュ可能PDBを作成する。
① 以後、定期的にプライマリからリフレッシュ操作を行う。これは、n分毎のように自動で行うようにも設定できるし、手動で行うこともできる。(今回試したのは手動

② 読み書き可能なPDBを作成するため、リフレッシュ可能PDBをクローンする。19cから、SE2でもPDBを3つまで作成できるようになったため、これができるようになった。

なお、わざわざクローンするのは、リフレッシュ可能PDBを読み書き可能な状態で起動しようとすると、リフレッシュモードを解除する必要があり、一度解除してしまうと以降リフレッシュできなくなってしまうため。PDBのスナップクローンという手もあるかもしれないが、デフォルトでデータファイルが作成される領域がASMなのでエラーになる。(DBCSでサポートされるかは置いておいて) データファイルをACFSに置けばできるかもしれない (試してない

# 前提環境
前提環境を作る手順は省略します。以下のような感じで作りました。

東京リージョンに、以下のDBCSインスタンス(VM)を作ります
　- エディション: Standard Edition
　- ストレージ管理ソフトウェア: Oracle Grid Infrastructure
　- DBバージョン：19.0.0.0

　ここでは、CDB名をSE01P、PDB名をPDB01としています。

同じように大阪リージョンに、以下のDBCSインスタンス(VM)を作ります
　- エディション: Standard Edition
　- ストレージ管理ソフトウェア: Oracle Grid Infrastructure
　- DBバージョン：19.0.0.0

　ここでは、CDB名をSE01S、PDB名をPDB01としています。
　※スタンバイ側は、既定で作成されるPDBは後で消してしまいますが。

東京と大阪は、リモートピアリングを使用して互いに通信できるようにしておきます。(ここでは、[私の過去記事](https://qiita.com/NSO-KC/items/3c84213a06b50b15d84b)を参考にセットアップしました。ただ、Hub&Spoke構成にする必要はないです。)
 - セキュリティ・リスト/NSGにより、プライマリ・スタンバイ間の通信はすべて許可した状態で試しています。


## 手順
### 参考にした手順

- [マニュアル(Administrator’s Guide)](https://docs.oracle.com/en/database/oracle/oracle-database/19/multi/administering-pdbs-with-sql-plus.html#GUID-B505C234-FAF4-4BAB-8B59-59276E0EA128)
- [Moving Your PDBs Between Oracle DBCS Instances](https://www.linkedin.com/pulse/moving-your-pdbs-between-oracle-dbcs-instances-kurt-liu/)

OCIの場合、SEでも表領域暗号化されるため、TDEのキーをexport/importしてあげる必要がありました。

### 1. パッチ適用
リフレッシュ可能PDB作成時に、以下のエラーが出力されて失敗するという事象に遭遇しました。MOS Doc #2643106.1に記載されているパッチを適用することで回避できました。詳細は同Docを参照してください。(RU 19.7.0.0.200414で遭遇)

```
*
ERROR at line 1:
ORA-19505: failed to identify file
"+DATA/xxxxx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/DATAFILE/users.275.1045393305"
ORA-15173: entry 'SE01P_NRT1C4' does not exist in directory '/'
```

### 2. ソースDB (プライマリ側) の準備
#### 2-1. 共通ユーザの作成 - ソース (プライマリ) DB側の作業

ソースDBに共通ユーザを作ります。(ターゲットDBからDBリンクで接続するユーザです)

```SQL
$ sudo su - oracle
$ sqlplus / as sysdba
SQL> CREATE USER C##REFRESH_ADMIN IDENTIFIED BY xxxxxx;
SQL> GRANT CREATE SESSION, RESOURCE, CREATE ANY TABLE, UNLIMITED TABLESPACE TO c##REFRESH_ADMIN CONTAINER=ALL;
SQL> GRANT CREATE PLUGGABLE DATABASE TO c##REFRESH_ADMIN CONTAINER=ALL;
SQL> GRANT SYSOPER TO c##REFRESH_ADMIN CONTAINER=ALL;
SQL> exit
```

#### 2-2. サービスの作成 - ソース (プライマリ) DB側の作業
デフォルトでは、DBユニーク名がついたサービスが作成されますが、識別しづらい(＊)のでサービスを作っています。(やらなくてもいいです。)

(＊) コンソールからDBシステム作成されると、サフィックスにランダム文字列がついた名前がDBユニーク名として設定されるため

```SQL
個人的に、サービス名にドメイン名がつくと煩わしいのでdb_domainをnullに変更
SQL> alter system set db_domain='' scope=spfile;
SQL> alter system set global_names=false;
↑ global_namesもDB Link設定する際に面倒なのでfalseにします

SQL> shutdown immediate;
SQL> startup;

$ srvctl add service -service SE01P -db <DBユニーク名>
$ srvctl start service -service SE01P -db <DBユニーク名>

$ cd $ORACLE_HOME/network/admin
$ vi tnsnames.ora
→ デフォルトで作成されるtnsnames.oraのエントリはドメイン名つきなので修正する

```

#### 2-3. マスターキーのexport
暗号化された表領域を持つPDBをクローンするためには、TDEマスター暗号化キーをexport/importしてあげる必要があります。

```SQL
$ sqlpus / as sysdba

## 自動ログイン・キーストアを閉じる (AUTO LOGIN状態ではexportできないため)
SQL> administer key management set keystore close;

## パスワード・ベースで再度キーストアをオープンします。
SQL> administer key management set keystore open identified by xxxxx container=all;
※xxxxxは、DBCS VM作成時に入力したsysパスワード

## セッションをPDB01に移動します。
SQL> alter session set container=PDB01;

## 暗号化キーをexportします。
SQL> ADMINISTER KEY MANAGEMENT EXPORT ENCRYPTION KEYS WITH SECRET "yyyyyy" TO '/tmp/tde_key_file' IDENTIFIED BY xxxxx ;

※yyyyyyは、/tmp/tde_key_fileに設定するパスワード

## パスワード・べースでオープンしたキーストアを閉じます。
SQL> conn / as sysdba
SQL> administer key management set keystore close identified by xxxxx container=all;

SQL> exit;

/tmp/tde_key_fileをスタンバイの/tmpに転送する。

## 再度、自動ログインでキーストアをオープンするため、再起動します
$ srvctl stop database -db <DBユニーク名>
$ srvctl start database -db <DBユニーク名>

```

### 3. リフレッシュ可能PDBの作成 - ターゲット(スタンバイ)DB側の作業
#### 3-1. デフォルトPDBの削除

DBCSインスタンスを作成時にデフォルトで作成されたPDBをまず削除します。

```SQL
$ sqlplus / as sysdba
SQL> show pdbs
    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB01                          READ WRITE NO → これを削除する

SQL> alter pluggable database pdb01 close;
SQL> drop pluggable database pdb01 including datafiles;
SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO

SQL> exit
```

#### 3-2. サービスの作成
プライマリと同じ用にサービスを作成 (やらなくてもいいです)

```SQL
SQL> alter system set db_domain='' scope=spfile;
SQL> alter system set global_names=false;
↑ global_namesも面倒なのでfalseにします

SQL> shutdown immediate;
SQL> startup;

$ srvctl add service -service SE01S -db <DBユニーク名>
$ srvctl start service -service SE01S -db <DBユニーク名>

```

#### 3-3. TDE Keyのimport
プライマリからexportしたTDEキーをスタンバイにimportします。

```SQL
## 転送した、暗号化キーのパーミッションを変更
$ sudo chmod 600 /tmp/tde_key_file
$ sudo chown oracle.oinstall /tmp/tde_key_file

$ sudo su - oracle
$ sqlplus / as sysdba
## 自動ログイン・キーストアを閉じる (AUTO LOGIN状態ではimport できないため)
SQL> administer key management set keystore close;

## パスワード・ベースで再度キーストアをオープンします。
SQL> administer key management set keystore open identified by xxxxx container=all;
※xxxxxは、DBCS VM作成時に入力したsysパスワード

## 暗号化キーをimportします。
SQL> administer key management import encryption keys with secret yyyyyy from '/tmp/tde_key_file' identified by xxxxxx with backup;
※yyyyyyは、import時に/tmp/tde_key_fileに設定したパスワード

## パスワード・べースでオープンしたキーストアを閉じます。
SQL> administer key management set keystore close identified by xxxxx container=all;

SQL> exit;

## 再度、自動ログインでキーストアをオープンするため、再起動します
$ srvctl stop database -db <DBユニーク名>
$ srvctl start database -db <DBユニーク名>

```

#### 3-4. DBリンクの作成
「1. 共通ユーザの作成」の手順で作成したユーザでログインするDBリンクを作成します。

```SQL
$ cd $ORACLE_HOME/network/admin

$ vi tnsnames.ora
SE01P =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = xx.xx.xx.xx)(PORT = 1521))
    (CONNECT_DATA =
      (SERVER = DEDICATED)
      (SERVICE_NAME = SE01P)
    )
  )
→ Primaryに接続するためのエントリを書きます

$ tnsping se01p
…
OK (20 msec)

$ sqlplus C##REFRESH_ADMIN/xxxxxx@SE01P
→ ログインできること
SQL> exit

$ sqlplus / as sysdba
SQL> CREATE DATABASE LINK DBL_PDB_REFRESH CONNECT TO c##REFRESH_ADMIN IDENTIFIED BY xxxxx USING 'SE01P';

SQL>  select * from tab@DBL_PDB_REFRESH;
no rows selected
→ エラーにならないこと

SQL> exit
```

#### 3-5. リフレッシュ可能PDBの作成
ここでは、PDB01_REFRESHABLE という名前のリフレッシュ可能PDBを作成します。

```SQL
$ sqlplus / as sysdba

SQL> create pluggable database PDB01_REFRESHABLE from PDB01@DBL_PDB_REFRESH REFRESH MODE MANUAL keystore identified by xxxxxx;
※ xxxxxxは、DBCS VM作成時に入力したsysパスワードと同一

Pluggable database created.

## 作成後の状態は、MOUNTEDです。
SQL> show pdbs
    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB01_REFRESHABLE              MOUNTED ★

## 確認のため、read only で起動します。
SQL> alter pluggable database PDB01_REFRESHABLE open read only;

SQL> show pdbs
    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB01_REFRESHABLE              READ ONLY  NO ★

## 再度閉じます。
SQL> alter pluggable database PDB01_REFRESHABLE close;

```

### 4. リフレッシュ確認

#### 4-1. ソースDB(プライマリ側)でデータ投入

ソースDBにユーザを作成して、ダミーデータを投入します。

```SQL
$ sqlplus / as sysdba
SQL> alter session set container=pdb01;
SQL> create user pdb_user identified by xxxxx default tablespace users quota unlimited on users;
SQL> grant create session, create table to pdb_user;

SQL> exit
$ sqlplus pdb_user/xxxxx@$(hostname -f)/pdb01
SQL> show user
USER is "PDB_USER"

SQL> create table test (col1 number, col2 date, col3 char(100));
SQL> insert into test select level,sysdate,'TEST'||level from dual connect by level <= 100000;
SQL> commit;
SQL> select count(*) from test;
  COUNT(*)
----------
    100000

SQL> exit
```

#### 4-2.ターゲットDB(スタンバイ側)でリフレッシュ

```SQL
$ sqlplus / as sysdba
SQL> alter pluggable database PDB01_REFRESHABLE refresh;
SQL> alter pluggable database PDB01_REFRESHABLE open read only;
SQL> exit

## プライマリ側で作成したユーザを利用してログインし、データが存在することを確認します。
$ sqlplus pdb_user/xxxxx@$(hostname -f)/pdb01_refreshable

SQL> select count(*) from test;
  COUNT(*)
----------
    100000

## 確認後、再度PDBをクローズします。
SQL> conn / as sysdba
SQL> alter pluggable database PDB01_REFRESHABLE close;

```

### 5. リフレッシュ可能PDBから、クローンを作成 - ターゲットDB(スタンバイ)側の作業
スタンバイ側で、読み書き可能なPDBを作成するため、リフレッシュ可能PDBからクローンを作成します。

```SQL
$ sqlplus / as sysdba

## クローンを作るために、リフレッシュ可能PDBをread onlyで起動する必要があります。
SQL> alter pluggable database PDB01_REFRESHABLE open read only;

## クローンを作成します
SQL> create pluggable database PDB01 from PDB01_REFRESHABLE keystore identified by xxxxxx;

## 作成直後はクローズしています。
SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB01_REFRESHABLE              READ ONLY  NO
         5 PDB01                          MOUNTED ★

## 読み書き可能状態で起動します。
SQL> alter pluggable database pdb01 open;
    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB01_REFRESHABLE              READ ONLY  NO
         5 PDB01                          READ WRITE NO ★

SQL> alter pluggable database PDB01_REFRESHABLE close;

```

とういことで、SE2の基本機能だけで、いい感じに災対環境を作れそうな予感がしました。