[Oracle Cloud] 透過的アプリケーション・フェイルオーバー (TAF) を Data Guard 構成でやってみた

# はじめに

[前回の記事](https://qiita.com/sugimount/items/217498a8d79563c58148) で、Oracle Cloud Infrastructure (以下OCI) 上で、Oracle DB の Data Guard の自動フェールオーバー (Fast-Start Failover) を構成しました。
データベース側の自動的なフェールオーバーが実現できましたが、これと同時にアクセス元の Oracle Client 側でフェールオーバーの動きに追従する仕組みが必要になります。

いくつかの選択肢がありますが、透過的アプリケーション・フェイルオーバー (TAF：Transparent Application Failover) 機能を使うことで、追従することが出来ます。アプリケーション側から見ると、Oracle DB の Primary に障害が発生した際に、数分 ~ 数十分のアクセス断を待機することで、自動的に接続が復活します。
注意点としては、アプリケーション側でアクセス断に対するリトライ制御が必要になります。
アクセス断を完全に無くしたい場合は、紹介する構成とは違う構成になりますが、以下の方法があります。

- Oracle DB の 構成 : RAC or Active Data Guard を利用
- アプリケーションの構成 : Application Continuity を利用



それでは、透過的アプリケーション・フェイルオーバーの構成方法を紹介します。



# 構成図

![1567323605328.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/91e8ae17-ce19-5663-0f43-ea682e9ab051.png)



# 透過的アプリケーション・フェイルオーバー(TAF)とは

引用Document
https://docs.oracle.com/cd/E96517_01/adfns/high-availability.html#GUID-AF2A0A43-6B38-40DD-8954-55BF02BB9703

> 透過的アプリケーション・フェイルオーバー(TAF)はOCI、OCCI、Java Database Connectivity (JDBC) OCIのドライバおよびODP.NETのクライアント側機能であり、インスタンスまたはネットワークの障害によりデータベース接続に失敗したときに発生するエンドユーザー・アプリケーションに対する障害を最小限にするために設計されています。TAFは、Oracle Real Application Clusters (Oracle RAC)やOracle Data Guardの物理スタンバイ・データベースなどの様々なシステム構成でも、起動後の単一インスタンス・システムでも実装できます(たとえば、修復を行うとき)。
>
> TAFにより、クライアント・アプリケーションは、事前に構成した2次インスタンスに透過的に再接続し、最初の元のインスタンスで確立したのと新たに同じ接続を自動的に作成します。つまり、接続が失われた状態にかかわらず、接続プロパティは直前の接続のプロパティと同じです。この場合、アクティブ・トランザクションはロールバックされます。また、試行の失敗後にアプリケーションが使用しようとしたすべての文もフェイルオーバーします。



# DBaaS Data Guard インスタンスの設定

TAF を利用するためには、Data Guard を構成している全てのインスタンスで、同一の ServiceName を設定する必要があります。
ServiceName は、 Oracle Client がアクセスする際に必要になる識別子になります。同一の ServiceName を設定することで、フェールオーバーをしても、同一の ServiceName を使用してアクセスすることが可能になります。



## Service の設定(1台目)

DBaaS 1台目にSSHして、oracle ユーザーへスイッチ

```
[opc@client-6032 ~]$ ssh opc@sugihostname01.priv01.vcn.oraclevcn.com 
Last login: Fri Aug 30 14:11:55 2019 from 10.2.0.32 
[opc@sugihostname01 ~]$ sudo su - oracle 
[oracle@sugihostname01 ~]$  
```



SQL*Plus の起動

```
[oracle@sugihostname01 ~]$ sqlplus / as sysdba 

SQL*Plus: Release 18.0.0.0.0 - Production on Fri Aug 30 14:13:08 2019
Version 18.6.0.0.0

Copyright (c) 1982, 2018, Oracle.  All rights reserved.


Connected to:
Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Production
Version 18.6.0.0.0

SQL> 
```



PDB の名前を確認 `SGPBD01`

```
SQL> select name, open_mode from v$pdbs; 

NAME
--------------------------------------------------------------------------------
OPEN_MODE
----------
PDB$SEED
READ ONLY

SGPBD01
READ WRITE


SQL> 
```



`srvctl add service` コマンドで、新たな Service を作成します

- -db : Database Unique Name を指定 (OCIコンソール上から確認可能)
- -pdb : PDB の名前を指定 (PDBが存在しない場合は指定しない)
- -service : 新しく作成するサービス名を指定

```
srvctl add service -db suginm01_nrt1mv -pdb SGPBD01 -service taf_service
```



実行例
何も output されない

```
[oracle@sugihostname01 ~]$ srvctl add service -db suginm01_nrt1mv -pdb SGPBD01 -service taf_service 
[oracle@sugihostname01 ~]$  
```



status の確認
`not running` 状態となっている

```
[oracle@sugihostname01 ~]$ srvctl status service -db suginm01_nrt1mv -service taf_service   
Service taf_service is not running. 
```



起動

```
srvctl start service -db suginm01_nrt1mv -service taf_service 
```



実行例
何も output されない

```
[oracle@sugihostname01 ~]$ srvctl start service -db suginm01_nrt1mv -service taf_service  
[oracle@sugihostname01 ~]$  
```



status の確認
`running` 状態

```
[oracle@sugihostname01 ~]$ srvctl status service -db suginm01_nrt1mv -service taf_service 
Service taf_service is running on instance(s) suginm01 
```



`lsnrctl status` コマンドを grep して、新たに作成した service がリスナーに含まれているか確認
`taf_service.priv01.vcn.oraclevcn.com` を tnsnames.ora の SERVICE_NAME に指定可能となります。

```
[oracle@sugihostname01 ~]$ lsnrctl status | grep -i "taf" -A1 
Service "taf_service.priv01.vcn.oraclevcn.com" has 1 instance(s). 
  Instance "suginm01", status READY, has 2 handler(s) for this service...
[oracle@sugihostname01 ~]$
```



## Service の設定(2台目)

Data Guard 2台目のインスタンスでも、同様の設定を行う必要があります。
Data Guard の Standby ロールのままでも操作が可能です

2台目のインスタンスへ SSH ログインして、oracle ユーザーへスイッチをします

```
[opc@client-6032 ~]$ ssh opc@sugihostname02.priv01.vcn.oraclevcn.com 
Last login: Sat Aug 31 13:49:03 2019 from 10.2.0.32 
[opc@sugihostname02 ~]$ sudo su - oracle 
[oracle@sugihostname02 ~]$  
```



`srvctl add service` コマンドで、新たな Service を作成します

- -db : Database Unique Name を指定 (OCIコンソール上から確認可能)
- -pdb : PDB の名前を指定 (PDBが存在しない場合は指定しない)
- -service : 新しく付与したいサービス名を指定

```
srvctl add service -db suginm01_nrt1fr -pdb SGPBD01 -service taf_service
```



status の確認

```
[oracle@sugihostname02 ~]$ srvctl status service -db suginm01_nrt1fr -service taf_service   
Service taf_service is not running. 
[oracle@sugihostname02 ~]$  
```



Standby Role なので、起動(start) はエラーになるため、start は行いません。フェールオーバーで Primary になったときに、自動的にstartされます



## PDB に ユーザーの作成

アプリケーションから接続するために、PDB にユーザを作成します。
Data Guard 1台目(Primary)の oracle ユーザーにSSHログインします

```
[opc@client-6032 ~]$ ssh opc@sugihostname01.pub01.vcn.oraclevcn.com
Last login: Sun Sep  1 04:03:12 2019 from 10.2.0.32 
[opc@sugihostname01 ~]$  
[opc@sugihostname01 ~]$ sudo su - oracle 
```



SQL PLUS を起動して、CDB へ接続します

```
sqlplus / as sysdba
```



接続先が CDB か確認します

```
SQL> show con_name;  

CON_NAME
------------------------------
CDB$ROOT
```



PDB の一覧を確認します

```
SQL> show pdbs; 

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 SGPBD01                        READ WRITE NO
```



PDB (SGPBD01) へ接続します

```
alter session set container=SGPBD01;
```



以下コマンドを実行して、ユーザーの作成・権限の付与・表領域の上限解除を行います

```
CREATE USER sugi IDENTIFIED BY Sug1_Passw0rd_dayo;
GRANT CONNECT, RESOURCE TO sugi;
ALTER USER sugi quota unlimited on USERS;
```



# Client 側の設定

Client から Oracle DB への接続確認を行うために、Java アプリケーションの配置を含めた、様々な設定をします。



## インスタンス作成

CentOS 7 で適当に作成します

 

## Oracle JDK Install

https://qiita.com/sugimount/items/33c65e2f648ea46c7239



## Firewalld 停止

OCIの仮想ネットワークの機能で、以下のFirewall の機能があるため、インスタンス上の Firewalld を停止します

- Security List (サブネット単位の設定)
- Network Security Group (NIC単位の設定)

```
sudo systemctl stop firewalld
sudo systemctl disable firewalld
```



## Oracle Call Interface の使用

Javaアプリケーションから Oracle DB に接続する際には、JDBC と呼ばれるAPIを経由してデータの入出力を行います。
JDBC には大きく分けて2個のパターンがあります。

- Oracle Call Interface (OCI) を使用して接続 : Oracle JDBC Driver と Oracle Instant Client を導入する必要がある
- Thin Driver を使用して接続 : Oracle JDBC Driver の導入だけでOK

基本的な使い分けは、Oracle Interface Driver の固有の機能(TAFなど)を使う必要がなければ、Thin Driverを使用します。
今回は TAF を使用したいため、Oracle Call Interface を使用する方法で接続を行います



### Oracle JDBC Driver を Install

OTN(Oracle Technology Network) から、Oracle JDBC Driver の Jar ファイルを ダウンロードします。以下のページから、ダウンロードしたいバージョンを選択します。基本的には最新で問題ないと思います。
https://www.oracle.com/technetwork/database/application-development/jdbc/downloads/index.html



tar.gz ファイルを解凍します

```
cd /home/opc
tar xfvz ojdbc10-full.tar.gz
```



今回の例では、以下のディレクトリに解凍をしています。

```
[opc@client ~]$ ls -la /home/opc/ojdbc10-full  
total 8668 
drwxr-xr-x. 2 opc opc     211 Apr 29 22:15 .
drwx------. 6 opc opc     171 Aug 29 15:35 ..
-rw-r--r--. 1 opc opc    2625 Apr 29 22:15 README.txt
-r-xr-xr-x. 1 opc opc   11596 Apr 24 21:07 ojdbc.policy
-r--r--r--. 1 opc opc 4243140 Apr 24 21:07 ojdbc10.jar
-r--r--r--. 1 opc opc  144681 Apr 24 21:07 ons.jar
-r--r--r--. 1 opc opc  306004 Apr 24 21:07 oraclepki.jar
-r--r--r--. 1 opc opc 1661488 Apr 24 21:07 orai18n.jar
-r--r--r--. 1 opc opc  205154 Apr 24 21:07 osdt_cert.jar
-r--r--r--. 1 opc opc  306854 Apr 24 21:07 osdt_core.jar
-r-xr-xr-x. 1 opc opc   29205 Apr 24 21:07 simplefan.jar
-r--r--r--. 1 opc opc 1680074 Apr 24 21:07 ucp.jar
-r--r--r--. 1 opc opc  262664 Apr 24 21:07 xdb.jar
```



### Oracle Instant Client の Install

Oracle Instant Client は、[こちら](https://www.oracle.com/database/technologies/instant-client/downloads.html) のページで公開されています。今回は、CentOS 7 なので、`Instant Client for Linux x86-64` からインストールを行います。

![1567093338552.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/7b6759a4-0a44-cd52-5a3e-73a1c44a8cc4.png)



Basic Package を Install します

![1567093442799.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104247/697ab55d-670d-8707-c581-060ade656786.png)



wget コマンドで、作成した CentOS 7 上に rpm ファイルをダウンロードします

```
wget https://download.oracle.com/otn_software/linux/instantclient/193000/oracle-instantclient19.3-basic-19.3.0.0.0-1.x86_64.rpm
```



yum localinstall で、ダウンロードしてきた rpm ファイルから install を行います

```
sudo yum -y localinstall oracle-instantclient19.3-basic-19.3.0.0.0-1.x86_64.rpm
```



依存関係は特になにもありません

```
Dependencies Resolved

===========================================================================================================================================================
Package                                   Arch              Version                  Repository                                                      Size 
===========================================================================================================================================================
Installing:
 oracle-instantclient19.3-basic            x86_64            19.3.0.0.0-1             /oracle-instantclient19.3-basic-19.3.0.0.0-1.x86_64            225 M 

Transaction Summary
===========================================================================================================================================================
```



環境変数の設定

```
export LD_LIBRARY_PATH=/usr/lib/oracle/19.3/client64/lib
echo 'export LD_LIBRARY_PATH=/usr/lib/oracle/19.3/client64/lib' >> ~/.bashrc
```



参考 Document [2.2 ZIPファイルから、RPMから、およびOracle Universal Installerからのインストール]
https://docs.oracle.com/cd/E96517_01/lnoci/instant-client.html#GUID-7D65474A-8790-4E81-B535-409010791C2F



## tnsnames.ora の作成

Oracle Client から、アクセスする先の Oracle DB の情報を定義するために、tnsnames.ora を作成します。

ポイントは以下の通りです

- Data Guard で構成を組んでいる、2台分の Oracle DB インスタンスの情報を記載
- SERVICE_NAME に、新規作成した Service 名 `taf_service.priv01.vcn.oraclevcn.com` を指定
- TIMEOUT を指定することで、1台目のサーバーに障害が発生している際でも、2台目への接続時間を短くする

```
cat <<'EOF' > /usr/lib/oracle/19.3/client64/lib/network/admin/tnsnames.ora
taf_service=
(DESCRIPTION=
  (LOAD_BALANCE=off)
  (FAILOVER=on)
  (CONNECT_TIMEOUT=1000 ms)
  (TRANSPORT_CONNECT_TIMEOUT=700 ms)
  (RETRY_COUNT=3)
  (ADDRESS=
    (PROTOCOL=tcp)
    (HOST=sugihostname01.priv01.vcn.oraclevcn.com)  
    (PORT=1521)
  ) 
  (ADDRESS=
    (PROTOCOL=tcp)  
    (HOST=sugihostname02.priv01.vcn.oraclevcn.com)
    (PORT=1521)
  ) 
  (CONNECT_DATA=
    (SERVICE_NAME=taf_service.priv01.vcn.oraclevcn.com) 
    (FAILOVER_MODE=
      (TYPE=select) 
      (METHOD=basic)
    )
  )
)
EOF
```



# DBaaS にサンプルテーブルを作成

Visual Studio Code や SQL*PLUS から PDB に接続して、サンプルテーブルとデータを入力します

```
-- Create a new Relational table with 3 columns

CREATE TABLE SAMPLE_TABLE 
(
  id VARCHAR2(255) NOT NULL,
  name VARCHAR2(1024)
);

-- Insert rows in a Table

INSERT INTO SAMPLE_TABLE 
VALUES
(
  '001',
  'suzuki'
);

-- Insert rows in a Table

INSERT INTO SAMPLE_TABLE 
VALUES
(
  '002',
  'satou'
);

-- Insert rows in a Table

INSERT INTO SAMPLE_TABLE 
VALUES
(
  '003',
  'tanaka'
);
```



# Java アプリケーション

Client 側に Java アプリケーションを配置します

```
vim /home/opc/java/SampleRun.java
```



ソースコード (サンプルなので、雑なアルゴリズムになっています・・・)

```java
import java.sql.*;
import java.util.Calendar;
import java.util.TimeZone;
import java.lang.Thread;

public class SampleRun {
    public static void main(String[] args) {
        final String path = "jdbc:oracle:oci8:@taf_service";
        final String id = "sugi";  //ID
        final String pw = "Sug1_Passw0rd_dayo";  //password

        try (
            Connection conn = DriverManager.getConnection(path, id, pw);
            Statement  stmt = conn.createStatement();
        ) {
            for (int i = 0; i < 100000; i++) {
                ResultSet  rs   = stmt.executeQuery("SELECT NAME FROM SAMPLE_TABLE");

                Calendar cal1 = Calendar.getInstance();
                cal1.setTimeZone(TimeZone.getTimeZone("JST"));
                System.out.println("JST : " + cal1.get(Calendar.YEAR) + "/" 
                    + (cal1.get(Calendar.MONTH) + 1) + "/"
                    + cal1.get(Calendar.DAY_OF_MONTH) + " "
                    + cal1.get(Calendar.HOUR_OF_DAY) + ":" 
                    + cal1.get(Calendar.MINUTE) + ":" 
                    + cal1.get(Calendar.SECOND));

                while (rs.next()) {
                    String name = rs.getString("name");
                    System.out.print(name + ",");
                }
                System.out.print("\n----------------------------\n");

                try {
                    Thread.sleep(1000); // wait 1sec
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch(SQLException ex) {
            ex.printStackTrace();  //Error
        }
    }
}
```



コンパイル

```
javac /home/opc/java/SampleRun.java
```



実行

```
cd /home/opc/java
java -classpath /home/opc/ojdbc10-full/ojdbc10.jar:\
/home/opc/ojdbc10-full/ucp.jar:\
/home/opc/ojdbc10-full/oraclepki.jar:\
/home/opc/ojdbc10-full/osdt_core.jar:\
/home/opc/ojdbc10-full/osdt_cert.jar:. \
SampleRun
```



実行例

```
[opc@client-6032 java]$ java -classpath /home/opc/ojdbc10-full/ojdbc10.jar:/home/opc/ojdbc10-full/ucp.jar:/home/opc/ojdbc10-full/oraclepki.jar:/home/opc/ojdbc10-full/osdt_core.jar:/home/opc/ojdbc10-full/osdt_cert.jar:. SampleRun
JST : 2019/8/31　23:54:18
suzuki,satou,tanaka,
----------------------------
JST : 2019/8/31　23:54:19
suzuki,satou,tanaka,
----------------------------
JST : 2019/8/31　23:54:20
suzuki,satou,tanaka,
----------------------------
^C[opc@client-6032 java]$ 
```



# テスト

## フェールオーバー (1台目の PMON プロセスをkill)

Primary Role のインスタンスで疑似障害を発生させるために、PMON プロセスを kill します。
PMONプロセスの、PIDの確認

```
[opc@sugihostname01 ~]$ sudo ps -aux | grep ora_pmon  
Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ 
oracle   16233  0.0  0.2 16466872 62932 ?      Ss   05:33   0:00 ora_pmon_suginm01 
opc      32058  0.0  0.0 103360  1596 pts/0    S+   05:36   0:00 grep ora_pmon 
```



PMON の kill を行うことで、Broker により自動フェールオーバー(FSFO) が発生します

```
sudo kill -9 16233
```



実行例

- start : `JST : 2019/9/1　14:36:32`
- end : `JST : 2019/9/1　14:37:28`

```
[opc@client-6032 java]$ java -classpath /home/opc/ojdbc10-full/ojdbc10.jar:/home/opc/ojdbc10-full/ucp.jar:/home/opc/ojdbc10-full/oraclepki.jar:/home/opc/ojdbc10-full/osdt_core.jar:/home/opc/ojdbc10-full/osdt_cert.jar:. SampleRun
JST : 2019/9/1　14:36:30
suzuki,satou,tanaka,
----------------------------
JST : 2019/9/1　14:36:31
suzuki,satou,tanaka,
----------------------------
JST : 2019/9/1　14:36:32
suzuki,satou,tanaka,
----------------------------
ORA-03113: end-of-file on communication channel
Process ID: 33127
Session ID: 338 Serial number: 12940

ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

=========== 略 ===========

ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

JST : 2019/9/1　14:37:28
suzuki,satou,tanaka,
----------------------------
JST : 2019/9/1　14:37:29
suzuki,satou,tanaka,
----------------------------
JST : 2019/9/1　14:37:30
suzuki,satou,tanaka,
----------------------------
JST : 2019/9/1　14:37:31
suzuki,satou,tanaka,
----------------------------
```



疑似障害を起こした直後に、アプリケーションからアクセスが出来なくなりましたが、
今回の場合では1分ほど待つことで、フェールオーバー先のOracle DB へ接続することが確認できました。

フェールオーバー時間は、Oracle DB の更新量によって大きく左右されるため、参考レベルとなります。



## わかったこと

`tnsnames.ora` を以下のように構成しているときに、1台目の `sugihostname01.priv01.vcn.oraclevcn.com` が死んでいるときに、初回接続が遅くなる。
初回接続完了後、Oracle Instant Client がエラーとなっている1台目の状況を理解しているのか、2回目以降の接続は早い。再起動しても早い。

手順に記載しているとおり、TIMEOUT を指定すると良さそう
↓ TIMEOUT を指定しない方式だと、接続時間が長くなる場合がある

```
taf_service=
(DESCRIPTION=
  (LOAD_BALANCE=off) 
  (FAILOVER=on) 
  (ADDRESS=
    (PROTOCOL=tcp)  
    (HOST=sugihostname01.pub01.vcn.oraclevcn.com)  
    (PORT=1521)
  ) 
  (ADDRESS=
    (PROTOCOL=tcp)  
    (HOST=sugihostname02.pub01.vcn.oraclevcn.com)
    (PORT=1521)
  ) 
  (CONNECT_DATA=
    (SERVICE_NAME=taf_service.pub01.vcn.oraclevcn.com) 
    (FAILOVER_MODE=
      (TYPE=select) 
      (METHOD=basic)
    )
  )
)

```



# 参考URL

## Oracle の Document

https://docs.oracle.com/cd/E96517_01/adfns/high-availability.html#GUID-799A6C37-32B5-4785-AEB0-49C9AFA6F4EE

https://docs.oracle.com/cd/E96517_01/netag/enabling-advanced-features.html#GUID-4354E9E3-F521-48B7-83A0-AADC1970C84F

https://docs.oracle.com/cd/E96517_01/adfns/high-availability.html#GUID-8DAB53D8-5761-42A5-BF4C-B38FDE9D7C1E

【技術資料】透過的アプリケーション・フェイルオーバー（TAF）：Oracle Database 10g
https://blogs.oracle.com/oracle4engineer/taforacle-database-10g

https://www.oracle.com/technetwork/jp/articles/chapter2-3-083925-ja.html



## General

https://www.slideshare.net/techblogyahoo/oracle-data-guard-48172431

TAF(透過的アプリケーション・フェイル オーバー)の紹介
https://www.ydc.co.jp/solution/standby/article/_taf.html

Oracleへの接続
https://www.techscore.com/tech/Java/JavaEE/JDBC/oracle-1/