HW更改でDBサーバのIPが変わっても安心！Oracle Databaseのリスナー・リダイレクト機能を試してみた

HWのEOSL対応などでDB基盤を更改する機会というのは割とあると思います。
その際に新しい基盤で現行のIPとFQDNを使いまわせれば良いですが、何らかの事情で新しく割り当て直さなければならないケースもあります。
そこで問題となるのがアプリケーションやバッチ・スクリプトに書かれているDB接続情報を修正する作業です。
全て同じ形式で書かれていれば良いですが、FQDNを使ったりIPを使ったりなどまちまちな場合が多いのではないでしょうか。
また修正すべき対象コードを網羅的に挙げるのもかなり苦労が伴うと思われます。

そうした課題に適用できる解決策が今回試してみたリスナー・リダイレクト機能です。
こちらはOracle Database 12cR2から追加された機能で、現行のDB（PDB含む）に対する接続情報のまま、新しい更改先のDBへ接続できる機能です。
内部動作としては一旦現行DBで接続を確立した後、自動的にリダイレクトすることで最終的に新DBへ接続する流れとなります。
マニュアルの該当箇所は以下になります。
[9.6 リスナーのリダイレクトの理解](https://docs.oracle.com/cd/F19136_01/netag/configuring-and-administering-oracle-net-listener.html#GUID-63F36312-FDA8-4B20-B8E1-4949AD55707A)
設定の際は下記3つの初期化パラメータを使います。

- [LOCAL_LISTENER](https://docs.oracle.com/cd/F19136_01/refrn/LOCAL_LISTENER.html#GUID-70F5D04D-02A3-4E89-8A3F-9410B6861BC4)
- [REMOTE_LISTENER](https://docs.oracle.com/cd/F19136_01/refrn/REMOTE_LISTENER.html#GUID-FEE2E8B5-CE02-4158-A6B4-030E59316756)
- [FORWARD_LISTENER](https://docs.oracle.com/cd/F19136_01/refrn/FORWARD_LISTENER.html#GUID-85B41AA7-EC84-4F6F-9316-9C86F90FDC08)

具体的な設定方法は FORWARD_LISTENER のページに記載されています。

> FORWARD_LISTENERパラメータを使用してフォワード・リスナーを構成すると、LOCAL_LISTENERパラメータは、REMOTE_LISTENERパラメータを使用して構成された一連の既存のリモート・リスナーへのすべての接続がFORWARD_LISTENERを使用して構成されたリスナーにのみ転送されるように、値を-oracle-none-に設定することでクリアできます。

やや分かりにくいですが、それぞれの初期化パラメータを以下のように設定せよと書かれています。

- LOCAL_LISTENER：使われないように値を -oracle-none- と設定
- REMOTE_LISTENER：現行DBが利用するリスナーの接続情報を設定
- FORWARD_LISTENER：リダイレクト先リスナーの接続情報を設定

以降では実際に試した手順を述べていきたいと思います。

## 検証環境

+ リダイレクト元DB
    - OCIのComputeで作成されたDBサーバ
    - DBバージョン：19c
    - DB名：CDB1
    - PDB名：OE
    - PDB OEへの接続で利用するサービス名：oe
+ リダイレクト先DB
    - OCIのBase Database Service（BaseDB）で作成されたDBサーバ
    - DBバージョン：19c
    - DB名：newdb
    - PDB名：NEWPDB

なおOCIで試す際は事前にOCIのネットワークに対して、リダイレクト元から先に対するOracle Net通信の許可設定が必要です。

## 検証手順

### BaseDB側の事前準備

今回リダイレクト先に使うBaseDBでは初期化パラメータ DB_DOMAIN が設定されています。
一方でリダイレクト元には設定していませんでした。
リスナー・リダイレクトを使うには元と先で同じ名前のサービスが存在する必要があり、
DB_DOMAINが一方だけ設定されていると同一とみなされません。
そのため以下の通り、BaseDB上のリダイレクト先PDBにおいて DB_DOMAIN を無効化しました。

```sql
# PDBにログインして DB_DOMAIN を無効化
$ sql sys/<your password>@<new host name>:1521/newpdb.<domain name> as sysdba
SQL> ALTER SYSTEM SET DB_DOMAIN='' SCOPE=spfile;
SQL> exit
```

```bash
# リスナーとDBを再起動
$ srvctl stop listener
$ srvctl stop database -d newdb
$ srvctl start database -d newdb
$ srvctl start listener
```

次にリダイレクト先PDBにリダイレクト元と同じ名前でサービスを作成＆起動します。

```bash
$ srvctl add service -db newdb -service oe -pdb newpdb
$ srvctl start service -db newdb -service oe
```

### リダイレクト元PDBの事前準備

まずはリスナー・リダイレクトを設定する前に、普通に現行PDBへ接続してみます。

```sql
$ sql sys/<your password>@<existing host name>:1521/oe as sysdba
SQL> col CDB_NAME for a10
SQL> col PDB_NAME for a10
SQL> SELECT SYS_CONTEXT ('USERENV','CDB_NAME') as CDB_NAME,  SYS_CONTEXT ('USERENV','DB_NAME') as PDB_NAME from dual;

CDB_NAME   PDB_NAME  
---------- ----------
CDB1       OE        
```

次にリダイレクト元のPDBにて前述した3つの初期化パラメータを設定し、
リスナー・リダイレクト機能を有効化します。

```sql
SQL> alter system set forward_listener='<new host name>:1521' scope=both;
SQL> alter system set remote_listener='<existing host name>:1521' scope=both;
SQL> alter system set local_listener='-oracle-none-' scope=both;
SQL> show parameter list

NAME              TYPE   VALUE                                          
----------------- ------ ---------------------------------------------- 
forward_listener  string <new host name>:1521 
listener_networks string                                                
local_listener    string -oracle-none-                                  
remote_listener   string <existing host name>:1521              
```

上記を設定した直後にリスナーのログを見ると、以下のように「service_died」という出力を確認できます。

```bash
$ tail $ORACLE_BASE/diag/tnslsnr/<existing host name>/listener/trace/listener.log
...(略)...
10-FEB-2023 05:58:52 * service_died * CDB1 * 12537
...(略)...
```

また対象リスナーのサービス一覧を出力すると、リスナー・リダイレクト機能を設定したPDB上のサービスに関して「FORWARD SERVER」という出力を確認できます。

```bash
$ lsnrctl services LISTENER

...(略)...
Service "oe" has 1 instance(s).
  Instance "CDB1", status READY, has 1 handler(s) for this service...
    Handler(s):
      "COMMON" established:0 refused:0 state:ready
         FORWARD SERVER
         (DESCRIPTION=(CONNECT_DATA=(SERVICE_NAME=))(ADDRESS=(PROTOCOL=tcp)(HOST=10.0.0.9)(PORT=1521)))
...(略)...
```

最後にリスナー・リダイレクト機能が使えるか動作確認します。
もう一度リダイレクト元のPDBに、以前と同じ接続情報でログインを試みます。
ログインできたらDB名とPDB名を確認します。

```sql
$ sql sys/<your password>@<existing host name>:1521/oe as sysdba

SQL> col CDB_NAME for a10
SQL> col PDB_NAME for a10
SQL> SELECT SYS_CONTEXT ('USERENV','CDB_NAME') as CDB_NAME,  SYS_CONTEXT ('USERENV','DB_NAME') as PDB_NAME from dual;

CDB_NAME   PDB_NAME  
---------- ----------
newdb      NEWPDB    
```

上記のようにBaseDB側のDB名とPDB名が出力され、BaseDB上のPDBへリダイレクトされたことが分かります。
またリダイレクト元と先の両方のリスナー・ログに、ほぼ同時に接続確立されたことが出力されています。

```bash
# リダイレクト元
$ tail $ORACLE_BASE/diag/tnslsnr/<existing host name>/listener/trace/listener.log
...(略)...
10-FEB-2023 14:52:10 * (CONNECT_DATA=(SERVICE_NAME=oe)(CID=(PROGRAM=java)(HOST=<existing host name>)(USER=oracle))) * (ADDRESS=(PROTOCOL=tcp)(HOST=127.0.0.1)(PORT=31254)) * establish * oe * 0
...(略)...

# リダイレクト先
$ tail $ORACLE_BASE/diag/tnslsnr/<new host name>/listener/trace/listener.log
...(略)...
10-FEB-2023 14:52:10 * (CONNECT_DATA=(SERVICE_NAME=oe)(CID=(PROGRAM=java)(HOST=<existing host name>)(USER=oracle))) * (ADDRESS=(PROTOCOL=tcp)(HOST=10.0.0.103)(PORT=53490)) * establish * oe * 0
...(略)...
```

ちなみに上記ログの「HOST=」の部分ですが、元のログではループバックアドレスが、先のログでは現行サーバのプライベートIPが表記されています。

## おわりに

今回は検証結果をもとに、リスナー・リダイレクト機能の簡単な使い方をまとめました。
接続先IP変更を伴うOracle Database基盤更改において、事故を防ぐ一助になりましたら幸いです。
ただ最終的には現行サーバは除却などすると思いますので、漏れなく新基盤に接続情報を修正していく対応は必要な気はします。
そのため例えば現行のリスナー・ログを監視して、新規接続を確認したら元をたどって修正する、といった作業を地道にやっていくのかな、と想像しています。

ちなみにリダイレクト先がRAC環境でSCANリスナーの場合は、[冒頭のマニュアル](https://docs.oracle.com/cd/F19136_01/netag/configuring-and-administering-oracle-net-listener.html#GUID-63F36312-FDA8-4B20-B8E1-4949AD55707A)に書かれている通り「ALLOW_MULTIPLE_REDIRECTS_listener=on」を listener.ora に追記する必要がありますのでご留意ください。

> ローカル・リスナーがOracle RAC構成内のSCANリスナーにリダイレクトした場合、このリスナーは、クライアントの接続要求をさらに別のクラスタ・ノードにリダイレクトする必要がある場合があります。このような複数リダイレクトは、Oracle Netリスナーではデフォルトでサポートされません。SCANリスナーがすでにリダイレクトされたクライアント接続要求を転送できるようにするには、ALLOW_MULTIPLE_REDIRECTS_listener_nameパラメータをそのlistener.oraファイルに追加します。