Webエンジニア susumuis の技術ブログ

このブログの内容は個人の見解であり、所属する組織の公式見解ではありません

PostgreSQL で LANケーブルが抜けた場合にConnectionを破棄する方法

TomcatでWebアプリを開発していてDBとの接続はコネクションプーリングを行なっています。コネクションプーリングをしているとはいえ、何らかの原因でConnectionが使い物にならない場合は、再接続をするために、Connection取得時に以下のようなSQLを投げて死活チェックを行なっています。

select 1

ところが、例えばDBサーバへのLANケーブルが抜けた場合、このSQLをいつまでも実行してしまい、結果的にConnectionPoolからの取得待ち行列がたまり、Tomcatのスレッドを消費してDBが不要なサービスにまで影響を及ぼしてしまうという問題があります。

その問題が発生しないように、クエリにタイムアウトを施したいと思い、まずは以下のようにしてみました。

Statement stmt = connection.createStatement();
stmt.setQueryTimeout(1);

ところがこの方法は上手くいきません。

Method setQueryTimeout(int) is not yet implemented.

などのように言われてしまいます。実装されていないのか?(なぜ?)それでは、タイマーを用意して横からcancelをさせることを考えました。

final Statement stmt = connection.createStatement();
Runnable canceler = new Runnable() {
	public void run() {
		try {
			stmt.cancel();
		} catch (SQLException e) {
			// I can do nothing :)
		}
	}
};
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
executor.schedule(canceler, 500, TimeUnit.MILLISECONDS);

この方法は上手くいきませんでした。なにせLANケーブルが抜けているのでcancelすら到達しないようです。

それでは、逆の発想で、別スレッドでテストをさせて、メインのスレッドはjoinをして、制限時間を過ぎればスルーして終了する実装にしてみましょう。

final Statement stmt = connection.createStatement();
class Checker implements Runnable {
	boolean succeeded = false;
	public void run() {
		try {
			try {
				stmt.execute("SELECT 1");
				succeeded = true;
			} finally {
				stmt.close();
			}
		} catch (SQLException e) {
			// I can do nothing :)
		}
	}
}
Checker checker = new Checker();
Thread thread = new Thread(checker);
thread.start();
try {
	thread.join(1000);
} catch (InterruptedException e) {
	// i can do nothing :)
}
 return !checker.succeeded;

この方法でとりあえず何とかしましたが、これでもなんだかcheckerのスレッドを消費しそうなのと毎回スレッド生成するので遅くなりそうですね……。背に腹は変えられないということか?