Эх сурвалжийг харах

Updates benchmark to latest version of Helidon (#9326)

* Centralizes JSON serialization. Minor optimizations to pgclient repository implementation.

Signed-off-by: Santiago Pericas-Geertsen <[email protected]>

* Fixes problem in PgClientRepository.getFortunes().

Signed-off-by: Santiago Pericas-Geertsen <[email protected]>

* Sets a VT factory for Hikari CP.

Signed-off-by: Santiago Pericas-Geertsen <[email protected]>

* Additional configuration for Hikari CP.

Signed-off-by: Santiago Pericas-Geertsen <[email protected]>

* Sets Helidon version to 4.1.2.

Signed-off-by: Santiago Pericas-Geertsen <[email protected]>

* Set content type in response. Fixes problems with JSON serialization.

Signed-off-by: Santiago Pericas-Geertsen <[email protected]>

---------

Signed-off-by: Santiago Pericas-Geertsen <[email protected]>
Santiago Pericas-Geertsen 1 жил өмнө
parent
commit
51155f12b2

+ 1 - 1
frameworks/Java/helidon/nima/pom.xml

@@ -21,7 +21,7 @@
     <parent>
         <groupId>io.helidon.applications</groupId>
         <artifactId>helidon-se</artifactId>
-        <version>4.0.3</version>
+        <version>4.1.2</version>
         <relativePath/>
     </parent>
 

+ 92 - 0
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<String, String> 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);
+        }
+    }
+}

+ 6 - 52
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.HttpRules;
 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 @@ public final class Main {
 
         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 final class Main {
 
     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)));
         }
     }
 

+ 0 - 19
frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/DbRepository.java

@@ -6,35 +6,16 @@ import java.util.List;
 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<World> 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<World> updateWorlds(int count);

+ 17 - 4
frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/HikariJdbcRepository.java

@@ -7,6 +7,8 @@ import java.sql.ResultSet;
 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 {

+ 45 - 60
frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/PgClientRepository.java

@@ -8,27 +8,25 @@ import java.util.concurrent.TimeUnit;
 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<RowSet<Row>> getFortuneQuery;
+    private final PreparedQuery<RowSet<Row>> getWorldQuery;
+    private final PreparedQuery<RowSet<Row>> 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 class PgClientRepository implements DbRepository {
 
         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 class PgClientRepository implements DbRepository {
     @Override
     public List<World> getWorlds(int count) {
         try {
-            List<World> result = new ArrayList<>(count);
+            List<Future<?>> 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 class PgClientRepository implements DbRepository {
 
     @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 @@ public class PgClientRepository implements DbRepository {
 
     @Override
     public List<Fortune> getFortunes() {
-        return Single.create(queryPool.preparedQuery("SELECT id, message FROM fortune")
-                .execute()
-                .map(rows -> {
-                    List<Fortune> 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<World> 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<Fortune> 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<List<World>> updateWorlds(List<World> worlds, int from, SqlClient pool) {
@@ -193,8 +179,7 @@ public class PgClientRepository implements DbRepository {
             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();

+ 3 - 3
frameworks/Java/helidon/nima/src/main/java/io/helidon/benchmark/nima/models/World.java

@@ -9,9 +9,9 @@ import jakarta.json.JsonObject;
 
 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;

+ 12 - 19
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 class DbService implements HttpService {
 
     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<World> 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<String> values = parameters.all("queries");
-        if (values.isEmpty()) {
+        OptionalValue<String> 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;
         }

+ 1 - 0
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