diff --git a/frameworks/Java/helidon/nima/pom.xml b/frameworks/Java/helidon/nima/pom.xml
index c884659a0a0..c859b758cfc 100644
--- a/frameworks/Java/helidon/nima/pom.xml
+++ b/frameworks/Java/helidon/nima/pom.xml
@@ -21,7 +21,7 @@
io.helidon.applications
helidon-se
- 4.0.3
+ 4.1.2
diff --git a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/JsonSerializer.java b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/JsonSerializer.java
new file mode 100644
index 00000000000..322a7cf030c
--- /dev/null
+++ b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/JsonSerializer.java
@@ -0,0 +1,92 @@
+package io.helidon.benchmark.nima;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.List;
+
+import com.jsoniter.output.JsonStream;
+import com.jsoniter.output.JsonStreamPool;
+import com.jsoniter.spi.JsonException;
+
+public class JsonSerializer {
+
+ private JsonSerializer() {
+ }
+
+ /**
+ * Serialize an instance into a JSON object and return it as a byte array.
+ *
+ * @param obj the instance
+ * @return the byte array
+ */
+ public static byte[] serialize(Object obj) {
+ JsonStream stream = JsonStreamPool.borrowJsonStream();
+ try {
+ stream.reset(null);
+ stream.writeVal(obj.getClass(), obj);
+ return Arrays.copyOfRange(stream.buffer().data(), 0, stream.buffer().tail());
+ } catch (IOException e) {
+ throw new JsonException(e);
+ } finally {
+ JsonStreamPool.returnJsonStream(stream);
+ }
+ }
+
+ /**
+ * Serialize a map of strings into a JSON object and return it as a byte array.
+ *
+ * @param map the map
+ * @return the byte array
+ */
+ public static byte[] serialize(Map map) {
+ JsonStream stream = JsonStreamPool.borrowJsonStream();
+ try {
+ stream.reset(null);
+ stream.writeObjectStart();
+ map.forEach((k, v) -> {
+ try {
+ stream.writeObjectField(k);
+ stream.writeVal(v);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ stream.writeObjectEnd();
+ return Arrays.copyOfRange(stream.buffer().data(), 0, stream.buffer().tail());
+ } catch (IOException e) {
+ throw new JsonException(e);
+ } finally {
+ JsonStreamPool.returnJsonStream(stream);
+ }
+ }
+
+ /**
+ * Serialize a list of objects into a JSON array and return it as a byte array.
+ *
+ * @param objs the list of objects
+ * @return the byte array
+ */
+ public static byte[] serialize(List> objs) {
+ JsonStream stream = JsonStreamPool.borrowJsonStream();
+ try {
+ stream.reset(null);
+ stream.writeArrayStart();
+ int i = 0;
+ int n = objs.size();
+ for (Object obj : objs) {
+ stream.writeVal(obj.getClass(), obj);
+ if (i++ < n - 1) {
+ stream.writeMore();
+ }
+
+ }
+ stream.writeArrayEnd();
+ return Arrays.copyOfRange(stream.buffer().data(), 0, stream.buffer().tail());
+ } catch (IOException e) {
+ throw new JsonException(e);
+ } finally {
+ JsonStreamPool.returnJsonStream(stream);
+ }
+ }
+}
diff --git a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/Main.java b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/Main.java
index 92896867246..df669d8a7a7 100644
--- a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/Main.java
+++ b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/Main.java
@@ -16,14 +16,9 @@
package io.helidon.benchmark.nima;
-import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
import java.util.logging.Logger;
-import com.jsoniter.output.JsonStream;
-import com.jsoniter.output.JsonStreamPool;
-import com.jsoniter.spi.JsonException;
import io.helidon.benchmark.nima.models.DbRepository;
import io.helidon.benchmark.nima.models.HikariJdbcRepository;
import io.helidon.benchmark.nima.models.PgClientRepository;
@@ -41,6 +36,8 @@
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
+import static io.helidon.benchmark.nima.JsonSerializer.serialize;
+
/**
* Main class of the benchmark.
* Opens server on localhost:8080 and exposes {@code /plaintext} and {@code /json} endpoints adhering to the
@@ -90,29 +87,14 @@ static void routing(HttpRules rules) {
rules.get("/plaintext", new PlaintextHandler())
.get("/json", new JsonHandler())
- .get("/10k", new JsonKHandler(10))
.get("/fortunes", new FortuneHandler(repository))
.register("/", new DbService(repository));
}
- private static byte[] serializeMsg(Message obj) {
- JsonStream stream = JsonStreamPool.borrowJsonStream();
- try {
- stream.reset(null);
- stream.writeVal(Message.class, obj);
- return Arrays.copyOfRange(stream.buffer().data(), 0, stream.buffer().tail());
- } catch (IOException e) {
- throw new JsonException(e);
- } finally {
- JsonStreamPool.returnJsonStream(stream);
- }
- }
-
static class PlaintextHandler implements Handler {
static final Header CONTENT_TYPE = HeaderValues.createCached(HeaderNames.CONTENT_TYPE,
- "text/plain; charset=UTF-8");
+ "text/plain; charset=UTF-8");
static final Header CONTENT_LENGTH = HeaderValues.createCached(HeaderNames.CONTENT_LENGTH, "13");
-
private static final byte[] RESPONSE_BYTES = "Hello, World!".getBytes(StandardCharsets.UTF_8);
@Override
@@ -126,44 +108,16 @@ public void handle(ServerRequest req, ServerResponse res) {
static class JsonHandler implements Handler {
private static final String MESSAGE = "Hello, World!";
- private static final int JSON_LENGTH = serializeMsg(new Message(MESSAGE)).length;
+ private static final int JSON_LENGTH = serialize(new Message(MESSAGE)).length;
static final Header CONTENT_LENGTH = HeaderValues.createCached(HeaderNames.CONTENT_LENGTH,
- String.valueOf(JSON_LENGTH));
+ String.valueOf(JSON_LENGTH));
@Override
public void handle(ServerRequest req, ServerResponse res) {
res.header(CONTENT_LENGTH);
res.header(HeaderValues.CONTENT_TYPE_JSON);
res.header(Main.SERVER);
- res.send(serializeMsg(newMsg()));
- }
-
- private static Message newMsg() {
- return new Message("Hello, World!");
- }
- }
-
- static class JsonKHandler implements Handler {
- private final Header contentLength;
- private final String message;
-
- JsonKHandler(int kilobytes) {
- this.message = "a".repeat(1024 * kilobytes);
- int length = serializeMsg(new Message(message)).length;
- this.contentLength = HeaderValues.createCached(HeaderNames.CONTENT_LENGTH,
- String.valueOf(length));
- }
-
- @Override
- public void handle(ServerRequest req, ServerResponse res) {
- res.header(contentLength);
- res.header(HeaderValues.CONTENT_TYPE_JSON);
- res.header(Main.SERVER);
- res.send(serializeMsg(newMsg()));
- }
-
- private Message newMsg() {
- return new Message(message);
+ res.send(serialize(new Message(MESSAGE)));
}
}
diff --git a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/DbRepository.java b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/DbRepository.java
index 204c9ad5ad1..d1f75b558c3 100644
--- a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/DbRepository.java
+++ b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/DbRepository.java
@@ -6,35 +6,16 @@
import java.util.concurrent.ThreadLocalRandom;
import jakarta.json.Json;
-import jakarta.json.JsonArray;
-import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonBuilderFactory;
-import jakarta.json.JsonObject;
public interface DbRepository {
JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
- default World getWorld() {
- return getWorld(randomWorldNumber());
- }
-
World getWorld(int id);
- default JsonObject getWorldAsJson(int id) {
- return getWorld().toJson();
- }
-
List getWorlds(int count);
- default JsonArray getWorldsAsJson(int count) {
- JsonArrayBuilder result = JSON.createArrayBuilder();
- for (World world : getWorlds(count)) {
- result.add(world.toJson());
- }
- return result.build();
- }
-
World updateWorld(World world);
List updateWorlds(int count);
diff --git a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/HikariJdbcRepository.java b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/HikariJdbcRepository.java
index 686559b2fd9..fd9760939df 100644
--- a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/HikariJdbcRepository.java
+++ b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/HikariJdbcRepository.java
@@ -7,6 +7,8 @@
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
import java.util.logging.Logger;
import com.zaxxer.hikari.HikariConfig;
@@ -22,20 +24,31 @@ public class HikariJdbcRepository implements DbRepository {
private final HikariConfig hikariConfig;
public HikariJdbcRepository(Config config) {
+ // hikari connection configuration
String url = "jdbc:postgresql://" +
config.get("host").asString().orElse("tfb-database") +
":" + config.get("port").asString().orElse("5432") +
"/" + config.get("db").asString().orElse("hello_world");
-
hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(config.get("username").asString().orElse("benchmarkdbuser"));
hikariConfig.setPassword(config.get("password").asString().orElse("benchmarkdbpass"));
- hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
+ // hikari additional configuration
int poolSize = config.get("sql-pool-size").asInt().orElse(64);
- hikariConfig.addDataSourceProperty("maximumPoolSize", poolSize);
- LOGGER.info("Db pool size is set to " + poolSize);
+ hikariConfig.setMaximumPoolSize(poolSize);
+ LOGGER.info("Hikari pool size is set to " + poolSize);
+ ThreadFactory vtThreadFactory = Thread.ofVirtual().factory();
+ hikariConfig.setThreadFactory(vtThreadFactory);
+ hikariConfig.setScheduledExecutor(Executors.newScheduledThreadPool(poolSize, vtThreadFactory));
+ LOGGER.info("Set thread factory to VTs");
+
+ // data source properties
+ hikariConfig.addDataSourceProperty("cachePrepStmts","true");
+ hikariConfig.addDataSourceProperty("prepStmtCacheSize","250");
+ hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit","2048");
+ hikariConfig.addDataSourceProperty("ssl", "false");
+ hikariConfig.addDataSourceProperty("tcpKeepAlive", "true");
}
private Connection getConnection() throws SQLException {
diff --git a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/PgClientRepository.java b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/PgClientRepository.java
index 7775a177537..e5166b10fbc 100644
--- a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/PgClientRepository.java
+++ b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/PgClientRepository.java
@@ -8,27 +8,25 @@
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
-import io.helidon.common.reactive.Multi;
-import io.helidon.common.reactive.Single;
import io.helidon.config.Config;
+
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
+import io.vertx.core.Future;
import io.vertx.pgclient.PgConnectOptions;
import io.vertx.pgclient.PgPool;
import io.vertx.sqlclient.PoolOptions;
+import io.vertx.sqlclient.PreparedQuery;
import io.vertx.sqlclient.Row;
+import io.vertx.sqlclient.RowSet;
import io.vertx.sqlclient.SqlClient;
import io.vertx.sqlclient.Tuple;
-import jakarta.json.JsonArray;
-import jakarta.json.JsonArrayBuilder;
-import jakarta.json.JsonObject;
import static io.helidon.benchmark.nima.models.DbRepository.randomWorldNumber;
public class PgClientRepository implements DbRepository {
private static final Logger LOGGER = Logger.getLogger(PgClientRepository.class.getName());
-
private final SqlClient queryPool;
private final SqlClient updatePool;
@@ -36,9 +34,13 @@ public class PgClientRepository implements DbRepository {
private final long updateTimeout;
private final int maxRetries;
+ private final PreparedQuery> getFortuneQuery;
+ private final PreparedQuery> getWorldQuery;
+ private final PreparedQuery> updateWorldQuery;
+
public PgClientRepository(Config config) {
Vertx vertx = Vertx.vertx(new VertxOptions()
- .setPreferNativeTransport(true));
+ .setPreferNativeTransport(true));
PgConnectOptions connectOptions = new PgConnectOptions()
.setPort(config.get("port").asInt().orElse(5432))
.setCachePreparedStatements(config.get("cache-prepared-statements").asBoolean().orElse(true))
@@ -59,31 +61,20 @@ public PgClientRepository(Config config) {
queryPool = PgPool.client(vertx, connectOptions, clientOptions);
updatePool = PgPool.client(vertx, connectOptions, clientOptions);
- }
- @Override
- public JsonObject getWorldAsJson(int id) {
- return getWorld(id, queryPool).map(World::toJson).await();
+ getWorldQuery = queryPool.preparedQuery("SELECT id, randomnumber FROM world WHERE id = $1");
+ updateWorldQuery = queryPool.preparedQuery("UPDATE world SET randomnumber = $1 WHERE id = $2");
+ getFortuneQuery = queryPool.preparedQuery("SELECT id, message FROM fortune");
}
@Override
public World getWorld(int id) {
try {
- return getWorld(id, queryPool).toCompletableFuture().get();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public JsonArray getWorldsAsJson(int count) {
- try {
- return Multi.range(0, count)
- .flatMap(i -> getWorld(randomWorldNumber(), queryPool))
- .map(World::toJson)
- .reduce(JSON::createArrayBuilder, JsonArrayBuilder::add)
- .map(JsonArrayBuilder::build)
- .await();
+ return getWorldQuery.execute(Tuple.of(id))
+ .map(rows -> {
+ Row r = rows.iterator().next();
+ return new World(r.getInteger(0), r.getInteger(1));
+ }).toCompletionStage().toCompletableFuture().get();
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -92,17 +83,15 @@ public JsonArray getWorldsAsJson(int count) {
@Override
public List getWorlds(int count) {
try {
- List result = new ArrayList<>(count);
+ List> futures = new ArrayList<>();
for (int i = 0; i < count; i++) {
- World world = queryPool.preparedQuery("SELECT id, randomnumber FROM world WHERE id = $1")
- .execute(Tuple.of(randomWorldNumber()))
- .map(rows -> {
- Row r = rows.iterator().next();
- return new World(r.getInteger(0), r.getInteger(1));
- }).toCompletionStage().toCompletableFuture().get();
- result.add(world);
+ futures.add(getWorldQuery.execute(Tuple.of(randomWorldNumber()))
+ .map(rows -> {
+ Row r = rows.iterator().next();
+ return new World(r.getInteger(0), r.getInteger(1));
+ }));
}
- return result;
+ return Future.all(futures).toCompletionStage().toCompletableFuture().get().list();
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -110,10 +99,14 @@ public List getWorlds(int count) {
@Override
public World updateWorld(World world) {
- return Single.create(queryPool.preparedQuery("UPDATE world SET randomnumber = $1 WHERE id = $2")
- .execute(Tuple.of(world.id, world.id))
- .toCompletionStage()
- .thenApply(rows -> world)).await();
+ try {
+ return updateWorldQuery.execute(Tuple.of(world.id, world.id))
+ .toCompletionStage()
+ .thenApply(rows -> world)
+ .toCompletableFuture().get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
@Override
@@ -165,25 +158,18 @@ private List updateWorldsRetry(List worlds, int from, int retries)
@Override
public List getFortunes() {
- return Single.create(queryPool.preparedQuery("SELECT id, message FROM fortune")
- .execute()
- .map(rows -> {
- List fortunes = new ArrayList<>(rows.size() + 1);
- for (Row r : rows) {
- fortunes.add(new Fortune(r.getInteger(0), r.getString(1)));
- }
- return fortunes;
- }).toCompletionStage()).await();
- }
-
- private static Single getWorld(int id, SqlClient pool) {
- return Single.create(pool.preparedQuery("SELECT id, randomnumber FROM world WHERE id = $1")
- .execute(Tuple.of(id))
- .map(rows -> {
- Row r = rows.iterator().next();
- return new World(r.getInteger(0), r.getInteger(1));
- }).toCompletionStage());
-
+ try {
+ return getFortuneQuery.execute()
+ .map(rows -> {
+ List fortunes = new ArrayList<>(rows.size() + 1);
+ for (Row r : rows) {
+ fortunes.add(new Fortune(r.getInteger(0), r.getString(1)));
+ }
+ return fortunes;
+ }).toCompletionStage().toCompletableFuture().get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
private CompletableFuture> updateWorlds(List worlds, int from, SqlClient pool) {
@@ -193,8 +179,7 @@ private CompletableFuture> updateWorlds(List worlds, int from
World w = worlds.get(i);
tuples.add(Tuple.of(w.randomNumber, w.id));
}
- return pool.preparedQuery("UPDATE world SET randomnumber = $1 WHERE id = $2")
- .executeBatch(tuples)
+ return updateWorldQuery.executeBatch(tuples)
.toCompletionStage()
.thenApply(rows -> worlds)
.toCompletableFuture();
diff --git a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/World.java b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/World.java
index ee8eb9194cd..39deafea11b 100644
--- a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/World.java
+++ b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/World.java
@@ -9,9 +9,9 @@
public final class World {
- private static final String ID_KEY = "id";
- private static final String ID_RANDOM_NUMBER = "randomNumber";
- private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
+ static final String ID_KEY = "id";
+ static final String ID_RANDOM_NUMBER = "randomNumber";
+ static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
public int id;
public int randomNumber;
diff --git a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/services/DbService.java b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/services/DbService.java
index 46c244d96f5..e3bd1fe39fc 100644
--- a/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/services/DbService.java
+++ b/frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/services/DbService.java
@@ -1,27 +1,23 @@
package io.helidon.benchmark.nima.services;
-import java.util.Collections;
import java.util.List;
import io.helidon.benchmark.nima.models.DbRepository;
import io.helidon.benchmark.nima.models.World;
import io.helidon.common.parameters.Parameters;
+import io.helidon.http.HeaderValues;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
-
-import jakarta.json.Json;
-import jakarta.json.JsonArrayBuilder;
-import jakarta.json.JsonBuilderFactory;
-import jakarta.json.JsonObject;
+import io.helidon.common.mapper.OptionalValue;
import static io.helidon.benchmark.nima.Main.SERVER;
import static io.helidon.benchmark.nima.models.DbRepository.randomWorldNumber;
+import static io.helidon.benchmark.nima.JsonSerializer.serialize;
public class DbService implements HttpService {
- private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap());
private final DbRepository repository;
@@ -38,36 +34,33 @@ public void routing(HttpRules httpRules) {
private void db(ServerRequest req, ServerResponse res) {
res.header(SERVER);
- res.send(repository.getWorldAsJson(randomWorldNumber()));
+ res.header(HeaderValues.CONTENT_TYPE_JSON);
+ res.send(serialize(repository.getWorld(randomWorldNumber())));
}
private void queries(ServerRequest req, ServerResponse res) {
res.header(SERVER);
+ res.header(HeaderValues.CONTENT_TYPE_JSON);
int count = parseQueryCount(req.query());
- res.send(repository.getWorldsAsJson(count));
+ res.send(serialize(repository.getWorlds(count)));
}
private void updates(ServerRequest req, ServerResponse res) {
res.header(SERVER);
+ res.header(HeaderValues.CONTENT_TYPE_JSON);
int count = parseQueryCount(req.query());
List worlds = repository.updateWorlds(count);
- JsonArrayBuilder arrayBuilder = JSON.createArrayBuilder();
- for (World world : worlds) {
- JsonObject json = world.toJson();
- arrayBuilder.add(json);
- }
- res.send(arrayBuilder.build());
+ res.send(serialize(worlds));
}
private int parseQueryCount(Parameters parameters) {
- List values = parameters.all("queries");
- if (values.isEmpty()) {
+ OptionalValue value = parameters.first("queries");
+ if (value.isEmpty()) {
return 1;
}
- String first = values.get(0);
int parsedValue;
try {
- parsedValue = Integer.parseInt(first, 10);
+ parsedValue = Integer.parseInt(value.get(), 10);
} catch (NumberFormatException e) {
return 1;
}
diff --git a/frameworks/Java/helidon/nima/src/main/resources/application.yaml b/frameworks/Java/helidon/nima/src/main/resources/application.yaml
index ff4aa100b67..41f4d64ec12 100644
--- a/frameworks/Java/helidon/nima/src/main/resources/application.yaml
+++ b/frameworks/Java/helidon/nima/src/main/resources/application.yaml
@@ -19,6 +19,7 @@ server:
port: 8080
backlog: 8192
write-queue-length: 8192
+ smart-async-writes: true
connection-options:
read-timeout: PT0S
connect-timeout: PT0S