ValueStoreWalThroughputBenchmark.java
/*******************************************************************************
* Copyright (c) 2025 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.sail.nativerdf.wal;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Measurement(iterations = 3)
@Fork(1)
@State(Scope.Benchmark)
public class ValueStoreWalThroughputBenchmark {
@Param({ "COMMIT", "INTERVAL", "ALWAYS" })
public String syncPolicy;
@Param({ "32", "256" })
public int payloadBytes;
@Param({ "0", "1000" })
public int ackEvery;
private ValueStoreWalConfig config;
private ValueStoreWAL wal;
private String lexical;
private final AtomicInteger seq = new AtomicInteger();
@Setup(Level.Trial)
public void setup() throws IOException {
Path walDir = Files.createTempDirectory("wal-bench-");
ValueStoreWalConfig.Builder builder = ValueStoreWalConfig.builder()
.walDirectory(walDir)
.storeUuid(UUID.randomUUID().toString());
builder.syncPolicy(ValueStoreWalConfig.SyncPolicy.valueOf(syncPolicy));
config = builder.build();
wal = ValueStoreWAL.open(config);
lexical = randomAscii(payloadBytes);
}
@TearDown(Level.Trial)
public void tearDown() throws IOException {
if (wal != null) {
wal.close();
}
}
@Benchmark
@Threads(8)
public void logMint_literal() throws IOException, InterruptedException {
int id = seq.incrementAndGet();
long lsn = wal.logMint(id, ValueStoreWalValueKind.LITERAL, lexical, "", "", 0);
if (ackEvery > 0) {
// acknowledge durability occasionally
if ((id % ackEvery) == 0) {
wal.awaitDurable(lsn);
}
}
}
@Benchmark
@Threads(8)
public void logMint_iri() throws IOException, InterruptedException {
int id = seq.incrementAndGet();
long lsn = wal.logMint(id, ValueStoreWalValueKind.IRI, "http://example.com/" + id, "", "", 0);
if (ackEvery > 0) {
if ((id % ackEvery) == 0) {
wal.awaitDurable(lsn);
}
}
}
private static String randomAscii(int len) {
StringBuilder sb = new StringBuilder(len);
ThreadLocalRandom r = ThreadLocalRandom.current();
for (int i = 0; i < len; i++) {
// printable ASCII range 32..126
char c = (char) r.nextInt(32, 127);
sb.append(c);
}
return sb.toString();
}
}