Hi all,
I'm using H2 as an in-memory cache in a long-running server application.
Are there any guidelines on configuring H2 for such use, with the primary
goal being low latency for queries? I'd like to bring performance as close
as possible to say, scanning some in-memory collections using an SQL
interface.
One key latency-sensitive step here is where I need to query 10-30 tables
(select * from table), serially or in parallel, whichever is faster. Here
is a benchmark I'm using to evaluate, and the code I'm using to setup a
HikariCP connection pool that is backed by H2. The benchmark below is what
I've arrived at by iteratively stripping away code from the original
system's code path of concern.
The workloads are running on OSX, with OpenJDK - 12.0.1. I've also done a
sweep with different numbers of tables/rows etc, and can share more data if
required.
Benchmark (rowsPerTable) Mode
Cnt Score Error Units
ModelUpdateDataBenchmark.withPreparedPool 1000 avgt
10 4973866.388 ± 734500.031 ns/op
ModelUpdateDataBenchmark.withPreparedPoolParallel 1000 avgt
10 1313731.990 ± 186437.956 ns/op
The parts that I'd like feedback on:
- is 1.3ms what I should expect to query 10 tables, each with 1000 rows of
2 columns, from an in-memory H2 db? It still feels high, but I'm likely
missing something.
- Any JDBC options I should be using? E.g., I added the options to disable
tracing after I spotted JdbcResultSet.getString() spending a lot of time in
debugCodeCall(). I feel like there might be more ways to tweak this
further. :)
Thanks in advance!
--
Lalith
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class ModelUpdateDataBenchmark {
private static final int NUM_TABLES = 10;
@Param({"1000"})
static int rowsPerTable;
@State(Scope.Benchmark)
@SuppressWarnings("all")
public static class BenchmarkState {
final DBConnectionPool dbConnectionPool = setupDbConnectionPool();
final AtomicInteger integer = new AtomicInteger(0);
@Nullable
Model model = null;
@Setup(Level.Invocation)
public void setUpDb() {
final DSLContext conn = dbConnectionPool.getConnectionToDb();
for (int t = 0; t < NUM_TABLES; t++) {
conn.execute(String.format("delete from t%s where c2 >= 0",
t));
for (int i = 0; i < rowsPerTable; i++) {
final int val = integer.incrementAndGet();
conn.execute(String.format("insert into t%s values
('%s', %s)", t, val, val));
}
}
}
private DBConnectionPool setupDbConnectionPool() {
final DBConnectionPool dbConnectionPool = new
DBConnectionPool();
for (int t = 0; t < NUM_TABLES; t++) {
dbConnectionPool.getConnectionToDb()
.execute(String.format("create table t%s (c1
varchar(36) primary key, c2 integer)", t));
}
return dbConnectionPool;
}
}
@Benchmark
public void withPreparedPoolParallel(final
ModelUpdateDataBenchmark.BenchmarkState state,
final Blackhole blackhole) {
IntStream.range(0, NUM_TABLES).parallel().forEach(
t -> {
try (final ResultQuery<?> rq =
state.dbConnectionPool.getConnectionToDb().selectFrom("t" +
t).keepStatement(true)) {
blackhole.consume(rq.fetch());
}
}
);
}
@Benchmark
public void withPreparedPool(final
ModelUpdateDataBenchmark.BenchmarkState state,
final Blackhole blackhole) {
IntStream.range(0, NUM_TABLES).forEach(
t -> {
try (final ResultQuery<?> rq =
state.dbConnectionPool.getConnectionToDb().selectFrom("t" +
t).keepStatement(true)) {
blackhole.consume(rq.fetch());
}
}
);
}
}
#DBConnectionPool
class DBConnectionPool {
private static final Settings JOOQ_SETTING = new
Settings().withExecuteLogging(false);
private final String databaseName;
private final DataSource ds;
DBConnectionPool() {
this.databaseName = UUID.randomUUID().toString();
setupDb();
final HikariConfig config = new HikariConfig();
config.setJdbcUrl(String.format("jdbc:h2:mem:%s;TRACE_LEVEL_FILE=0;TRACE_LEVEL_SYSTEM_OUT=0;",
databaseName));
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("maximumPoolSize", "20");
this.ds = new HikariDataSource(config);
}
@VisibleForTesting
DSLContext getConnectionToDb() {
return using(ds, SQLDialect.H2, JOOQ_SETTING);
}
}
--
You received this message because you are subscribed to the Google Groups "H2
Database" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/h2-database/6ed9ca4f-bc55-4da5-970e-ae2bb6abd54do%40googlegroups.com.