Skip to content

Commit

Permalink
Add some version-store microbenchmarks
Browse files Browse the repository at this point in the history
This can serve as a tool to later optimize these parts in the Nessie code base:
* version store implementation
* database specific `Persist` implementations
* storate logic implementations

So far, the results look good enough, no concerning outliers w/ in-memory, mongo + cassandra.

Microbenchmarks use `BackendTestFactory` to locate and initialize a `Backend` (and in turn `Persist`) implementation. Microbenchmarks are intended to inspect the Nessie code down up to the database level, but not intended to optimize configurations for a particular database. In other words: only the Nessie code is interesting, but the performance of the database itself is not (that much).

The `BackendTestFactory` interface got a new function `getName()` and the corresponding service definitions have been added.

Needed to use a new Gradle project, because the microbenchmarks need the Java `ServiceLoader` for various reasons, so it the JMH uber-jar needs the _shadow_ plugin, but its presence would make `nessie-services.jar` an uber-jar as well - so it's easier to use a separate Gradle project.
  • Loading branch information
snazy committed Jun 3, 2023
1 parent 6d0978f commit 4c649ce
Show file tree
Hide file tree
Showing 24 changed files with 677 additions and 0 deletions.
1 change: 1 addition & 0 deletions gradle/projects.main.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ nessie-s3minio=testing/s3minio
nessie-s3mock=testing/s3mock
nessie-multi-env-test-engine=testing/multi-env-test-engine
nessie-services=servers/services
nessie-services-bench=servers/services-bench
nessie-server-store=servers/store
nessie-server-store-proto=servers/store-proto
nessie-content-generator=tools/content-generator
Expand Down
28 changes: 28 additions & 0 deletions servers/services-bench/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Nessie VersionStore micro benchmarks

Building:
```bash
./gradlew :nessie-services-bench:jmhJar
```

Running:
```bash
java -jar servers/services-bench/build/libs/nessie-services-bench-*-jmh.jar
```

## Async-profiler

See the [Async Profiler repo](https://github.com/async-profiler/async-profiler) for a pre-built library or how to build
it from source.

Running (Linux):
```bash
LD_LIBRARY_PATH=(PATH-TO-LIBRARY)/ java \
-jar servers/services-bench/build/libs/nessie-services-bench-*-jmh.jar \
-prof async
```

## Linux Perf tools

Install the appropriate `linux-tools` package for your distribution, or `make` it from the Linux sources in `tools/perf`
for your running Linux kernel version. `perf` needs to be in `PATH`.
58 changes: 58 additions & 0 deletions servers/services-bench/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2022 Dremio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
`java-library`
`nessie-conventions`
id("com.github.johnrengelman.shadow")
alias(libs.plugins.jmh)
}

extra["maven.name"] = "Nessie - Services - Microbenchmarks"

dependencies {
implementation(project(":nessie-model"))
implementation(project(":nessie-versioned-spi"))
implementation(libs.slf4j.api)

jmhRuntimeOnly(project(":nessie-server-store"))

implementation(project(":nessie-versioned-storage-common"))
implementation(project(":nessie-versioned-storage-store"))
implementation(project(":nessie-versioned-storage-testextension"))

jmhImplementation(libs.jmh.core)
jmhAnnotationProcessor(libs.jmh.generator.annprocess)
jmhCompileOnly(libs.microprofile.openapi)
jmhRuntimeOnly(project(":nessie-versioned-storage-inmemory"))
jmhRuntimeOnly(project(":nessie-versioned-storage-cassandra"))
jmhRuntimeOnly(project(":nessie-versioned-storage-rocksdb"))
jmhRuntimeOnly(project(":nessie-versioned-storage-mongodb"))
jmhRuntimeOnly(project(":nessie-versioned-storage-dynamodb"))
jmhRuntimeOnly(project(":nessie-versioned-storage-jdbc"))
jmhRuntimeOnly(libs.testcontainers.testcontainers)
jmhRuntimeOnly(libs.testcontainers.cassandra)
jmhRuntimeOnly(libs.testcontainers.mongodb)
jmhRuntimeOnly(libs.docker.java.api)
jmhRuntimeOnly(libs.agroal.pool)
jmhRuntimeOnly(libs.h2)
}

jmh { jmhVersion.set(libs.versions.jmh.get()) }

tasks.named<ShadowJar>("jmhJar") { mergeServiceFiles() }
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2023 Dremio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.projectnessie.services;

import static org.projectnessie.versioned.storage.common.logic.Logics.repositoryLogic;

import java.util.HashSet;
import java.util.ServiceLoader;
import java.util.Set;
import org.projectnessie.versioned.VersionStore;
import org.projectnessie.versioned.storage.common.config.StoreConfig;
import org.projectnessie.versioned.storage.common.persist.Backend;
import org.projectnessie.versioned.storage.common.persist.Persist;
import org.projectnessie.versioned.storage.common.persist.PersistFactory;
import org.projectnessie.versioned.storage.testextension.BackendTestFactory;
import org.projectnessie.versioned.storage.versionstore.VersionStoreImpl;

abstract class BaseParams {
public static final String DEFAULT_BRANCH_NAME = "main";

Backend backend;
VersionStore versionStore;
BackendTestFactory backendTestFactory;

protected void init(String backendName) throws Exception {
Set<String> known = new HashSet<>();
for (BackendTestFactory candidate : ServiceLoader.load(BackendTestFactory.class)) {
String name = candidate.getName();
known.add(name);
if (backendName.equals(name)) {
backendTestFactory = candidate;
break;
}
}
if (backendTestFactory == null) {
throw new IllegalArgumentException(
"Could not find backend named " + backendName + ", known backends: " + known);
}

backendTestFactory.start();

backend = backendTestFactory.createNewBackend();
backend.setupSchema();
PersistFactory factory = backend.createFactory();
Persist persist = factory.newPersist(StoreConfig.Adjustable.empty());
repositoryLogic(persist).initialize(DEFAULT_BRANCH_NAME);
versionStore = new VersionStoreImpl(persist);
}

protected void tearDown() throws Exception {
if (backend != null) {
try {
backend.close();
} finally {
backend = null;
}
}
if (backendTestFactory != null) {
try {
backendTestFactory.stop();
} finally {
backendTestFactory = null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright (C) 2023 Dremio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.projectnessie.services;

import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.projectnessie.model.CommitMeta.fromMessage;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
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;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.projectnessie.model.ContentKey;
import org.projectnessie.model.IcebergTable;
import org.projectnessie.model.Namespace;
import org.projectnessie.versioned.BranchName;
import org.projectnessie.versioned.ContentResult;
import org.projectnessie.versioned.KeyEntry;
import org.projectnessie.versioned.Operation;
import org.projectnessie.versioned.Put;
import org.projectnessie.versioned.ReferenceCreatedResult;
import org.projectnessie.versioned.paging.PaginationIterator;

@Warmup(iterations = 2, time = 2000, timeUnit = MILLISECONDS)
@Measurement(iterations = 3, time = 1000, timeUnit = MILLISECONDS)
@Fork(
value = 1,
jvmArgs = {"-Xms8g", "-Xmx8g"})
@Threads(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(MICROSECONDS)
public class ContentOpsBench {

@State(Scope.Benchmark)
public static class BenchmarkParam extends BaseParams {

@Param({"100", "1000", "10000"})
public int contents;

@Param({"In-Memory"})
public String backendName;

ReferenceCreatedResult ref;
List<ContentKey> keys = new ArrayList<>();

@Setup
public void setup() throws Exception {
super.init(backendName);

Namespace ns = Namespace.of("my-namespace");

BranchName branchName = BranchName.of("branch");
ref = versionStore.create(branchName, Optional.empty());

versionStore.commit(
branchName,
Optional.empty(),
fromMessage("initial"),
Collections.singletonList(Put.of(ns.toContentKey(), ns)));
List<Operation> commitOps = new ArrayList<>();
for (int j = 0; j < contents; j++) {
ContentKey key = ContentKey.of(ns, "table-" + j);
keys.add(key);
commitOps.add(Put.of(key, IcebergTable.of("meta-" + j, j, j, j, j)));
if (commitOps.size() == 500) {
versionStore.commit(branchName, Optional.empty(), fromMessage("x"), commitOps);
commitOps.clear();
}
}
if (!commitOps.isEmpty()) {
versionStore.commit(branchName, Optional.empty(), fromMessage("x"), commitOps);
}
}

@Override
@TearDown
public void tearDown() throws Exception {
super.tearDown();
}

public ContentKey randomKey() {
return keys.get(ThreadLocalRandom.current().nextInt(contents));
}
}

@Benchmark
public void getKeys(BenchmarkParam param, Blackhole bh) throws Exception {
PaginationIterator<KeyEntry> iter =
param.versionStore.getKeys(param.ref.getNamedRef(), null, false, null, null, null, null);
while (iter.hasNext()) {
bh.consume(iter.next());
}
}

@Benchmark
public ContentResult getValue(BenchmarkParam param) throws Exception {
ContentKey key = param.randomKey();
return param.versionStore.getValue(param.ref.getNamedRef(), key);
}

@Benchmark
public Map<ContentKey, ContentResult> getTenValues(BenchmarkParam param) throws Exception {
Set<ContentKey> k = new HashSet<>();
while (k.size() < 10) {
k.add(param.randomKey());
}
return param.versionStore.getValues(param.ref.getNamedRef(), k);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (C) 2023 Dremio
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.projectnessie.services;

import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.util.Optional;
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.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;
import org.openjdk.jmh.annotations.Warmup;
import org.projectnessie.versioned.BranchName;
import org.projectnessie.versioned.ReferenceCreatedResult;

@Warmup(iterations = 2, time = 2000, timeUnit = MILLISECONDS)
@Measurement(iterations = 3, time = 1000, timeUnit = MILLISECONDS)
@Fork(1)
@Threads(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(MICROSECONDS)
public class CreateReferencesBench {

@State(Scope.Benchmark)
public static class BenchmarkParam extends BaseParams {

@Param({"In-Memory"})
public String backendName;

@Setup
public void setup() throws Exception {
super.init(backendName);
}

@Override
@TearDown
public void tearDown() throws Exception {
super.tearDown();
}

AtomicInteger createReferences = new AtomicInteger();
}

@Benchmark
public ReferenceCreatedResult createBranch(BenchmarkParam param) throws Exception {
return param.versionStore.create(
BranchName.of("branch-" + param.createReferences.getAndIncrement()), Optional.empty());
}
}

0 comments on commit 4c649ce

Please sign in to comment.