WebLogic データソース管理POC

# 本記事の目的
WebLogicでデータソースを構成する手順とサンプルコード、データベース接続検証関連パラメータの製品挙動についてまとめている。

WAS Liberty編、JBoss編、Tomcat編についてはこちらを参照ください。
[WebSphere Libertyデータソース管理POC](https://qiita.com/thashi/items/d720e03a2062ecb512c2)
[JBoss データソース管理（DBはAurora PostgreSQL）POC](https://qiita.com/thashi/items/8b30041be752235196b3)
[Tomcat DBCP POC](https://qiita.com/thashi/items/e7b8a70fe382ca3d68c2)

# 背景
とある超重要システムでDb2障害時にWebSphereを再起動していた。
ミッションクリティカルなシステムではAP再起動はアプリ全体の停止につながるため、対応方法としてはありえない。影響範囲を極小化するためにせめてコネクションプールのリフレッシュだけにできないか？と思ったのがこの記事のモチベーション。

# 検証環境
WebLogic 14.1.1
Oracle JDK 1.8.0_202
Red Hat Enterprise Linux release 9.3 (Plow)
Oracle Database 21c Enterprise Edition Release 21.0.0.0.0

# WebLogicのインストールと設定
https://www.oracle.com/jp/middleware/technologies/fusionmiddleware-downloads.html
fmw_14.1.1.0.0_wls_lite_Disk1_1of1をダウンロードする。

https://www.oracle.com/middleware/technologies/fusion-certification.html
に記載の通り最新のJDKはcertifyされていないので注意が必要。
Oracle JDK、かつ1.8.0_251+、11.0.6+
最初、certification matrixを見ずに最新のJDKバージョンで動かしていたが、100発100中、データソースの作成でJVMがクラッシュして落ちた。XXXExceptionではなくJVMプロセスごと落ちたのでJVM最適化部分で何等かbugがある可能性が高い。最新のOracle JVMを使う際には注意が必要。

### GUIインストール
```
export LANG=c　・・・これは文字化け回避
/opt/weblogic/jdk-1.8.0_202/bin/java -jar fmw_14.1.1.0.0_wls_lite_generic.jar
```
今回はWindows開発環境を開発モードでインストールし、Linux POC環境を本番モードでインストール。

今回のPOCで一番はまったのがWindows開発環境のセットアップ
https://blogs.oracle.com/oracle4engineer/post/ja-wls-14-in-eclipse-ide-for-java-ee-developers
が大筋ではあるが若干記事が古く、そのままでは動いてくれないため、最新のEclipseで適宜読み替えないといけない。また、動作が不安定で時折、WebLogic再起動でアプリケーションを認識せず、アプリケーションコンポーネントがない趣旨のエラーが発生し、アプリの強制停止、削除と再デプロイが必要になることがあった。ソースコード編集と動的クラスロードについても機能しなかったためテストフィードバックサイクル観点で難あり。以前のバージョンでは動いていたため機能劣化しているのが残念。早くVSCodeプラグインを出して欲しい。

### WebLogicの設定
後述するが、ドキュメントや管理コンソールのパラメータにない挙動がありその動きをつかむのに苦戦した。
検証目的では最初から下記の設定をいれることを推奨する。本番環境ではリソース負荷が高いので開発環境に限定して利用することを推奨。データソース周りのトラブルシューティングでも役立つので覚えておきたい。

### WebLogicのJVMオプション設定
/opt/weblogic/14.1.1/user_projects/domains/base_domain/bin/setDomainEnv.shに下記を追加
```
JAVA_OPTIONS="${JAVA_OPTIONS} -Dweblogic.debug.DebugJDBCSQL=true -Dweblogic.log.StdoutSeverity=Debug"
export JAVA_OPTIONS
```

### 管理コンソール設定
環境→サーバー→をたどり「デバッグ」メニューで下記を設定する。
- weblogic/jdbc/connection/DebugJDBCConn
- weblogic/jdbc/internal/DebugJDBCInternal
- weblogic/jdbc/sql/DebugJDBCSQL

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325048/26435cdc-beda-828b-1e21-fb48baa86146.png)

サービス→データソース→をたどりモニタリングを設定する。JBossやWAS LibertyのようにDestroyされた数がみれないのは残念だが、接続の総数でCreateの挙動が追える。
- 使用不可数
- 使用可能数
- 再接続の失敗数
- 接続の総数

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325048/b4016061-2d84-6ecd-2a74-8f5f33c36146.png)

接続プールの設定は下記の通り
本番では容量をトランザクション量に応じて100程度にするが、今回のPOCでは5にしている。理由は、容量数に比例してConnectionオブジェクトのvalidation処理のデバッグログが出力されてトレースしにくくなるため。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325048/7cb4b527-8a55-ade6-09f6-53cc380e3cf6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325048/bc214a14-75b5-e4d7-e5d7-94a4a045f458.png)

テスト対象の表名については下記のOracleのドキュメントに記載のとおり

[データソースはDB接続をテストする場合、[SQL ISVALID]と[SQL SELECT 1 FROM DUAL]の区別について (Doc ID 2238470.1)](https://support.oracle.com/epmos/faces/DocumentDisplay?_afrLoop=512944101745445&id=2238470.1&_afrWindowMode=0&_adf.ctrl-state=ayyc3zfx6_4)

- [ISVALID] を利用すると、パフォーマンスが向上させるが、ある場合は正しくDBの状態がチェックできません。

と記載されているため、SELECT 1 FROM DUALを設定するのがよい。

### 検証手順用のコード
WebLogicは、ドキュメントに記載されていない内部仕様によってDB障害復帰後の無効コネクションオブジェクトを破棄する。DB強制停止、起動、ログ確認、コンソール確認、ブラウザでのアプリ実行を手動実行してしまうとWebLogicの正しい動きをとらえられなくなる。そのため、下記のコードを利用した。

Oracle強制停止
```
#!/bin/bash

sqlplus sys/password as sysdba@localhost:1521/ORCLPDB  <<EOS
shutdown abort
exit
EOS
```
Oracle起動
```
#!/bin/bash

sqlplus sys/password as sysdba@localhost:1521/ORCLPDB  <<EOS
startup
exit
EOS
```

Oracle起動直後のアプリケーションアクセス
WebLogicの内部メカニズムによるリカバリ処理よりも早くアプリケーションアクセスするのが目的。
test.shの中身
```
#!/bin/bash

/opt/oracle/bin/shutdown_abort.sh

/opt/oracle/bin/startup.sh

/usr/bin/curl http://192.168.47.135:7001/oracle/TestServlet

sleep 1

/usr/bin/curl http://192.168.47.135:7001/oracle/TestServlet

sleep 1

/usr/bin/curl http://192.168.47.135:7001/oracle/TestServlet
```

## アプリケーションのデプロイ
今回使用したコードは下記のとおり
```
package test;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

/**
 * Servlet implementation class TestServlet
 */
@WebServlet("/TestServlet")
public class TestServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public TestServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Connection conn = null;
		try {
			String sql = "select col1 from test";
			long start = System.nanoTime();
			Context envContext = new InitialContext();
			DataSource ds = (DataSource)envContext.lookup("oracle");
			conn = ds.getConnection();
			long end = System.nanoTime();
			System.out.println((end - start)/1000 + "micro秒");
            String result = executeQuery(conn, sql, 1);
			PrintWriter out = response.getWriter();
			out.println("result:" + result);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}
	
	private String executeQuery(Connection conn, String sql) {
		String result = "";
		Statement stmt;
		try {
			stmt = conn.createStatement();
			ResultSet rset = stmt.executeQuery(sql);
			while (rset.next()) {
				result = result + rset.getString(1);
			}
			rset.close();
	        stmt.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		System.out.println("result:" + result);
		return result;
	}
	
	private String executeQuery(Connection conn, String sql, int count) {
		String result = "";
		for(int i = 0; i < count; i++) {
			result = result + executeQuery(conn, sql);
		}
		return result;
	}

}
```

## 検証：Oracle障害

### 障害後のコネクション数の回復挙動
JBossではパラメータ「use-fast-fail」によってDB復旧後の最初のアプリケーションアクセスで一気に接続数を回復するか、徐々に回復するかの挙動を制御できる。WebLogicには該当パラメータがないため、内部仕様を調査した。検証結果は下記挙動（少しづつ回復）となった。初期段階で多く回復し、後半にいけばいくほど少しづつ回復する挙動。

- 使用不可能数/使用可能数/再接続の失敗数/接続の総数
- 0/100/0/10　・・・正常状態
- 0/1/2/101　・・・ Oracle再起動により100から一気に減る。
- 0/24/2/124　・・・アプリケーションアクセスにより接続数が23増加。
- 0/38/2/138　・・・アプリケーションアクセスにより接続数が14増加。
- 0/51/2/151
・・・省略・・・
- 0/90/2/190
- 0/94/2/194
- 0/98/2/198　・・・アプリケーションアクセスにより接続数が4増加。
- 0/100/2/200　・・・アプリケーションアクセスにより接続数が2増加。(完全回復)

JBoss cli同様に、コネクション回復用機能が提供されている。
データソースの制御メニューの強制停止と起動でプールが再作成され状態復帰した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325048/36393b1e-9393-abf3-15a1-f5e1784b70fd.png)

### Oracleインスタンス障害時の挙動
```
<Info> <Common> <BEA-000633> <リソース作成が連続して失敗した回数がしきい値の2を超えたため、リソース・プール"oracle"は中断しています>
<Info> <JDBC> <BEA-001128> <プール"oracle"の接続がクローズされました。>
```
WebLogicが障害を検知し、管理コンソールの「モニタリング」に表示される状態が「Running」から「Suspended」に変化する。

### インスタンス起動後
エラーが5秒おきに出力される。コネクションをテストしてエラーになって破棄する趣旨のメッセージが出力される。この5秒おきの挙動についてはドキュメントにも記載がなくこの5秒を設定するパラメータもない。
この挙動によりコネクションの使用可能数は100からゼロに近づく。
その後、下記のようなメッセージが出力され使用可能数が5秒おきに一つずつ増える。
```
<Info> <Common> <BEA-000628> <プール"oracle"に"1"個のリソースが作成されました。そのうちの"1"個は利用可能で、"0"個は利用できません。>
```

### 通常時の挙動
デフォルト設定では120秒に一回下記メッセージが出力される。
```
<Debug> <JDBCInternal> <BEA-000000> < > CE:test (10) sqlQuery = SELECT 1 FROM DUAL>
<Debug> <JDBCInternal> <BEA-000000> < < CE:test (40) returns 1>
```
この120秒はテスト頻度：120の挙動。
>「未使用の接続をテストするときに、次のテストが試行されるまでWebLogic Serverインスタンスが待機する秒数(「テスト対象の表名」を指定する必要があります)。テストに失敗した接続は閉じられ、再度開かれて有効な物理接続が再確立されます。テストが再度失敗すると、その接続は閉じられます。」

JMeterでの性能検証。（CPU80%くらいの負荷でのスループット）
- テスト頻度:10秒時のスループット＝921tps
- テスト頻度:120秒時のスループット＝970tps(約5%増)

120秒は長すぎるため、リソースへの影響も少ないことから10秒程度に設定するのがよい。

### 回復後の挙動
上記test.shの結果、インスタンス起動直後のアプリケーションアクセスについてはエラーになる場合と正常の場合の両方が確認された。
これはWebLogicの内部の回復挙動が早いか、アプリケーションが早いかの違い。
他社製品と異なり、復帰後初回アクセスが成功になる保証はないが、秒単位で正常アクセス状態に戻る事が確認できた。

## まとめ
WebLogicを利用する際には、「予約時に接続をテスト」と「テスト対象の表名:SQL SELECT 1 FROM DUAL」を適切に設定することでDB障害復帰後にWebLogicを再起動する必要はなく、無効なDB接続は即時ではないものの破棄されDB接続は自然回復する。

## おまけ：このテーマに関する各製品の特長
- 初回正常復帰：DB復旧後のアプリケーションの初回実行時に無効となったコネクションを破棄してコネクションを作成して処理が正常になる意味

| |アプリ実行時validation|定期実行validation|接続の強制リフレッシュ|
|:-----------------|------------------:|:------------------:|:------------------:|
| Tomcat|〇初回正常復帰|〇アプリ実行時validationと併用可能|×なし|
| JBoss|〇初回正常復帰|△アプリ実行時validationと併用不可だが併用する必要なし|〇提供あり|
| WAS Liberty|〇初回正常復帰|△提供なしだが必要なし|×なし|
| WebLogic|×初回エラー|〇内部仕様により5秒程度で復旧|〇提供あり|