システムインフィニティ 武石
はじめに
コネクションプールを使用した環境において実際に発生した落とし穴です。
環境
APサーバ :Resin 2.1.14
JAVA :J2SDK
1.4.2_07
DBサーバ(Microsoft
SQL Server 2000 SP3)は別マシン環境にありAPサーバ(Resin)のコネクションプールを利用してアプリケーションからJDBCで接続している。
問題点の解説
コネクションプールが一杯になりデータベース接続が遅延・滞留してしまう原因の1つとして以下のコネクションの存在が判明。
コネクション
import com.caucho.sql.DBPool; import
java.sql.Connection; import
java.sql.SQLException;
・・・・・ ・・・・・ ・・・・・ Connection
conn = null; try
{ conn = DBPool.getPool([データソース名]).getConnection(); ・・・・・ conn = DBPool.getPool([データソース名]).getConnection();
}
catch (SQLException e) { ・・・・・ ・・・・・ ・・・・・ }
finally { if
(conn != null) {
conn.close(); }
catch (SQLException e) { //
例外は無視する } conn = null; } } ・・・・・ ・・・・・ ・・・・・ |
コネクションの問題点
今回コネクションの障害は同一のオブジェクト(Connection)に対して2回データベース接続を行ったことが原因であるが、このパターンの問題は通常あまり見られずまた見落としがちである。
データベース接続に対する開放(close)は多少の経験があれば必ず行う必要のある処理であることは理解できまた正しく確実に開放(close)されているか確認することであろう。
しかしながら今回の問題はデータベース接続を開放(close)する通常フォーカスしているのとはまったく違う意味で実装(不具合の実装)が可能でなおかつ見つけにくい問題であったが、ソース解析によってこの問題点にフォーカスできさらにこの問題を障害として再現可能であったため取り除くことが可能であった。
大げさだと思われるかもしれないがプログラマーのスキル(知識や経験)、そしてちょっとした処理フローの見落としで難なく発生することが可能になる。
また特定が難しい場合も多くある。特にオブジェクト(Connection)を広範囲のスコープで利用可能にしている場合は注意が必要であるしこういったオブジェクトは限られたスコープで用いるべきであろう。
発生するのは当たり前だと思っている貴方も今までのプログラムを注意深く観察してみては如何であろう。
先ほど「問題の障害を再現可能であったため」と言ったがコネクションプールを利用していたのが障害発生の諸原因であり逆にコネクションプールを利用することで障害の確認が容易に可能であった。
もしコネクションプールを使用していなかったら障害が発生していなかったかもしれない、あるいは違った形の障害となっていた可能性もある。しかしその場合その障害を確認するのは困難を極めたであろう。
※
コネクションプールを利用すべきであると説いているわけではないので誤解しないように。
問題点の確認
もしこの問題を実際に試してみたければ以下の内容を参考にAPサーバでご確認いただきたい。
なおAPサーバには Resin
のバージョン 2 を想定しているのでご了承願いたい。
また Resin のバージョン 3 でも確認は出来ると思うので試してみても良いでしょう。
但しこの動作確認における責任は各自で負う事。
1.Resinのコンフィグレーションファイル
resin.conf(%RESIN_HOME%/conf/resin.conf)をテキストエディターで開くと40行目あたりに以下の記述が見つかる。
<resin.conf>
・・・・・ ・・・・・ ・・・・・ <resource-ref> <res-ref-name>jdbc/test</res-ref-name> <res-type>javax.sql.DataSource</res-type> <init-param
driver-name="com.caucho.jdbc.mysql.Driver"/> <init-param
url="jdbc:mysql_caucho://localhost:3306/test"/> <init-param
user=""/> <init-param
password=""/> <init-param
max-connections="20"/> <init-param
max-idle-time="30"/> </resource-ref> ・・・・・ ・・・・・ ・・・・・ |
参考に以下の様にリソース定義を追加する。
なお追加する内容は動作可能な環境に合わせて記述願う。
<resource-ref>
</resource-ref> |
@
リソース名(プーリングのデータソース名)を定義する。
A
リソース(データソース)のタイプを設定。
B
Microsoft SQL Server のJDBCドライバを使用する場合。
他のデータベース及びJDBCドライバを使用する場合は合わせてください。
C
接続先のデータベースサーバ名またはIPアドレスを指定。
D
接続先のデータベースサーバのポート番号を定義
Microsoft SQL Server の場合通常は 1433 になる。
E
データベースアカウントの既定のデータベース以外に接続する場合や明示的に指定する場合、データベース名を指定。
データベースアカウントの既定のデータベースを使用する場合や明示的に指定をしない場合 'databasename=〜' 部分は省略可能である。
F
データベースアカウントのユーザー
G
データベースアカウントのパスワード
H
コネクションプールの最大プール数を指定。
確認テストであれば多くは要らない。
I
コネクションプールからプールを取得する最大待ち時間と思われる(単位は分)
しかし1分に設定しても1分で帰ってこないので違うかも知れない。
知っている人は情報ください。
※
データベースに接続した後、テーブルなどのオブジェクトに対して操作はしないのでバックアップなどは不要である。
※
次ページから確認用プログラム(JSP)が3本出てくるが、その3本のプログラムの配置場所はサーブレットコンテナ上で実行するのに有効な場所であればどこに配置しても問題は無い。ぜひ試していただきたい。
2.コネクションのプログラムで確認
コネクションプールを消費させるコネクションのサンプルソースを以下に載せる。
resin.confのリソース(データソース)定義に合わせればこのまま動くはずである。
<trouble_dbpool_resin.jsp>
<%@
page contentType="text/html;charset=MS932"
pageEncoding="MS932" %> <%@
page import="com.caucho.sql.DBPool" %> <%@
page import="java.sql.Connection" %> <%@
page import="java.sql.SQLException" %> <%! private
static final String DATA_SOURCE_NAME = "jdbc/pool-test"; %> <% DBPool dbpool = DBPool.getPool(DATA_SOURCE_NAME); String
resule = ""; if
(request.getParameter("connect") != null)
{ Connection
conn = null; try
{ //
1回目のデータベース接続 conn = dbpool.getConnection(); //
2回目のデータベース接続 conn = dbpool.getConnection(); resule = "2回接続しました"; }
catch (SQLException e) { throw
e; }
finally { if
(conn != null) { try
{ conn.close(); }
catch (SQLException e) { //
例外は無視する } conn = null; } resule += "<br>1回開放しました"; } } resule += "<br>現在のアクティブなコネクションプール数は"; resule += dbpool.getActiveConnections(); resule += "です"; %> <html> <head> <title>WEBアプリケーションにおけるデータベースプール使用時の障害事例</title> </head> <body
bgcolor="white"> <form
action="<%= request.getRequestURI()
%>" method="post" target="_top"> <center> <hr> <font
size="5"> 接続ボタンを押してください。<br> 押す度にコネクションが1つずつ消費されていきます。 </font> <hr> <input
type="submit" name="connect" value=" 接 続 "> <hr> <%=
resule %> <hr> </center> </form> </body> </html> |
3.コネクション未開放のプログラムで確認
コネクションを1回だけ行い開放(クローズ)しない場合でも確認してしてはどうなるであろう。聞くまでも無いだろう。
<noclose_dbpool_resin.jsp>
<%@
page contentType="text/html;charset=MS932"
pageEncoding="MS932" %> <%@
page import="com.caucho.sql.DBPool" %> <%@
page import="java.sql.Connection" %> <%@
page import="java.sql.SQLException" %> <%! private
static final String DATA_SOURCE_NAME = "jdbc/pool-test"; %> <% DBPool dbpool = DBPool.getPool(DATA_SOURCE_NAME); String
resule = ""; if
(request.getParameter("connect") != null)
{ Connection
conn = null; try
{ //
1回目のデータベース接続 conn = dbpool.getConnection(); resule = "1回接続しました"; }
catch (SQLException e) { throw
e; } } resule += "<br>現在のアクティブなコネクションプール数は"; resule += dbpool.getActiveConnections(); resule += "です"; %> <html> <head> <title>WEBアプリケーションにおけるデータベースプール使用時の障害事例</title> </head> <body
bgcolor="white"> <form
action="<%= request.getRequestURI()
%>" method="post" target="_top"> <center> <hr> <font
size="5"> 接続ボタンを押してください。<br> 押す度にコネクションが1つずつ消費されていきます。 </font> <hr> <input
type="submit" name="connect" value=" 接 続 "> <hr> <%=
resule %> <hr> </center> </form> </body> </html> |
4.コネクションプールのリソース確認プログラム
コネクションプールの各情報とresinが使用するJVMヒープメモリなど情報を表示するツールを以下に載せる。
Resinにはコネクションプールを利用するためのライブラリ(com.caucho.sql.DBPool)が標準で付いているためコネクションプールを利用するのがとても楽である。
環境に合わせて本ソースを修正する場合は5行目〜16行目のみの修正でよいはずである。
<admin_dbpool_resin.jsp>
<%@
page contentType="text/html;charset=MS932"
pageEncoding="MS932" %> <%@
page import="java.text.DecimalFormat"
%> <%@
page import="com.caucho.sql.DBPool" %> <%! //
------------------------------------------------------------ //
resin.conf の設定に併せて以下を変更してください。 // sDB_POOL_MANES
: DBプール名 //
------------------------------------------------------------ static
final String[] sDB_POOL_MANES = { "jdbc/ pool-test"
}; //
------------------------------------------------------------ //
横に表示するDPプールの情報数(既定値は1列) // iDISP_COLS
: DBプール表示数(横) //
------------------------------------------------------------ static
final int iDISP_COLS = 1; %> <% //
------------------------------------------------------------ //
ここから下のソースは変更しないでください!! //
------------------------------------------------------------ int iDB_PoolCnt = sDB_POOL_MANES.length; int[]
iDB_Pool_Max = new int[iDB_PoolCnt]; int[]
iDB_Pool_Used = new int[iDB_PoolCnt]; int[]
iDB_Pool_Total = new int[iDB_PoolCnt]; DBPool[] dbpool
= new DBPool[iDB_PoolCnt]; //
リロード時間の初期値(3000ミリ秒) int re = 3000; if
(request.getParameter("re") != null) { re
= Integer.parseInt(request.getParameter("re")); } //
JVMの総ヒープメモリ取得、空きメモリ取得 DecimalFormat df = new DecimalFormat("###,###,###,###,###"); String
freeMemory = df.format(Runtime.getRuntime().freeMemory()); String
totalMemory = df.format(Runtime.getRuntime().totalMemory()); //
初期化 for
(int i = 0; i < iDB_PoolCnt; i++) { //
DBプールのMAX数 iDB_Pool_Max[i] = -1; //
DBプールの使用数 iDB_Pool_Used[i] = -1; //
DBプールの合計数 iDB_Pool_Total[i] = -1; //
DBプール dbpool[i] = null; } //
JDBCドライババージョン・初期化 String
strVersion
= "--"; //
JSPバージョン・初期化 JspFactory jsFact =
null; JspEngineInfo jsEinf =
null; String
strJspVersion =
"--"; //
サーブレットAPIバージョン・初期化 String
strServletApiVersion = "--"; //
例外時のエラー文字列・初期化 String
errorStr = ""; try
{ //
DBプール情報取得 for
(int i = 0; i < iDB_PoolCnt; i++) { dbpool[i]
= DBPool.getPool(sDB_POOL_MANES[i]); iDB_Pool_Max[i] = dbpool[i].getMaxConnections(); iDB_Pool_Used[i] = dbpool[i].getActiveConnections(); iDB_Pool_Total[i] = dbpool[i].getTotalConnections(); } //
JDBCドライババージョン情報取得 strVersion = dbpool[0].getMajorVersion() + "." + dbpool[0].getMinorVersion(); //
JSPバージョン取得 jsFact = JspFactory.getDefaultFactory(); jsEinf = jsFact.getEngineInfo(); strJspVersion = jsEinf.getSpecificationVersion(); //
サーブレットAPIバージョン取得 strServletApiVersion = getServletContext().getMajorVersion() + "." + getServletContext().getMinorVersion(); }
catch (Exception e) { errorStr = e.toString(); e.printStackTrace(); }
finally { jsFact = null; jsEinf = null; } %> <html> <head> <!--
meta http-equiv="refresh" content="<%= (re / 1000) %>;url=<%= request.getRequestURI()
%>" --> <title>Audit</title> <SCRIPT
LANGUAGE="JavaScript"> <!-- setTimeout("re()",
<%= re %>); function re() { location.reload(); } //--> </SCRIPT> </head> <body
bgcolor="white"> <center> <table
border="0" style="font-size:12px"> <tr> <td
align="center"> <b>DBプールおよび、サーブレットコンテナ(Tomcat/Resin)監視画面<br></b> <font
color="gray"><%= (re / 1000) %>秒おきにリロードします。</font> </td> </tr> <tr> <td> <table
border="0"> <% int iDispCols = iDISP_COLS; if
(iDispCols < 1) { iDispCols = 1; } for
(int cnt = 0; cnt < iDB_PoolCnt;) { %>
<tr> <% for
( int cols = 0; cols < iDispCols
&& cnt < iDB_PoolCnt;
cols++, cnt++) { %>
<td>
<table border="1" width="270"
style="font-size:12px">
<tr>
<td align="center" colspan="2"
bgcolor="#FFCCCC"><%= sDB_POOL_MANES[cnt]
%></td>
</tr>
<tr> <td
align="left" width="200"> DBプールのMAX数</td>
<td align="center"><%= iDB_Pool_Max[cnt] %></td>
</tr>
<tr>
<td align="left"> コネクションの合計数</td>
<td align="center"><%= iDB_Pool_Total[cnt] %></td>
</tr>
<tr>
<td align="left"> 現在、使用中のプール数</td> <% if
(((iDB_Pool_Used[cnt] *
10) / iDB_Pool_Max[cnt])
>= 4 && ((iDB_Pool_Used[cnt] * 10) / iDB_Pool_Max[cnt]) <= 7) { %>
<td align="center" bgcolor="#FFFF99"><%=
iDB_Pool_Used[cnt]
%></td> <% }
else if ((( iDB_Pool_Used[cnt]
* 10) / iDB_Pool_Max[cnt]
) > 7) { %>
<td align="center" bgcolor="#FFCCCC"><%=
iDB_Pool_Used[cnt]
%></td> <% }
else { %>
<td align="center"><%= iDB_Pool_Used[cnt] %></td> <% } %>
</tr>
</table>
</td> <% } %>
</tr>
<tr>
<td></td>
</tr> <% } %> </table> </td> </tr> <tr> <td>・pool数の背景が黄色、または赤色になりましたらシステム管理者に連絡して下さい。</td> </tr> <tr> <td> (黄色:40-70%使用中、赤色:70-100%使用中)</td> </tr> <tr> <td>・情報取得に失敗した場合は、-1が表示されます。</td> </tr> <tr> <td> </td> </tr> <tr> <td> <table
border="1" width="270"
style="font-size:12px">
<tr>
<td align="center" colspan="2"
bgcolor="#FFCCCC">その他、JAVA環境の情報</td>
</tr>
<tr>
<td align="left" width="200"> JDBCドライババージョン</td>
<td align="center"><%= strVersion
%></td>
</tr>
<tr>
<td align="left"> JSPバージョン</td>
<td align="center"><%= strJspVersion
%></td>
</tr>
<tr>
<td align="left"> サーブレットAPIバージョン</td>
<td align="center"><%= strServletApiVersion
%></td>
</tr>
<tr>
<td align="left"> JVM総ヒープメモリ(バイト)</td>
<td align="right"><%= totalMemory
%></td>
</tr>
<tr>
<td align="left"> JVM空きメモリ(バイト)</td>
<td align="right"><%= freeMemory
%></td>
</tr> </table> </td> </tr> </table> <%=
errorStr %> </center> </body> </html> |