H2Databaseを追っかけていたりしたブログ

H2 database のリリースノートを読んだりとか。

各種パラメータ変更によるH2のパフォーマンスへの影響

せっかくどのようなコードか書いてもらったので、H2でいくつか設定を変更しつつ試してみる。

実行環境は以下の通り。

OSX 10.8.2
MacBook 
13-inch, Aluminum、Late 2008
2Ghz Intel Core2 Duo
8GB 1333MHz DDR3

HDD WesternDigital 320GB 7200回転

Eclipseから実行。
JREは、JavaSE-1.7(Java SE 7)で、1.7.0_05。
VM引数としては、明示的には -Xmx2048m -Xms2048m のみ。

結果としては、下記のような感じ。各設定毎に14回実行して、実行前に毎回データベースのディレクトリは削除している。

jdbc:h2:/Volumes/Macintosh HDD/test/db/h2 --(1)
H2              10万件    4.38秒    1.48秒
H2              10万件    1.79秒    0.65秒
H2              10万件    1.74秒    0.51秒
H2              10万件    1.70秒    0.52秒
H2              10万件    1.65秒    0.53秒
H2              10万件    1.60秒    0.57秒
H2              10万件    1.69秒    0.55秒
H2              10万件    1.66秒    0.62秒
H2              10万件    1.59秒    0.52秒
H2              10万件    1.59秒    0.52秒
H2              10万件    1.55秒    0.53秒
H2              10万件    1.56秒    0.53秒
H2              10万件    1.56秒    0.56秒
H2              10万件    1.60秒    0.56秒
jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072 --(2)
H2              10万件    1.61秒    0.18秒
H2              10万件    1.69秒    0.15秒
H2              10万件    1.58秒    0.18秒
H2              10万件    1.59秒    0.18秒
H2              10万件    1.58秒    0.18秒
H2              10万件    1.72秒    0.18秒
H2              10万件    1.59秒    0.19秒
H2              10万件    1.56秒    0.17秒
H2              10万件    1.62秒    0.70秒
H2              10万件    1.66秒    0.18秒
H2              10万件    1.65秒    0.18秒
H2              10万件    1.60秒    0.18秒
H2              10万件    1.73秒    0.18秒
H2              10万件    1.74秒    0.18秒
jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072;PAGE_SIZE=32768; --(3)
H2              10万件    1.31秒    0.17秒
H2              10万件    1.37秒    0.16秒
H2              10万件    1.38秒    0.16秒
H2              10万件    1.77秒    0.32秒
H2              10万件    1.34秒    0.16秒
H2              10万件    1.36秒    0.25秒
H2              10万件    1.87秒    0.16秒
H2              10万件    1.47秒    0.17秒
H2              10万件    1.34秒    0.16秒
H2              10万件    1.72秒    0.16秒
H2              10万件    1.25秒    0.16秒
H2              10万件    1.35秒    0.19秒
H2              10万件    1.29秒    0.16秒
H2              10万件    1.35秒    0.16秒
jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0; --(4)
H2              10万件    0.39秒    0.15秒
H2              10万件    0.32秒    0.15秒
H2              10万件    0.32秒    0.14秒
H2              10万件    0.42秒    0.15秒
H2              10万件    0.32秒    0.14秒
H2              10万件    0.32秒    0.15秒
H2              10万件    0.33秒    0.15秒
H2              10万件    0.33秒    0.24秒
H2              10万件    0.32秒    0.14秒
H2              10万件    0.32秒    0.14秒
H2              10万件    0.32秒    0.15秒
H2              10万件    0.39秒    0.15秒
H2              10万件    0.32秒    0.15秒
H2              10万件    0.32秒    0.14秒
jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;PAGE_SIZE=32768; --(5)
H2              10万件    0.28秒    0.17秒
H2              10万件    0.29秒    0.20秒
H2              10万件    0.28秒    0.12秒
H2              10万件    0.29秒    0.17秒
H2              10万件    0.37秒    0.12秒
H2              10万件    0.28秒    0.12秒
H2              10万件    0.36秒    0.12秒
H2              10万件    0.34秒    0.12秒
H2              10万件    0.29秒    0.12秒
H2              10万件    0.33秒    0.19秒
H2              10万件    0.33秒    0.13秒
H2              10万件    0.28秒    0.13秒
H2              10万件    0.37秒    0.17秒
H2              10万件    0.31秒    0.12秒
jdbc:h2:mem:;LOG=0;LOCK_MODE=0;UNDO_LOG=0; --(6)
H2              10万件    0.28秒    0.26秒
H2              10万件    0.17秒    0.24秒
H2              10万件    0.17秒    0.22秒
H2              10万件    0.22秒    0.26秒
H2              10万件    0.18秒    0.22秒
H2              10万件    0.18秒    0.25秒
H2              10万件    0.18秒    0.23秒
H2              10万件    0.22秒    0.23秒
H2              10万件    0.23秒    0.22秒
H2              10万件    0.17秒    0.22秒
H2              10万件    0.17秒    0.23秒
H2              10万件    0.18秒    0.22秒
H2              10万件    0.17秒    0.28秒
H2              10万件    0.17秒    0.23秒
jdbc:h2:/SSD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0; --(7)
H2              10万件    0.38秒    0.17秒
H2              10万件    0.46秒    0.14秒
H2              10万件    0.34秒    0.14秒
H2              10万件    0.37秒    0.15秒
H2              10万件    0.35秒    0.14秒
H2              10万件    0.36秒    0.22秒
H2              10万件    0.34秒    0.14秒
H2              10万件    0.35秒    0.13秒
H2              10万件    0.35秒    0.15秒
H2              10万件    0.35秒    0.14秒
H2              10万件    0.35秒    0.25秒
H2              10万件    0.35秒    0.14秒
H2              10万件    0.37秒    0.17秒
H2              10万件    0.34秒    0.13秒
jdbc:h2:/SSD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;PAGE_SIZE=32768; --(8)
H2              10万件    0.38秒    0.12秒
H2              10万件    0.28秒    0.12秒
H2              10万件    0.27秒    0.12秒
H2              10万件    0.30秒    0.13秒
H2              10万件    0.28秒    0.12秒
H2              10万件    0.33秒    0.13秒
H2              10万件    0.30秒    0.12秒
H2              10万件    0.28秒    0.12秒
H2              10万件    0.27秒    0.12秒
H2              10万件    0.31秒    0.15秒
H2              10万件    0.32秒    0.21秒
H2              10万件    0.37秒    0.13秒
H2              10万件    0.33秒    0.18秒
H2              10万件    0.39秒    0.14秒

初回以降が速いのは、JITの影響だろうか。

(1)は、まったくのデフォルトの状態。(2)で、キャッシュサイズを131072kbに設定している。これでおそらくキャッシュにデータが大方乗ったのか、selectが大分速くなった。(3)はページサイズを32768bytesに設定している。現行のH2では最大(デフォルトでは2048bytes)で、ページ分割の回数が減るからこういうケースではinsert時間が減るのでは、と思ったら20%程度速くなっている。selectにもう少し悪影響が出るかなと思ったらそうでもなかった。(4)では、トランザクションログ、ロック、アンドゥログをすべて無効にしているので速い(が、当然通常使用には適さない)。(5)はページサイズとログ周り無効の組み合わせ。ログ周り無効の影響に比べれば、ページサイズの調整は効果が少ない。(6)はオンメモリデータベース。insertは最速だが、selectが若干遅くなった? あんまり関係なさそうな気もするのだけれど。(7),(8)はSSD。このケースでは思ったより効果でず。キャッシュでオンメモリになっていて、あまりランダムアクセスが発生していないのでは、と推測。

設定は若干異なるが、下記が100万件で実施。元ブログよりも結構速いが、1000万件まで増やすととたんに返ってこなくなった。今度ちょっと突っ込んで調べてみよう。

jdbc:h2:/Volumes/Macintosh HDD/test/db/h2
H2             100万件   23.51秒    7.35秒
H2             100万件   18.32秒    6.49秒
H2             100万件   18.96秒    6.55秒
jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072
H2             100万件   19.31秒    9.09秒
H2             100万件   19.09秒    8.44秒
H2             100万件   18.65秒    8.93秒
jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;
H2             100万件    6.73秒    6.26秒
H2             100万件    6.66秒    6.76秒
H2             100万件    9.26秒    6.62秒
jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;PAGE_SIZE=32768;
H2             100万件    9.25秒    4.33秒
H2             100万件    9.17秒    5.11秒
H2             100万件    8.76秒    5.00秒
jdbc:h2:mem:;LOG=0;LOCK_MODE=0;UNDO_LOG=0;
H2             100万件    2.24秒    3.59秒
H2             100万件    3.13秒    2.99秒
H2             100万件    2.37秒    4.19秒
jdbc:h2:/SSD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;PAGE_SIZE=32768;
H2             100万件    9.82秒    4.85秒
H2             100万件    9.83秒    4.43秒
H2             100万件   11.76秒    5.06秒

ソースコードは下記の通り。

package test;

import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

import org.junit.Test;

public class JdbcTest {

	@Test
	public void h2_00() throws Exception {
		String url = "jdbc:h2:/Volumes/Macintosh HDD/test/db/h2";
		System.out.println(url);
		for (int i = 0; i < TRY_COUNT; i++) {
			before();
			con = DriverManager.getConnection(url);
			Statement st = con.createStatement();
			executeUpdate(st, "drop table if exists person");
			executeUpdate(st,
					"create table person (id integer primary key, name varchar)");
			executeQuery();
			after();
		}
	}

	@Test
	public void h2_01() throws Exception {
		String url = "jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072";
		System.out.println(url);
		for (int i = 0; i < TRY_COUNT; i++) {
			before();
			con = DriverManager.getConnection(url);
			Statement st = con.createStatement();
			executeUpdate(st, "drop table if exists person");
			executeUpdate(st,
					"create table person (id integer primary key, name varchar)");
			executeQuery();
			after();
		}
	}

	@Test
	public void h2_02() throws Exception {
		String url = "jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072;PAGE_SIZE=32768;";
		System.out.println(url);
		for (int i = 0; i < TRY_COUNT; i++) {
			before();
			con = DriverManager.getConnection(url);
			Statement st = con.createStatement();
			executeUpdate(st, "drop table if exists person");
			executeUpdate(st,
					"create table person (id integer primary key, name varchar)");
			executeQuery();
			after();
		}
	}

	@Test
	public void h2_03() throws Exception {
		String url = "jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;";
		System.out.println(url);
		for (int i = 0; i < TRY_COUNT; i++) {
			before();
			con = DriverManager.getConnection(url);
			Statement st = con.createStatement();
			executeUpdate(st, "drop table if exists person");
			executeUpdate(st,
					"create table person (id integer primary key, name varchar)");
			executeQuery();
			after();
		}
	}

	@Test
	public void h2_04() throws Exception {
		String url = "jdbc:h2:/Volumes/Macintosh HDD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;PAGE_SIZE=32768;";
		System.out.println(url);
		for (int i = 0; i < TRY_COUNT; i++) {
			before();
			con = DriverManager.getConnection(url);
			Statement st = con.createStatement();
			executeUpdate(st, "drop table if exists person");
			executeUpdate(st,
					"create table person (id integer primary key, name varchar)");
			executeQuery();
			after();
		}
	}

	@Test
	public void h2_05() throws Exception {
		String url = "jdbc:h2:mem:;LOG=0;LOCK_MODE=0;UNDO_LOG=0;";
		System.out.println(url);
		for (int i = 0; i < TRY_COUNT; i++) {
			before();
			con = DriverManager.getConnection(url);
			Statement st = con.createStatement();
			executeUpdate(st, "drop table if exists person");
			executeUpdate(st,
					"create table person (id integer primary key, name varchar)");
			executeQuery();
			after();
		}
	}

	@Test
	public void h2_06() throws Exception {
		String url = "jdbc:h2:/SSD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;";
		System.out.println(url);
		for (int i = 0; i < TRY_COUNT; i++) {
			before("/Users/ayataro/test/db");
			con = DriverManager.getConnection(url);
			Statement st = con.createStatement();
			executeUpdate(st, "drop table if exists person");
			executeUpdate(st,
					"create table person (id integer primary key, name varchar)");
			executeQuery();
			after();
		}
	}
	
	@Test
	public void h2_07() throws Exception {
		String url = "jdbc:h2:/SSD/test/db/h2;CACHE_SIZE=131072;LOG=0;LOCK_MODE=0;UNDO_LOG=0;PAGE_SIZE=32768;";
		System.out.println(url);
		for (int i = 0; i < TRY_COUNT; i++) {
			before("/Users/ayataro/test/db");
			con = DriverManager.getConnection(url);
			Statement st = con.createStatement();
			executeUpdate(st, "drop table if exists person");
			executeUpdate(st,
					"create table person (id integer primary key, name varchar)");
			executeQuery();
			after();
		}
	}

	// 共通メンバー -------------------------------------------

	private Connection con;
	private static int COUNT = 10000 * 10;
	private static int TRY_COUNT = 14;
	private static final String DATA = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";

	public void before() throws Exception {
		before("/Volumes/Macintosh HDD/test/db");
	}

	public void before(String path) throws Exception {
		File baseDir = new File(path);
		for(File f : baseDir.listFiles()){
			f.delete();
		}
		baseDir.delete();
		baseDir.mkdir();
	}

	public void after() throws Exception {
		if (con != null) {
			con.close();
		}
	}

	private void executeUpdate(Statement st, String sql) {
		try {
			st.executeUpdate(sql);
		} catch (Exception e) {
			System.out.println(e.toString());
		}
	}

	private void executeQuery() throws Exception {
		executeQuery(con.getMetaData().getDatabaseProductName());
	}

	private void executeQuery(String databaseName) throws Exception {

		boolean isCassandra = databaseName.contains("Cassandra");
		boolean isAutoCommit = isCassandra;

		System.out.printf("%-14s", databaseName);
		if (!isAutoCommit) {
			con.setAutoCommit(false);
		}

		long insertStart = System.currentTimeMillis();
		PreparedStatement insertPs = con
				.prepareStatement("insert into person (id, name) values(?, ?)");
		for (int i = 0; i < COUNT; i++) {
			insertPs.setInt(1, i);
			insertPs.setString(2, DATA);
			insertPs.executeUpdate();
			if (!isAutoCommit && i % 10000 == 0) {
				con.commit();
			}
		}
		if (!isAutoCommit) {
			con.commit();
		}
		double insertSec = (double) (System.currentTimeMillis() - insertStart) / 1000;

		long selectStart = System.currentTimeMillis();
		PreparedStatement selectPs = con
				.prepareStatement("select * from person where id = ?");
		for (int i = 0; i < COUNT; i++) {
			selectPs.setInt(1, i);
			selectPs.executeQuery().next();
		}
		double selectSec = (double) (System.currentTimeMillis() - selectStart) / 1000;

		String countSql = "select count(1) from person";
		if (isCassandra) {
			countSql += " limit 100000000";
		}
		ResultSet rs = con.createStatement().executeQuery(countSql);
		rs.next();
		logProcessTime(rs.getInt(1), insertSec, selectSec);
	}

	private void logProcessTime(long count, double insertSec, double selectSec) {

		System.out.printf("%4d万件 ", count / 10000);
		System.out.printf("%7.2f秒 ", insertSec);
		System.out.printf("%7.2f秒", selectSec);
		System.out.println();
	}
}