コネクションプーリングの効果を実測してみる
Java EE アプリケーションサーバを使うとどの製品にもDBコネクションプーリング機能が付いている。DB接続の再接続にはコストがかかることがこの機能の動機だが、いったいどれくらいの『コスト』なのか。あまり実データを見たことないので、実際に測ってみる。
テスト環境
コストを実感しやすいように、DBには接続ごとに対応した子プロセスをfork()するPostgreSQLを使ってみる。
- Mac OS X (Core i5 1.7GHz)
- OracleJDK 1.7.0_51
- PostgreSQL 9.3
- PostgreSQL JDBC Driver 9.3-1100-jdbc41
- Commons DBCP 1.4
テスト内容
10多重で1回、100回、1000回、10000回の新規接続して SELECT 1 を投げるプログラムを実行する。DBCPを使ったコネクションプーリングの有無で性能を比較する。
テストプログラム
まずはSQLを実行する部分。SELECT 1 を投げるだけのとてもシンプルなタスク。
DBCPを使わない速度を測るときには、コメントアウトされている方のコードを動かす。
public class QueryRunner implements Runnable { @Override public void run() { // try (Connection con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/test", "postgres", "postgres")) { try (Connection con = DataSourceFactory.getDataSource().getConnection()){ PreparedStatement ps = con.prepareStatement("SELECT 1"); ps.executeQuery(); } catch (SQLException e) { e.printStackTrace(); } } }
10多重で動かして、時間を測る部分。Executorフレームワークを使用した。
public class Main { private static final Logger logger = Logger.getLogger(Main.class.getName()); public static void main(String ... args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(10); long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { executor.submit(new QueryRunner()); } executor.shutdown(); if (!executor.awaitTermination(120, TimeUnit.SECONDS)) { logger.warning("timeout occur. over 120 seconds."); } long end = System.currentTimeMillis(); long time = end - start; logger.info("time(millsec): " + time); } }
DBCPの初期化をする部分。スタンドアロンのDBCP初期化コードはあまり書いたことがないので自信なし。この部分はもっと良い書き方もあると思う。
public class DataSourceFactory { private static final Logger log = Logger.getLogger(DataSourceFactory.class.getName()); private static DataSource ds = null; static { try(InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties")) { Properties props = new Properties(); props.load(in); ds = BasicDataSourceFactory.createDataSource(props); log.info("DataSource initialized."); } catch (Exception e) { log.warning("DataSource initialize failed."); } } public static DataSource getDataSource() { if (ds == null) { throw new IllegalStateException("DataSource initialize failed."); } return ds; } }
最後にDBCPのプロパティファイル。アカウントの部分は適宜置き換えて使う。ちょっとハマったのがdefaultAutoCommitがデフォルトでtrueであること。DBCPを使っていると、JDBCを直接使うときと同じように、デフォルトではINSERT/UPDATE/DELETEを実行した時点でコミットされてしまうので、多くのシステムではここをfalseに明示的に設定した方が良いと思う。
driverClassName=org.postgresql.Driver url=jdbc:postgresql://localhost:5432/test username=postgres password=postgres defaultAutoCommit=false initialSize=10 maxActive=10 maxIdle=10 maxWait=10000
測定結果
100トランザクションまでは両方とも比較的早い応答があるが、1000トランザクション以降差が広がり、一万トランザクションでは10倍以上の性能差が開く。コネクションプーリングは改めて有効だということがわかった。
またトランザクション数1の時に、DBCPなしの方が早い結果が出ているが、これはDBCPのプロパティを読み込んで初期化する処理が時間の測定範囲内に含まれていることが要因だと思う。
(時間の単位はミリ秒)
トランザクション数 | DBCPなし | DBCPあり |
---|---|---|
1 | 135 | 262 |
100 | 1328 | 308 |
1000 | 6575 | 2062 |
5000 | 30864 | 3537 |
10000 | 70151 | 6326 |