Преглед изворни кода

Merge pull request #9555 from jkuhn1/master

Inverno upgrade to 1.12.0 + various performance improvements
Mike Smith пре 7 месеци
родитељ
комит
ff0bd35129

+ 12 - 9
frameworks/Java/inverno/README.md

@@ -2,20 +2,23 @@
 
 ### Test Type Implementation Source Code
 
-* [JSON](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
-* [PLAINTEXT](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
-* [DB](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
-* [QUERY](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
-* [CACHED QUERY](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
-* [UPDATE](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
-* [FORTUNES](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
+* [JSON](src/main/java/com/techempower/inverno/benchmark/internal/Controller.java)
+* [PLAINTEXT](src/main/java/com/techempower/inverno/benchmark/internal/Controller.java)
+* [DB](src/main/java/com/techempower/inverno/benchmark/internal/Controller.java)
+* [QUERY](src/main/java/com/techempower/inverno/benchmark/internal/Controller.java)
+* [CACHED QUERY](src/main/java/com/techempower/inverno/benchmark/internal/Controller.java)
+* [UPDATE](src/main/java/com/techempower/inverno/benchmark/internal/Controller.java)
+* [FORTUNES](src/main/java/com/techempower/inverno/benchmark/internal/Controller.java)
 
 ## Important Libraries
+
 The tests were run with:
-* [Java OpenJDK 16](https://openjdk.java.net/)
-* [Inverno 1.4.1](https://inverno.io)
+* [Java OpenJDK 21](https://openjdk.java.net/)
+* [Inverno 1.12.0](https://inverno.io)
+* [DSL-JSON 2.0.2](https://github.com/ngs-doo/dsl-json)
 
 ## Test URLs
+
 ### JSON
 
 http://localhost:8080/json

+ 1 - 2
frameworks/Java/inverno/inverno-postgres.dockerfile

@@ -1,8 +1,7 @@
-FROM maven:3.9.6-amazoncorretto-21 as maven
+FROM maven:3.9.9-eclipse-temurin-21 as maven
 WORKDIR /inverno
 COPY src src
 COPY pom.xml pom.xml
-RUN yum -y install binutils
 RUN mvn package -q -Pio.inverno.io_uring
 
 EXPOSE 8080

+ 1 - 2
frameworks/Java/inverno/inverno.dockerfile

@@ -1,8 +1,7 @@
-FROM maven:3.9.6-amazoncorretto-21 as maven
+FROM maven:3.9.9-eclipse-temurin-21 as maven
 WORKDIR /inverno
 COPY src src
 COPY pom.xml pom.xml
-RUN yum -y install binutils
 RUN mvn package -q -Pio.inverno.io_uring
 
 EXPOSE 8080

+ 54 - 10
frameworks/Java/inverno/pom.xml

@@ -6,7 +6,7 @@
 	<parent>
 		<groupId>io.inverno.dist</groupId>
 		<artifactId>inverno-parent</artifactId>
-		<version>1.10.0</version>
+		<version>1.12.0</version>
 	</parent>
 	<groupId>com.techempower</groupId>
 	<artifactId>inverno-benchmark</artifactId>
@@ -53,6 +53,11 @@
 			<artifactId>unbescape</artifactId>
 			<version>1.1.6.RELEASE</version>
 		</dependency>
+		<dependency>
+			<groupId>com.dslplatform</groupId>
+			<artifactId>dsl-json</artifactId>
+			<version>2.0.2</version>
+		</dependency>
 		<dependency>
 			<groupId>io.vertx</groupId>
 			<artifactId>vertx-pg-client</artifactId>
@@ -82,7 +87,52 @@
 			<artifactId>log4j-core</artifactId>
 		</dependency>
 	</dependencies>
-	
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>default-compile</id>
+						<configuration>
+							<annotationProcessorPaths combine.children="append">
+								<path>
+									<groupId>com.dslplatform</groupId>
+									<artifactId>dsl-json</artifactId>
+									<version>2.0.2</version>
+								</path>
+							</annotationProcessorPaths>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+
+		<pluginManagement>
+			<plugins>
+				<plugin>
+					<groupId>io.inverno.tool</groupId>
+					<artifactId>inverno-maven-plugin</artifactId>
+					<configuration>
+						<moduleOverrides combine.children="append">
+							<module>
+								<name>com.dslplatform.dsl.json</name>
+								<uses>
+									<use>
+										<type>com.dslplatform.json.Configuration</type>
+									</use>
+								</uses>
+							</module>
+						</moduleOverrides>
+						<vmOptions>-Dlog4j2.simplelogLevel=INFO -Dlog4j2.level=INFO --add-reads com.techempower.inverno.benchmark=com.dslplatform.dsl.json --add-opens com.techempower.inverno.benchmark/com.techempower.inverno.benchmark.model=com.dslplatform.dsl.json</vmOptions>
+					</configuration>
+				</plugin>
+			</plugins>
+		</pluginManagement>
+	</build>
+
 	<profiles>
 		<profile>
 			<id>io.inverno.epoll</id>
@@ -110,7 +160,7 @@
 									<launchers>
 										<launcher>
 											<name>inverno-benchmark</name>
-											<vmOptions>-Xms2g -Xmx2g -server -XX:+UseNUMA -XX:+UseParallelGC -Dlog4j2.level=OFF -Dio.netty.leakDetection.level=disabled -Dio.netty.buffer.checkBounds=false -Dio.netty.buffer.checkAccessible=false -Dvertx.disableHttpHeadersValidation=true -Dvertx.disableMetrics=true -Dvertx.disableH2c=true -Dvertx.disableWebsockets=true -Dvertx.flashPolicyHandler=false -Dvertx.threadChecks=false -Dvertx.disableContextTimings=true -Dvertx.disableTCCL=true --add-modules io.netty.transport.unix.common,io.netty.transport.classes.epoll,io.netty.transport.epoll.linux.x86_64</vmOptions>
+											<vmOptions>-Xms2g -Xmx2g -server -XX:+UseNUMA -XX:+UseParallelGC -Dlog4j2.level=OFF -Dio.netty.leakDetection.level=disabled -Dio.netty.buffer.checkBounds=false -Dio.netty.buffer.checkAccessible=false -Dvertx.disableHttpHeadersValidation=true -Dvertx.disableMetrics=true -Dvertx.disableH2c=true -Dvertx.disableWebsockets=true -Dvertx.flashPolicyHandler=false -Dvertx.threadChecks=false -Dvertx.disableContextTimings=true -Dvertx.disableTCCL=true --add-modules io.netty.transport.unix.common,io.netty.transport.classes.epoll,io.netty.transport.epoll.linux.x86_64 --add-reads com.techempower.inverno.benchmark=com.dslplatform.dsl.json --add-opens com.techempower.inverno.benchmark/com.techempower.inverno.benchmark.model=com.dslplatform.dsl.json</vmOptions>
 										</launcher>
 									</launchers>
 									<archiveFormats>
@@ -119,9 +169,6 @@
 								</configuration>
 							</execution>
 						</executions>
-						<configuration>
-							<vmOptions>--add-modules io.netty.transport.unix.common,io.netty.transport.classes.epoll,io.netty.transport.epoll.linux.x86_64</vmOptions>
-						</configuration>
 					</plugin>
 				</plugins>
 			</build>
@@ -157,7 +204,7 @@
 									<launchers>
 										<launcher>
 											<name>inverno-benchmark</name>
-											<vmOptions>-Xms2g -Xmx2g -server -XX:+UseNUMA -XX:+UseParallelGC -Dlog4j2.level=OFF -Dio.netty.leakDetection.level=disabled -Dio.netty.buffer.checkBounds=false -Dio.netty.buffer.checkAccessible=false -Dvertx.disableHttpHeadersValidation=true -Dvertx.disableMetrics=true -Dvertx.disableH2c=true -Dvertx.disableWebsockets=true -Dvertx.flashPolicyHandler=false -Dvertx.threadChecks=false -Dvertx.disableContextTimings=true -Dvertx.disableTCCL=true --add-modules io.netty.transport.unix.common,io.netty.incubator.transport.classes.io_uring,io.netty.incubator.transport.io_uring.linux.x86_64</vmOptions>
+											<vmOptions>-Xms2g -Xmx2g -server -XX:+UseNUMA -XX:+UseParallelGC -Dlog4j2.level=OFF -Dio.netty.leakDetection.level=disabled -Dio.netty.buffer.checkBounds=false -Dio.netty.buffer.checkAccessible=false -Dvertx.disableHttpHeadersValidation=true -Dvertx.disableMetrics=true -Dvertx.disableH2c=true -Dvertx.disableWebsockets=true -Dvertx.flashPolicyHandler=false -Dvertx.threadChecks=false -Dvertx.disableContextTimings=true -Dvertx.disableTCCL=true --add-modules io.netty.transport.unix.common,io.netty.incubator.transport.classes.io_uring,io.netty.incubator.transport.io_uring.linux.x86_64 --add-reads com.techempower.inverno.benchmark=com.dslplatform.dsl.json --add-opens com.techempower.inverno.benchmark/com.techempower.inverno.benchmark.model=com.dslplatform.dsl.json</vmOptions>
 										</launcher>
 									</launchers>
 									<archiveFormats>
@@ -166,9 +213,6 @@
 								</configuration>
 							</execution>
 						</executions>
-						<configuration>
-							<vmOptions>--add-modules io.netty.transport.unix.common,io.netty.incubator.transport.classes.io_uring,io.netty.incubator.transport.io_uring.linux.x86_64</vmOptions>
-						</configuration>
 					</plugin>
 				</plugins>
 			</build>

+ 3 - 4
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/Main.java

@@ -1,17 +1,16 @@
 package com.techempower.inverno.benchmark;
 
-import java.io.IOException;
-import java.util.function.Supplier;
-
 import io.inverno.core.annotation.Bean;
 import io.inverno.core.v1.Application;
 import io.inverno.mod.configuration.ConfigurationSource;
 import io.inverno.mod.configuration.source.BootstrapConfigurationSource;
+import java.io.IOException;
+import java.util.function.Supplier;
 
 public class Main {
 
 	@Bean
-	public interface AppConfigurationSource extends Supplier<ConfigurationSource<?, ?, ?>> {}
+	public interface AppConfigurationSource extends Supplier<ConfigurationSource> {}
 	
 	public static void main(String[] args) throws IllegalStateException, IOException {
 		Application.with(new Benchmark.Builder()

+ 54 - 106
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/Controller.java

@@ -1,7 +1,5 @@
 package com.techempower.inverno.benchmark.internal;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.techempower.inverno.benchmark.model.Fortune;
 import com.techempower.inverno.benchmark.model.Message;
 import com.techempower.inverno.benchmark.model.World;
@@ -14,15 +12,14 @@ import io.inverno.mod.base.Charsets;
 import io.inverno.mod.base.concurrent.Reactor;
 import io.inverno.mod.base.concurrent.ReactorScope;
 import io.inverno.mod.base.converter.ConverterException;
+import io.inverno.mod.base.reflect.Types;
 import io.inverno.mod.http.base.ExchangeContext;
 import io.inverno.mod.http.base.HttpException;
-import io.inverno.mod.http.base.InternalServerErrorException;
 import io.inverno.mod.http.base.Parameter;
 import io.inverno.mod.http.base.Status;
-import io.inverno.mod.http.server.Exchange;
 import io.inverno.mod.http.server.ErrorExchange;
+import io.inverno.mod.http.server.Exchange;
 import io.inverno.mod.http.server.ServerController;
-import io.inverno.mod.sql.SqlClient;
 import io.inverno.mod.sql.UnsafeSqlOperations;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
@@ -30,13 +27,14 @@ import io.netty.channel.EventLoopGroup;
 import io.netty.handler.codec.http.HttpHeaderNames;
 import io.netty.handler.codec.http.HttpHeaderValues;
 import io.netty.util.AsciiString;
+import java.lang.reflect.Type;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
@@ -51,36 +49,32 @@ public class Controller implements ServerController<ExchangeContext, Exchange<Ex
 	private static final String PATH_FORTUNES = "/fortunes";
 	
 	public static final String DB_SELECT_WORLD = "SELECT id, randomnumber from WORLD where id = $1";
-	public static final String DB_UPDATE_WORLD = "UPDATE world SET randomnumber=$1 WHERE id=$2";
-	public static final String DB_SELECT_FORTUNE = "SELECT id, message from FORTUNE";
-	
+
 	private static final CharSequence STATIC_SERVER = AsciiString.cached("inverno");
 
+	private static final Type LIST_WORLD_TYPE = Types.type(List.class).type(World.class).and().build();
+
 	private final Reactor reactor;
-	private final ObjectMapper mapper;
-	private final ReactorScope<Mono<SqlClient>> sqlClient;
+	private final ReactorScope<JsonSerializer> jsonSerializer;
+	private final ReactorScope<Mono<DbRepository>> dbRepository;
 	
 	private EventLoopGroup dateEventLoopGroup;
 	
 	private CharSequence date;
 	
 	public Controller(Reactor reactor, 
-			ObjectMapper mapper,
-			ReactorScope<Mono<SqlClient>> sqlClient
+			ReactorScope<JsonSerializer> jsonSerializer,
+			ReactorScope<Mono<DbRepository>> dbRepository
 		) {
 		this.reactor = reactor;
-		this.mapper = mapper;
-		this.sqlClient = sqlClient;
+		this.jsonSerializer = jsonSerializer;
+		this.dbRepository = dbRepository;
 	}
 	
 	@Init
 	public void init() {
 		this.dateEventLoopGroup = this.reactor.createIoEventLoopGroup(1);
-		this.dateEventLoopGroup.scheduleAtFixedRate(() -> {
-			this.date = new AsciiString(DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now()));
-		}, 0, 1000, TimeUnit.MILLISECONDS);
-		
-		
+		this.dateEventLoopGroup.scheduleAtFixedRate(() -> this.date = new AsciiString(DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now())), 0, 1000, TimeUnit.MILLISECONDS);
 	}
 	
 	@Destroy
@@ -139,15 +133,8 @@ public class Controller implements ServerController<ExchangeContext, Exchange<Ex
 	}
 	
 	private static final CharSequence STATIC_PLAINTEXT_LEN_VALUE = AsciiString.cached(String.valueOf(STATIC_PLAINTEXT_LEN));
-	
-	private static class PlaintextSupplier implements Supplier<ByteBuf> {
-		@Override
-		public ByteBuf get() {
-			return STATIC_PLAINTEXT_BYTEBUF.duplicate();
-		}
-	}
-	
-	private static final Mono<ByteBuf> PLAIN_TEXT_MONO = Mono.fromSupplier(new PlaintextSupplier());
+
+	private static final Mono<ByteBuf> PLAIN_TEXT_MONO = Mono.fromSupplier(STATIC_PLAINTEXT_BYTEBUF::duplicate);
 	
 	public void handle_plaintext(Exchange<ExchangeContext> exchange) throws HttpException {
 		exchange.response()
@@ -163,20 +150,15 @@ public class Controller implements ServerController<ExchangeContext, Exchange<Ex
 	}
 	
 	public void handle_json(Exchange<ExchangeContext> exchange) throws HttpException {
-		try {
-			exchange.response()
-				.headers(h -> h
-					.add(HttpHeaderNames.SERVER, STATIC_SERVER)
-					.add(HttpHeaderNames.DATE, this.date)
-					.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
-				)
-				.body()
-					.raw()
-						.value(Unpooled.wrappedBuffer(this.mapper.writeValueAsBytes(new Message("Hello, World!"))));
-		} 
-		catch (JsonProcessingException | IllegalStateException e) {
-			throw new InternalServerErrorException("Error serializing message as JSON", e);
-		}
+		exchange.response()
+			.headers(h -> h
+				.add(HttpHeaderNames.SERVER, STATIC_SERVER)
+				.add(HttpHeaderNames.DATE, this.date)
+				.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
+			)
+			.body()
+			.raw()
+			.value(this.jsonSerializer.get().serialize(new Message("Hello, World!"), Message.class));
 	}
 
 	private static int randomWorldId() {
@@ -191,20 +173,10 @@ public class Controller implements ServerController<ExchangeContext, Exchange<Ex
 				.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
 			)
 			.body()
-				.raw().stream(this.sqlClient.get().flatMap(client -> 
-					client.queryForObject(
-						DB_SELECT_WORLD, 
-						row -> {
-							try {
-								return Unpooled.wrappedBuffer(this.mapper.writeValueAsBytes(new World(row.getInteger(0), row.getInteger(1))));
-							} 
-							catch (JsonProcessingException e) {
-								throw new InternalServerErrorException(e);
-							}
-						}, 
-						randomWorldId()
-					)
-				));
+				.raw().stream(this.dbRepository.get()
+					.flatMap(repository -> repository.getWorld(randomWorldId()))
+					.map(world -> this.jsonSerializer.get().serialize(world, World.class))
+				);
 	}
 	
 	private static final String PARAMETER_QUERIES = "queries";
@@ -227,8 +199,8 @@ public class Controller implements ServerController<ExchangeContext, Exchange<Ex
 				.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
 			)
 			.body()
-				.raw().stream(this.sqlClient.get()
-					.flatMapMany(client -> ((UnsafeSqlOperations)client)
+				.raw().stream(this.dbRepository.get()
+					.flatMapMany(repository -> ((UnsafeSqlOperations)repository.getSqlClient())
 						.batchQueries(ops -> 
 							Flux.range(0, queries)
 								.map(ign -> ops.queryForObject(
@@ -239,58 +211,39 @@ public class Controller implements ServerController<ExchangeContext, Exchange<Ex
 						)
 					)
 					.collectList()
-					.map(worlds -> {
-						try {
-							return Unpooled.wrappedBuffer(this.mapper.writeValueAsBytes(worlds));
-						} 
-						catch (JsonProcessingException e) {
-							throw new InternalServerErrorException(e);
-						}
-					})
+					.map(worlds -> this.jsonSerializer.get().serialize(worlds, LIST_WORLD_TYPE))
 				);
 	}
 	
 	public void handle_updates(Exchange<ExchangeContext> exchange) throws HttpException {
 		int queries = this.extractQueriesParameter(exchange);
-		
 		exchange.response()
-		.headers(h -> h
-			.add(HttpHeaderNames.SERVER, STATIC_SERVER)
-			.add(HttpHeaderNames.DATE, this.date)
-			.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
-		)
-		.body()
-			.raw().stream(this.sqlClient.get()
-				.flatMapMany(client -> Flux.from(((UnsafeSqlOperations)client)
-					.batchQueries(ops -> 
-						Flux.range(0, queries)
-							.map(ign -> ops.queryForObject(
-								DB_SELECT_WORLD, 
-								row -> new World(row.getInteger(0), randomWorldId()), 
-								randomWorldId()
-							))
-					))
-					.collectSortedList()
-					.delayUntil(worlds -> client.batchUpdate(
-							DB_UPDATE_WORLD, 
-							worlds.stream().map(world -> new Object[] { world.getRandomNumber(), world.getId() })
-						)
+			.headers(h -> h
+				.add(HttpHeaderNames.SERVER, STATIC_SERVER)
+				.add(HttpHeaderNames.DATE, this.date)
+				.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
+			)
+			.body()
+				.raw().stream(this.dbRepository.get()
+					.flatMapMany(repository -> Flux.from(((UnsafeSqlOperations)repository.getSqlClient())
+						.batchQueries(ops ->
+							Flux.range(0, queries)
+								.map(ign -> ops.queryForObject(
+									DB_SELECT_WORLD,
+									row -> new World(row.getInteger(0), randomWorldId()),
+									randomWorldId()
+								))
+						))
+						.collectSortedList()
+						.delayUntil(repository::updateWorlds)
+						.map(worlds -> this.jsonSerializer.get().serialize(worlds, LIST_WORLD_TYPE))
 					)
-					.map(worlds -> {
-						try {
-							return Unpooled.wrappedBuffer(this.mapper.writeValueAsBytes(worlds));
-						} 
-						catch (JsonProcessingException e) {
-							throw new InternalServerErrorException(e);
-						}
-					})
-				)
-			);
+				);
 	}
 	
 	private static final CharSequence MEDIA_TEXT_HTML_UTF8 = AsciiString.cached("text/html; charset=utf-8");
 	
-	private static final FortunesTemplate.Renderer<CompletableFuture<ByteBuf>> FORTUNES_RENDERER = FortunesTemplate.bytebuf(() -> Unpooled.buffer());
+	private static final FortunesTemplate.Renderer<CompletableFuture<ByteBuf>> FORTUNES_RENDERER = FortunesTemplate.bytebuf(Unpooled::buffer);
 	
 	public void handle_fortunes(Exchange<ExchangeContext> exchange) throws HttpException {
 		exchange.response()
@@ -300,12 +253,7 @@ public class Controller implements ServerController<ExchangeContext, Exchange<Ex
 				.add(HttpHeaderNames.CONTENT_TYPE, MEDIA_TEXT_HTML_UTF8)
 			)
 			.body()
-				.raw().stream(this.sqlClient.get().flatMapMany(client -> 
-					client.query(
-							DB_SELECT_FORTUNE, 
-							row -> new Fortune(row.getInteger(0), row.getString(1))
-						)
-					)
+				.raw().stream(this.dbRepository.get().flatMapMany(DbRepository::listFortunes)
 					.collectList()
 					.flatMap(fortunes -> {
 						fortunes.add(new Fortune(0, "Additional fortune added at request time."));

+ 132 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/DbRepository.java

@@ -0,0 +1,132 @@
+package com.techempower.inverno.benchmark.internal;
+
+import com.techempower.inverno.benchmark.AppConfiguration;
+import com.techempower.inverno.benchmark.model.Fortune;
+import com.techempower.inverno.benchmark.model.World;
+import io.inverno.core.annotation.Bean;
+import io.inverno.core.annotation.Destroy;
+import io.inverno.core.annotation.Init;
+import io.inverno.mod.base.concurrent.Reactor;
+import io.inverno.mod.base.concurrent.VertxReactor;
+import io.inverno.mod.sql.PreparedStatement;
+import io.inverno.mod.sql.SqlClient;
+import io.inverno.mod.sql.vertx.ConnectionSqlClient;
+import io.vertx.core.Vertx;
+import io.vertx.core.VertxOptions;
+import io.vertx.pgclient.PgConnectOptions;
+import io.vertx.pgclient.PgConnection;
+import java.util.ArrayList;
+import java.util.List;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+public class DbRepository {
+
+	public static final String DB_SELECT_WORLD = "SELECT id, randomnumber from WORLD where id = $1";
+	public static final String DB_SELECT_FORTUNE = "SELECT id, message from FORTUNE";
+
+	private final SqlClient sqlClient;
+
+	private final PreparedStatement selectWorldByIdQuery;
+	private final PreparedStatement selectFortuneQuery;
+	private final PreparedStatement[] updateWorldQueries;
+
+	public DbRepository(SqlClient sqlClient) {
+		this.sqlClient = sqlClient;
+
+		this.selectWorldByIdQuery = sqlClient.preparedStatement(DB_SELECT_WORLD);
+		this.selectFortuneQuery = sqlClient.preparedStatement(DB_SELECT_FORTUNE);
+		this.updateWorldQueries = new PreparedStatement[500];
+		for(int i=0;i<this.updateWorldQueries.length;i++) {
+			updateWorldQueries[i] = sqlClient.preparedStatement(buildAggregatedUpdateQuery(i + 1));
+		}
+	}
+
+	private static String buildAggregatedUpdateQuery(int len) {
+		StringBuilder sql = new StringBuilder();
+		sql.append("UPDATE WORLD SET RANDOMNUMBER = CASE ID");
+		for (int i = 0; i < len; i++) {
+			int offset = (i * 2) + 1;
+			sql.append(" WHEN $").append(offset).append(" THEN $").append(offset + 1);
+		}
+		sql.append(" ELSE RANDOMNUMBER");
+		sql.append(" END WHERE ID IN ($1");
+		for (int i = 1; i < len; i++) {
+			int offset = (i * 2) + 1;
+			sql.append(",$").append(offset);
+		}
+		sql.append(")");
+		return sql.toString();
+	}
+
+	public SqlClient getSqlClient() {
+		return sqlClient;
+	}
+
+	public Mono<World> getWorld(int id) {
+		return Mono.from(this.selectWorldByIdQuery.bind(id).execute(row -> new World(row.getInteger(0), row.getInteger(1))));
+	}
+
+	public Flux<Fortune> listFortunes() {
+		return Flux.from(this.selectFortuneQuery.execute(row -> new Fortune(row.getInteger(0), row.getString(1))));
+	}
+
+	public Mono<Void> updateWorlds(List<World> worlds) {
+		int len = worlds.size();
+		List<Object> parameters = new ArrayList<>(len * 2);
+		for(World world : worlds) {
+			parameters.add(world.getId());
+			parameters.add(world.getRandomNumber());
+		}
+		return Mono.when(this.updateWorldQueries[len - 1].bind(parameters).execute());
+	}
+
+	@Bean( name = "dbRespository", visibility = Bean.Visibility.PRIVATE )
+	public static class ReactorScope extends io.inverno.mod.base.concurrent.ReactorScope<Mono<DbRepository>> {
+
+		private final AppConfiguration configuration;
+		private final Reactor reactor;
+
+		private Vertx vertx;
+		private PgConnectOptions connectOptions;
+
+		public ReactorScope(AppConfiguration configuration, Reactor reactor) {
+			this.configuration = configuration;
+			this.reactor = reactor;
+		}
+
+		@Init
+		public void init() {
+			if(this.reactor instanceof VertxReactor) {
+				this.vertx = ((VertxReactor)this.reactor).getVertx();
+			}
+			else {
+				this.vertx = Vertx.vertx(new VertxOptions().setPreferNativeTransport(this.configuration.boot().prefer_native_transport()));
+			}
+
+			this.connectOptions = new PgConnectOptions()
+				.setHost(this.configuration.db_host())
+				.setPort(this.configuration.db_port())
+				.setDatabase(this.configuration.db_database())
+				.setUser(this.configuration.db_username())
+				.setPassword(this.configuration.db_password())
+				.setCachePreparedStatements(true)
+				.setPreparedStatementCacheMaxSize(1024)
+				.setPipeliningLimit(100_100);
+		}
+
+		@Destroy
+		public void destroy() {
+			if(!(this.reactor instanceof VertxReactor)) {
+				this.vertx.close();
+			}
+		}
+
+		@Override
+		protected Mono<DbRepository> create() {
+			return Mono.fromCompletionStage(() -> PgConnection.connect(this.vertx, this.connectOptions).toCompletionStage())
+				.map(pgConn -> new DbRepository(new ConnectionSqlClient(pgConn)))
+				.cacheInvalidateWhen(repository -> ((ConnectionSqlClient)repository.getSqlClient()).onClose());
+		}
+	}
+}

+ 38 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/JsonSerializer.java

@@ -0,0 +1,38 @@
+package com.techempower.inverno.benchmark.internal;
+
+import com.dslplatform.json.DslJson;
+import com.dslplatform.json.JsonWriter;
+import io.inverno.core.annotation.Bean;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.lang.reflect.Type;
+
+public class JsonSerializer {
+
+	private static final DslJson<Object> DSL_JSON = new DslJson<>();
+
+	private final JsonWriter jsonWriter;
+
+	public JsonSerializer() {
+		this.jsonWriter = DSL_JSON.newWriter();
+	}
+
+	public <T> ByteBuf serialize(T value, Type type) {
+		try {
+			DSL_JSON.serialize(this.jsonWriter, type, value);
+			return Unpooled.wrappedBuffer(this.jsonWriter.toByteArray());
+		}
+		finally {
+			this.jsonWriter.reset();
+		}
+	}
+
+	@Bean( name = "jsonSerializer", visibility = Bean.Visibility.PRIVATE )
+	public static class ReactorScope extends io.inverno.mod.base.concurrent.ReactorScope<JsonSerializer> {
+
+		@Override
+		protected JsonSerializer create() {
+			return new JsonSerializer();
+		}
+	}
+}

+ 0 - 66
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/SqlClientReactorScope.java

@@ -1,66 +0,0 @@
-package com.techempower.inverno.benchmark.internal;
-
-import com.techempower.inverno.benchmark.AppConfiguration;
-
-import io.inverno.core.annotation.Bean;
-import io.inverno.core.annotation.Bean.Visibility;
-import io.inverno.core.annotation.Destroy;
-import io.inverno.core.annotation.Init;
-import io.inverno.mod.base.concurrent.Reactor;
-import io.inverno.mod.base.concurrent.ReactorScope;
-import io.inverno.mod.base.concurrent.VertxReactor;
-import io.inverno.mod.sql.SqlClient;
-import io.inverno.mod.sql.vertx.ConnectionSqlClient;
-import io.vertx.core.Vertx;
-import io.vertx.core.VertxOptions;
-import io.vertx.pgclient.PgConnectOptions;
-import io.vertx.pgclient.PgConnection;
-import reactor.core.publisher.Mono;
-
-@Bean( name = "SqlClient", visibility = Visibility.PRIVATE )
-public class SqlClientReactorScope extends ReactorScope<Mono<SqlClient>> {
-
-	private final AppConfiguration configuration;
-	private final Reactor reactor;
-	
-	private Vertx vertx;
-	private PgConnectOptions connectOptions;
-	
-	public SqlClientReactorScope(AppConfiguration configuration, Reactor reactor) {
-		this.configuration = configuration;
-		this.reactor = reactor;
-	}
-	
-	@Init
-	public void init() {
-		if(this.reactor instanceof VertxReactor) {
-			this.vertx = ((VertxReactor)this.reactor).getVertx();			
-		}
-		else {
-			this.vertx = Vertx.vertx(new VertxOptions().setPreferNativeTransport(this.configuration.boot().prefer_native_transport()));
-		}
-		
-		this.connectOptions = new PgConnectOptions()
-				.setHost(this.configuration.db_host())
-				.setPort(this.configuration.db_port())
-				.setDatabase(this.configuration.db_database())
-				.setUser(this.configuration.db_username())
-				.setPassword(this.configuration.db_password())
-				.setCachePreparedStatements(true)
-				.setPipeliningLimit(100_100);
-	}
-	
-	@Destroy
-	public void destroy() {
-		if(!(this.reactor instanceof VertxReactor)) {
-			this.vertx.close();			
-		}
-	}
-	
-	@Override
-	protected Mono<SqlClient> create() {
-		return Mono.fromCompletionStage(() -> PgConnection.connect(this.vertx, this.connectOptions).toCompletionStage())
-			.map(pgConn -> (SqlClient)new ConnectionSqlClient(pgConn))
-			.cacheInvalidateWhen(client -> ((ConnectionSqlClient)client).onClose());
-	}
-}

+ 3 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/Fortune.java

@@ -1,5 +1,8 @@
 package com.techempower.inverno.benchmark.model;
 
+import com.dslplatform.json.CompiledJson;
+
+@CompiledJson
 public final class Fortune implements Comparable<Fortune> {
 
 	private final int id;

+ 3 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/Message.java

@@ -1,5 +1,8 @@
 package com.techempower.inverno.benchmark.model;
 
+import com.dslplatform.json.CompiledJson;
+
+@CompiledJson
 public final class Message {
 
 	private final String message;

+ 6 - 5
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/World.java

@@ -1,14 +1,15 @@
 package com.techempower.inverno.benchmark.model;
 
+import com.dslplatform.json.CompiledJson;
+
+@CompiledJson
 public final class World implements Comparable<World> {
 
-	private final int id;
+	private int id;
 	private int randomNumber;
 
-	public World(int id) {
-		this.id = id;
-	}
-	
+	public World() {}
+
 	public World(int id, int randomNumber) {
 		this.id = id;
 		this.randomNumber = randomNumber;

+ 3 - 6
frameworks/Java/inverno/src/main/java/module-info.java

@@ -10,16 +10,13 @@ module com.techempower.inverno.benchmark {
 	requires io.netty.common;
 	requires io.netty.codec.http;
 	requires unbescape;
-	
+	requires static dsl.json;
+
 	requires io.vertx.client.sql.pg;
 	requires io.vertx.client.sql;
 	requires io.vertx.core;
 	requires java.sql;
-	
-	//requires transitive io.netty.transport;
-	//requires static io.netty.transport.unix.common;
-	//requires static io.netty.transport.epoll;
-	
+
 	exports com.techempower.inverno.benchmark;
 	exports com.techempower.inverno.benchmark.model;
 }