diff --git a/frameworks/Kotlin/ktor/benchmark_config.json b/frameworks/Kotlin/ktor/benchmark_config.json index 3a213a240ba..5a9b1816736 100644 --- a/frameworks/Kotlin/ktor/benchmark_config.json +++ b/frameworks/Kotlin/ktor/benchmark_config.json @@ -25,6 +25,29 @@ "notes": "http://ktor.io/", "versus": "netty" }, + "r2dbc": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "db_url": "/db", + "query_url": "/queries?queries=", + "update_url": "/updates?queries=", + "fortune_url": "/fortunes", + + "port": 9090, + "approach": "Realistic", + "classification": "Micro", + "database": "Postgres", + "framework": "ktor", + "language": "Kotlin", + "orm": "Raw", + "platform": "Netty", + "webserver": "None", + "os": "Linux", + "database_os": "Linux", + "display_name": "ktor-netty-r2dbc", + "notes": "http://ktor.io/", + "versus": "netty" + }, "jetty": { "plaintext_url": "/plaintext", "json_url": "/json", diff --git a/frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts index 61cedf013ce..029a0973171 100644 --- a/frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts +++ b/frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts @@ -1,6 +1,6 @@ plugins { application - kotlin("jvm") version "1.9.22" + kotlin("jvm") version "2.0.21" kotlin("plugin.serialization") version "2.0.0" id("com.github.johnrengelman.shadow") version "8.1.0" } @@ -17,8 +17,7 @@ application { } val ktor_version = "2.3.12" -val kotlinx_serialization_version = "1.6.3" -val vertx_pg_client = "4.5.8" +val kotlinx_serialization_version = "1.7.3" dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinx_serialization_version") @@ -26,11 +25,17 @@ dependencies { implementation("io.ktor:ktor-server-netty:$ktor_version") implementation("io.ktor:ktor-server-default-headers:$ktor_version") implementation("io.ktor:ktor-server-html-builder:$ktor_version") - implementation("com.github.jasync-sql:jasync-postgresql:2.2.0") + implementation("com.github.jasync-sql:jasync-postgresql:2.2.4") +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } tasks.shadowJar { - archiveBaseName.set("bench") + archiveBaseName.set("ktor-asyncdb") archiveClassifier.set("") archiveVersion.set("") } diff --git a/frameworks/Kotlin/ktor/ktor-asyncdb/gradle.properties b/frameworks/Kotlin/ktor/ktor-asyncdb/gradle.properties index 5790d58ceda..9408f64fb4d 100644 --- a/frameworks/Kotlin/ktor/ktor-asyncdb/gradle.properties +++ b/frameworks/Kotlin/ktor/ktor-asyncdb/gradle.properties @@ -1,4 +1,4 @@ kotlin.code.style=official -kotlin_version=1.9.22 +kotlin_version=2.0.21 ktor_version=2.3.12 diff --git a/frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.properties b/frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.properties index 3d66c176054..c6a2952d3c1 100644 --- a/frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.properties +++ b/frameworks/Kotlin/ktor/ktor-asyncdb/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Dec 07 21:01:17 MST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip diff --git a/frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle b/frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle deleted file mode 100644 index d58b02872bf..00000000000 --- a/frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle +++ /dev/null @@ -1,14 +0,0 @@ -pluginManagement { - resolutionStrategy { - eachPlugin { - if (requested.id.id == "org.jetbrains.kotlin.jvm") { - useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") - } - if (requested.id.id == "kotlinx-serialization") { - useModule("org.jetbrains.kotlin:kotlin-serialization:$kotlin_version") - } - } - } -} - -rootProject.name = 'tech-empower-framework-benchmark' \ No newline at end of file diff --git a/frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle.kts b/frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle.kts new file mode 100644 index 00000000000..fd565dc83c5 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-asyncdb/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "tech-empower-framework-benchmark" \ No newline at end of file diff --git a/frameworks/Kotlin/ktor/ktor-cio.dockerfile b/frameworks/Kotlin/ktor/ktor-cio.dockerfile index 33059ec9fd4..7443aed952f 100644 --- a/frameworks/Kotlin/ktor/ktor-cio.dockerfile +++ b/frameworks/Kotlin/ktor/ktor-cio.dockerfile @@ -1,13 +1,13 @@ -FROM maven:3.9.7-amazoncorretto-17-debian as maven +FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven WORKDIR /ktor COPY ktor/pom.xml pom.xml COPY ktor/src src RUN mvn clean package -q -FROM amazoncorretto:17.0.11-al2023-headless +FROM amazoncorretto:21-al2023-headless WORKDIR /ktor COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-cio-bundle.jar app.jar EXPOSE 9090 -CMD ["java", "-jar", "app.jar"] +CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"] diff --git a/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts index 80a9debfa16..5d6db161d20 100644 --- a/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts +++ b/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts @@ -9,7 +9,7 @@ repositories { mavenCentral() } -val ktorVersion = "3.0.1" +val ktorVersion = "3.0.3" val kotlinxSerializationVersion = "1.7.3" val exposedVersion = "0.56.0" diff --git a/frameworks/Kotlin/ktor/ktor-exposed/gradle/wrapper/gradle-wrapper.properties b/frameworks/Kotlin/ktor/ktor-exposed/gradle/wrapper/gradle-wrapper.properties index bdc9a83b1e6..4eaec467050 100644 --- a/frameworks/Kotlin/ktor/ktor-exposed/gradle/wrapper/gradle-wrapper.properties +++ b/frameworks/Kotlin/ktor/ktor-exposed/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/frameworks/Kotlin/ktor/ktor-jasync.dockerfile b/frameworks/Kotlin/ktor/ktor-jasync.dockerfile index 8c66e81ea4e..be5f6dd4606 100644 --- a/frameworks/Kotlin/ktor/ktor-jasync.dockerfile +++ b/frameworks/Kotlin/ktor/ktor-jasync.dockerfile @@ -1,13 +1,15 @@ -FROM maven:3.9.7-amazoncorretto-17-debian +FROM gradle:jdk21 as build WORKDIR /app COPY ktor-asyncdb/gradle gradle COPY ktor-asyncdb/build.gradle.kts build.gradle.kts -COPY ktor-asyncdb/gradle.properties gradle.properties COPY ktor-asyncdb/gradlew gradlew -COPY ktor-asyncdb/settings.gradle settings.gradle COPY ktor-asyncdb/src src RUN /app/gradlew --no-daemon shadowJar +FROM amazoncorretto:21-al2023-headless +WORKDIR /app +COPY --from=build /app/build/libs/ktor-asyncdb.jar ktor-asyncdb.jar + EXPOSE 9090 -CMD ["java", "-server", "-XX:+UseParallelGC", "-Xms2G","-Xmx2G", "-jar", "/app/build/libs/bench.jar", "jasync-sql"] +CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "ktor-asyncdb.jar", "jasync-sql"] diff --git a/frameworks/Kotlin/ktor/ktor-jetty.dockerfile b/frameworks/Kotlin/ktor/ktor-jetty.dockerfile index e753d7cc442..52855ab4a55 100644 --- a/frameworks/Kotlin/ktor/ktor-jetty.dockerfile +++ b/frameworks/Kotlin/ktor/ktor-jetty.dockerfile @@ -1,13 +1,13 @@ -FROM maven:3.9.7-amazoncorretto-17-debian as maven +FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven WORKDIR /ktor COPY ktor/pom.xml pom.xml COPY ktor/src src RUN mvn clean package -q -FROM amazoncorretto:17.0.11-al2023-headless +FROM amazoncorretto:21-al2023-headless WORKDIR /ktor COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-jetty-bundle.jar app.jar EXPOSE 9090 -CMD ["java", "-jar", "app.jar"] +CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"] diff --git a/frameworks/Kotlin/ktor/ktor-pgclient.dockerfile b/frameworks/Kotlin/ktor/ktor-pgclient.dockerfile index 0cf012e7596..301f5e55c31 100644 --- a/frameworks/Kotlin/ktor/ktor-pgclient.dockerfile +++ b/frameworks/Kotlin/ktor/ktor-pgclient.dockerfile @@ -1,4 +1,4 @@ -FROM maven:3.9.7-amazoncorretto-17-debian as build +FROM gradle:jdk21 as build WORKDIR /app COPY ktor-pgclient/gradle gradle COPY ktor-pgclient/build.gradle.kts build.gradle.kts @@ -6,10 +6,10 @@ COPY ktor-pgclient/gradlew gradlew COPY ktor-pgclient/src src RUN /app/gradlew --no-daemon shadowJar -FROM amazoncorretto:17.0.11-al2023-headless +FROM amazoncorretto:21-al2023-headless WORKDIR /app COPY --from=build /app/build/libs/ktor-pgclient.jar ktor-pgclient.jar EXPOSE 8080 -CMD ["java", "-server", "-XX:MaxRAMFraction=1", "-XX:-UseBiasedLocking", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "ktor-pgclient.jar"] +CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "ktor-pgclient.jar"] diff --git a/frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts index f080b64d69f..60524844558 100644 --- a/frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts +++ b/frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts @@ -1,6 +1,6 @@ plugins { application - kotlin("jvm") version "1.9.22" + kotlin("jvm") version "2.0.21" kotlin("plugin.serialization") version "2.0.0" id("com.github.johnrengelman.shadow") version "8.1.0" } @@ -17,19 +17,22 @@ application { } val ktor_version = "2.3.12" +val vertx_version = "4.5.11" dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3") implementation("io.ktor:ktor-server-netty:$ktor_version") implementation("io.ktor:ktor-server-html-builder-jvm:$ktor_version") implementation("io.ktor:ktor-server-default-headers-jvm:$ktor_version") - implementation("io.vertx:vertx-pg-client:4.5.8") - implementation("io.vertx:vertx-lang-kotlin:4.5.8") - implementation("io.vertx:vertx-lang-kotlin-coroutines:4.5.8") + implementation("io.vertx:vertx-pg-client:$vertx_version") + implementation("io.vertx:vertx-lang-kotlin:$vertx_version") + implementation("io.vertx:vertx-lang-kotlin-coroutines:$vertx_version") } -tasks.withType().configureEach { - kotlinOptions.jvmTarget = "17" +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } tasks.shadowJar { diff --git a/frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties b/frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties index e1bef7e873c..81aa1c0448a 100644 --- a/frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties +++ b/frameworks/Kotlin/ktor/ktor-pgclient/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile b/frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile new file mode 100644 index 00000000000..9281f4974ac --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile @@ -0,0 +1,13 @@ +FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven +WORKDIR /ktor-r2dbc +COPY ktor-r2dbc/pom.xml pom.xml +COPY ktor-r2dbc/src src +RUN mvn clean package -q + +FROM amazoncorretto:21-al2023-headless +WORKDIR /ktor-r2dbc +COPY --from=maven /ktor-r2dbc/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-netty-bundle.jar app.jar + +EXPOSE 9090 + +CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"] diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/README.md b/frameworks/Kotlin/ktor/ktor-r2dbc/README.md new file mode 100644 index 00000000000..5b758d68dbd --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/README.md @@ -0,0 +1,50 @@ +# Ktor + +Ktor is a framework for building servers and clients in connected systems using Kotlin programming language. +More information is available at [ktor.io](http://ktor.io). + +# Setup + +* Java 21 +* Postgres server + +# Requirements + +* Maven 3 +* JDK 21 +* Kotlin +* ktor +* netty +* R2DBC + +Maven is downloaded automatically via Maven Wrapper script (`mvnw`), add dependencies are specified in `pom.xml` so will be downloaded automatically from maven central and jcenter repositories. + +# Deployment + +Run maven to build a bundle + +```bash +./mvnw package +``` + +Once bundle build complete and mysql server is running you can launch the application + +```bash +java -jar target/tech-empower-framework-benchmark-1.0-SNAPSHOT.jar +``` + +Please note that the server holds tty so you may need nohup. See `setup.sh` for details. + +# Contact + +[Leonid Stashevsky](https://github.com/e5l) + +[Sergey Mashkov](https://github.com/cy6erGn0m) + +[Ilya Ryzhenkov](https://github.com/orangy) + +[Ilya Nemtsev](https://github.com/inemtsev) + +Slack ktor channel https://kotlinlang.slack.com/messages/ktor (you need an [invite](http://slack.kotlinlang.org/) to join) + + diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml new file mode 100644 index 00000000000..092e8ab9559 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml @@ -0,0 +1,174 @@ + + + + 4.0.0 + + org.jetbrains.ktor + tech-empower-framework-benchmark + 1.0-SNAPSHOT + jar + + org.jetbrains.ktor tech-empower-framework-benchmark + + + 2.0.21 + 1.10.1 + 3.0.3 + 1.7.3 + 0.11.0 + UTF-8 + 1.5.12 + 3.7.1 + 42.7.4 + 1.0.7.RELEASE + 1.0.2.RELEASE + + + + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + + + org.jetbrains.kotlinx + kotlinx-serialization-core + ${serialization.version} + + + org.jetbrains.kotlinx + kotlinx-serialization-json + ${serialization.version} + + + org.jetbrains.kotlinx + kotlinx-html-jvm + ${kotlinx.html.version} + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + ${kotlin.coroutines.version} + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + ${kotlin.coroutines.version} + + + io.ktor + ktor-server-default-headers-jvm + ${ktor.version} + + + io.ktor + ktor-server-html-builder-jvm + ${ktor.version} + + + org.postgresql + r2dbc-postgresql + ${r2dbc.version} + + + io.r2dbc + r2dbc-pool + ${r2dbc.pool.version} + + + io.projectreactor + reactor-core + ${reactor.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + io.ktor + ktor-server-netty-jvm + ${ktor.version} + + + + + src/main/kotlin + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + kotlinx-serialization + + + + + org.jetbrains.kotlin + kotlin-maven-serialization + ${kotlin.version} + + + + + maven-jar-plugin + + true + + + + default-jar + none + + + + + maven-assembly-plugin + 3.0.0 + + + + netty + + single + + + package + + + + src/main/assembly/netty-bundle.xml + + + + io.ktor.server.netty.EngineMain + + + + + + + + + diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/assembly/netty-bundle.xml b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/assembly/netty-bundle.xml new file mode 100644 index 00000000000..58ff7cff593 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/assembly/netty-bundle.xml @@ -0,0 +1,29 @@ + + netty-bundle + + + jar + + + false + + + + true + runtime + + + *:ktor-server-jetty + *:ktor-server-cio + + + + + + + ${project.build.outputDirectory} + / + + + \ No newline at end of file diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt new file mode 100644 index 00000000000..461c6e303e8 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt @@ -0,0 +1,186 @@ +package org.jetbrains.ktor.benchmarks + +import io.ktor.http.* +import io.ktor.http.content.* +import io.ktor.server.application.* +import io.ktor.server.config.* +import io.ktor.server.html.* +import io.ktor.server.plugins.defaultheaders.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import io.r2dbc.pool.ConnectionPool +import io.r2dbc.pool.ConnectionPoolConfiguration +import io.r2dbc.postgresql.PostgresqlConnectionConfiguration +import io.r2dbc.postgresql.PostgresqlConnectionFactory +import io.r2dbc.postgresql.client.SSLMode +import io.r2dbc.spi.Connection +import io.r2dbc.spi.ConnectionFactory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.html.* +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.jetbrains.ktor.benchmarks.Constants.DB_ROWS +import org.jetbrains.ktor.benchmarks.Constants.FORTUNES_QUERY +import org.jetbrains.ktor.benchmarks.Constants.UPDATE_QUERY +import org.jetbrains.ktor.benchmarks.Constants.WORLD_QUERY +import org.jetbrains.ktor.benchmarks.models.Fortune +import org.jetbrains.ktor.benchmarks.models.Message +import org.jetbrains.ktor.benchmarks.models.World +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import kotlin.random.Random + +fun Application.main() { + val config = ApplicationConfig("application.conf") + val dbConnFactory = configurePostgresR2DBC(config) + + install(DefaultHeaders) + + val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain) + + routing { + get("/plaintext") { + call.respond(helloWorldContent) + } + + get("/json") { + call.respondText(Json.encodeToString(Message("Hello, world!")), ContentType.Application.Json) + } + + get("/db") { + val random = Random.Default + val request = getWorld(dbConnFactory, random) + val result = request.awaitFirstOrNull() + + call.respondText(Json.encodeToString(result), ContentType.Application.Json) + } + + fun selectWorlds(queries: Int, random: Random): Flow = flow { + repeat(queries) { + emit(getWorld(dbConnFactory, random).awaitFirst()) + } + } + + get("/queries") { + val queries = call.queries() + val random = Random.Default + + val result = buildList { + selectWorlds(queries, random).collect { + add(it) + } + } + + call.respondText(Json.encodeToString(result), ContentType.Application.Json) + } + + get("/fortunes") { + val result = mutableListOf() + + val request = Flux.usingWhen(dbConnFactory.create(), { connection -> + Flux.from(connection.createStatement(FORTUNES_QUERY).execute()).flatMap { r -> + Flux.from(r.map { row, _ -> + Fortune( + row.get(0, Int::class.java)!!, row.get(1, String::class.java)!! + ) + }) + } + }, { connection -> connection.close() }) + + request.collectList().awaitFirstOrNull()?.let { result.addAll(it) } + + result.add(Fortune(0, "Additional fortune added at request time.")) + result.sortBy { it.message } + call.respondHtml { + head { title { +"Fortunes" } } + body { + table { + tr { + th { +"id" } + th { +"message" } + } + for (fortune in result) { + tr { + td { +fortune.id.toString() } + td { +fortune.message } + } + } + } + } + } + } + + get("/updates") { + val queries = call.queries() + val random = Random.Default + + val worlds = selectWorlds(queries, random) + + val worldsUpdated = buildList { + worlds.collect { world -> + world.randomNumber = random.nextInt(DB_ROWS) + 1 + add(world) + + Mono.usingWhen(dbConnFactory.create(), { connection -> + Mono.from( + connection.createStatement(UPDATE_QUERY) + .bind(0, world.randomNumber) + .bind(1, world.id) + .execute() + ).flatMap { Mono.from(it.rowsUpdated) } + }, Connection::close).awaitFirstOrNull() + } + } + + call.respondText(Json.encodeToString(worldsUpdated), ContentType.Application.Json) + } + } +} + +private fun getWorld( + dbConnFactory: ConnectionFactory, random: Random +): Mono = Mono.usingWhen(dbConnFactory.create(), { connection -> + Mono.from(connection.createStatement(WORLD_QUERY).bind(0, random.nextInt(DB_ROWS) + 1).execute()).flatMap { r -> + Mono.from(r.map { row, _ -> + World( + row.get(0, Int::class.java)!!, row.get(1, Int::class.java)!! + ) + }) + } +}, Connection::close) + +private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory { + val cfo = PostgresqlConnectionConfiguration.builder() + .host(config.property("db.host").getString()) + .port(config.property("db.port").getString().toInt()) + .database(config.property("db.database").getString()) + .username(config.property("db.username").getString()) + .password(config.property("db.password").getString()) + .loopResources { NioClientEventLoopResources(Runtime.getRuntime().availableProcessors()).cacheLoops() } + .sslMode(SSLMode.DISABLE) + .tcpKeepAlive(true) + .tcpNoDelay(true) + .build() + + val cf = PostgresqlConnectionFactory(cfo) + + val cp = ConnectionPoolConfiguration.builder(cf) + .initialSize(config.property("db.initPoolSize").getString().toInt()) + .maxSize(config.property("db.maxPoolSize").getString().toInt()) + .build() + + return ConnectionPool(cp) +} + +private fun ApplicationCall.queries() = request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1 + + +object Constants { + const val WORLD_QUERY = "SELECT id, randomnumber FROM world WHERE id = $1" + const val FORTUNES_QUERY = "SELECT id, message FROM fortune" + const val UPDATE_QUERY = "UPDATE world SET randomnumber = $1 WHERE id = $2" + const val DB_ROWS = 10000 +} diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/NioClientEventLoopResources.java b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/NioClientEventLoopResources.java new file mode 100644 index 00000000000..d0e03bb6b5e --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/NioClientEventLoopResources.java @@ -0,0 +1,123 @@ +package org.jetbrains.ktor.benchmarks; + +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.ServerSocketChannel; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.ThreadPerTaskExecutor; +import reactor.core.publisher.Mono; +import reactor.netty.FutureMono; +import reactor.netty.resources.LoopResources; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Copied from GitHub issue comment: https://github.com/r2dbc/r2dbc-pool/issues/190#issuecomment-1566845190 + */ +public class NioClientEventLoopResources implements LoopResources { + public static final String THREAD_PREFIX = "prefix-"; + final int threads; + final AtomicReference loops = new AtomicReference<>(); + final AtomicBoolean running; + + NioClientEventLoopResources(int threads) { + this.running = new AtomicBoolean(true); + this.threads = threads; + } + + + @Override + @SuppressWarnings("unchecked") + public Mono disposeLater(Duration quietPeriod, Duration timeout) { + return Mono.defer(() -> { + long quietPeriodMillis = quietPeriod.toMillis(); + long timeoutMillis = timeout.toMillis(); + EventLoopGroup serverLoopsGroup = loops.get(); + Mono slMono = Mono.empty(); + if (running.compareAndSet(true, false)) { + if (serverLoopsGroup != null) { + slMono = FutureMono.from((Future) serverLoopsGroup.shutdownGracefully( + quietPeriodMillis, timeoutMillis, TimeUnit.MILLISECONDS)); + } + } + return Mono.when(slMono); + }); + } + + @Override + public boolean isDisposed() { + return !running.get(); + } + + @Override + public EventLoopGroup onClient(boolean useNative) { + return cacheLoops(); + } + + @Override + public EventLoopGroup onServer(boolean useNative) { + throw new UnsupportedOperationException("This event loop is designed only for client DB calls."); + } + + @Override + public EventLoopGroup onServerSelect(boolean useNative) { + throw new UnsupportedOperationException("This event loop is designed only for client DB calls."); + } + + @Override + public CHANNEL onChannel(Class channelType, EventLoopGroup group) { + if (channelType.equals(SocketChannel.class)) { + return (CHANNEL) new NioSocketChannel(); + } + if (channelType.equals(ServerSocketChannel.class)) { + return (CHANNEL) new NioServerSocketChannel(); + } + if (channelType.equals(DatagramChannel.class)) { + return (CHANNEL) new NioDatagramChannel(); + } + throw new IllegalArgumentException("Unsupported channel type: " + channelType.getSimpleName()); + } + + @Override + public Class onChannelClass(Class channelType, + EventLoopGroup group) { + if (channelType.equals(SocketChannel.class)) { + return (Class) NioSocketChannel.class; + } + if (channelType.equals(ServerSocketChannel.class)) { + return (Class) NioServerSocketChannel.class; + } + if (channelType.equals(DatagramChannel.class)) { + return (Class) NioDatagramChannel.class; + } + throw new IllegalArgumentException("Unsupported channel type: " + channelType.getSimpleName()); + } + + @SuppressWarnings("FutureReturnValueIgnored") + EventLoopGroup cacheLoops() { + EventLoopGroup eventLoopGroup = loops.get(); + if (null == eventLoopGroup) { + EventLoopGroup newEventLoopGroup = createNewEventLoopGroup(); + if (!loops.compareAndSet(null, newEventLoopGroup)) { + //"FutureReturnValueIgnored" this is deliberate + newEventLoopGroup.shutdownGracefully(0, 0, TimeUnit.MILLISECONDS); + } + eventLoopGroup = cacheLoops(); + } + return eventLoopGroup; + } + + private NioEventLoopGroup createNewEventLoopGroup() { + return new NioEventLoopGroup(threads, new ThreadPerTaskExecutor(new DefaultThreadFactory(THREAD_PREFIX))); + } +} \ No newline at end of file diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Fortune.kt b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Fortune.kt new file mode 100644 index 00000000000..40b75ef4354 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Fortune.kt @@ -0,0 +1,6 @@ +package org.jetbrains.ktor.benchmarks.models + +import kotlinx.serialization.Serializable + +@Serializable +class Fortune(val id: Int, var message: String) diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Message.kt b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Message.kt new file mode 100644 index 00000000000..fc9bd1fada1 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Message.kt @@ -0,0 +1,6 @@ +package org.jetbrains.ktor.benchmarks.models + +import kotlinx.serialization.Serializable + +@Serializable +class Message(val message: String) diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/World.kt b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/World.kt new file mode 100644 index 00000000000..0c35be5c969 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/World.kt @@ -0,0 +1,6 @@ +package org.jetbrains.ktor.benchmarks.models + +import kotlinx.serialization.Serializable + +@Serializable +class World(val id: Int, var randomNumber: Int) diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/application.conf b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/application.conf new file mode 100644 index 00000000000..fe2031a7699 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/application.conf @@ -0,0 +1,26 @@ +ktor { + deployment { + port = 9090 + autoreload = false + watch = [ ] + shareWorkGroup = true + } + + application { + modules = [ org.jetbrains.ktor.benchmarks.HelloKt.main ] + } +} + +db { + driver = "pool" + protocol = "postgresql" + ssl = "false" + host = "tfb-database" + port = 5432 + database = "hello_world" + initPoolSize = 512 + maxPoolSize = 512 + username = "benchmarkdbuser" + password = "benchmarkdbpass" + //url = "r2dbc:postgresql://"${db.host}":"${db.port}"/"${db.database}"?loggerLevel=OFF&disableColumnSanitiser=true&assumeMinServerVersion=16&sslmode=disable&maxSize="${db.poolSize} +} diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml new file mode 100644 index 00000000000..9fd0f518971 --- /dev/null +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml @@ -0,0 +1,21 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + true + + + + + + + + + + + + diff --git a/frameworks/Kotlin/ktor/ktor/README.md b/frameworks/Kotlin/ktor/ktor/README.md index e3c141a70e0..31f2755e24c 100644 --- a/frameworks/Kotlin/ktor/ktor/README.md +++ b/frameworks/Kotlin/ktor/ktor/README.md @@ -5,13 +5,13 @@ More information is available at [ktor.io](http://ktor.io). # Setup -* Java 17 -* MySQL server +* Java 21 +* Postgres server # Requirements * Maven 3 -* JDK 17 +* JDK 21 * Kotlin * ktor * netty diff --git a/frameworks/Kotlin/ktor/ktor/pom.xml b/frameworks/Kotlin/ktor/ktor/pom.xml index 74d725ca6fa..670b77cdf51 100644 --- a/frameworks/Kotlin/ktor/ktor/pom.xml +++ b/frameworks/Kotlin/ktor/ktor/pom.xml @@ -13,7 +13,7 @@ 2.0.21 - 3.0.2 + 3.0.3 1.7.3 0.11.0 UTF-8 diff --git a/frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt b/frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt index b44808fba40..a83a16f326b 100644 --- a/frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt +++ b/frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt @@ -38,7 +38,7 @@ fun Application.main() { install(DefaultHeaders) - val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain).also { it.contentLength } + val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain) routing { get("/plaintext") {