From c21e8fad0f7f9b14b13c619b716df71d5afe206e Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Feb 2025 16:31:15 +0700 Subject: [PATCH 01/15] upgrade ktor depds --- frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts | 6 +++--- frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml | 6 +++--- frameworks/Kotlin/ktor/ktor/pom.xml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts index 5d6db161d20..a776a01bbe8 100644 --- a/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts +++ b/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts @@ -9,9 +9,9 @@ repositories { mavenCentral() } -val ktorVersion = "3.0.3" -val kotlinxSerializationVersion = "1.7.3" -val exposedVersion = "0.56.0" +val ktorVersion = "3.1.0" +val kotlinxSerializationVersion = "1.8.0" +val exposedVersion = "0.59.0" dependencies { implementation("io.ktor:ktor-server-core:$ktorVersion") diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml index 092e8ab9559..f6dcc7df3ca 100644 --- a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml @@ -14,9 +14,9 @@ 2.0.21 1.10.1 - 3.0.3 - 1.7.3 - 0.11.0 + 3.1.0 + 1.8.0 + 0.12.0 UTF-8 1.5.12 3.7.1 diff --git a/frameworks/Kotlin/ktor/ktor/pom.xml b/frameworks/Kotlin/ktor/ktor/pom.xml index 670b77cdf51..5528e896197 100644 --- a/frameworks/Kotlin/ktor/ktor/pom.xml +++ b/frameworks/Kotlin/ktor/ktor/pom.xml @@ -13,9 +13,9 @@ 2.0.21 - 3.0.3 - 1.7.3 - 0.11.0 + 3.1.0 + 1.8.0 + 0.12.0 UTF-8 5.1.0 1.2.13 From b44474e1ad603631cff9615b8f6e9f5cbabfd78a Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Thu, 27 Feb 2025 14:14:12 +0700 Subject: [PATCH 02/15] Update build.gradle.kts --- frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts b/frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts index a776a01bbe8..63db97d7dd2 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.1.0" +val ktorVersion = "3.1.1" val kotlinxSerializationVersion = "1.8.0" val exposedVersion = "0.59.0" From 1c195806b258638f28d7be1e962b6222ab8f851b Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Thu, 27 Feb 2025 14:14:34 +0700 Subject: [PATCH 03/15] Update pom.xml --- frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml index f6dcc7df3ca..fc814702884 100644 --- a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml @@ -14,7 +14,7 @@ 2.0.21 1.10.1 - 3.1.0 + 3.1.1 1.8.0 0.12.0 UTF-8 From 7d30e3bc7cb6e31750e0aae203e13b9f13c76f74 Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Thu, 27 Feb 2025 14:14:52 +0700 Subject: [PATCH 04/15] Update pom.xml --- frameworks/Kotlin/ktor/ktor/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/Kotlin/ktor/ktor/pom.xml b/frameworks/Kotlin/ktor/ktor/pom.xml index 5528e896197..991f453b16a 100644 --- a/frameworks/Kotlin/ktor/ktor/pom.xml +++ b/frameworks/Kotlin/ktor/ktor/pom.xml @@ -13,7 +13,7 @@ 2.0.21 - 3.1.0 + 3.1.1 1.8.0 0.12.0 UTF-8 From 19e8695104419a7200484386ebf5f7683f111004 Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Mon, 24 Mar 2025 13:20:32 +0700 Subject: [PATCH 05/15] some optimizations --- frameworks/Kotlin/ktor/ktor.dockerfile | 2 +- .../org/jetbrains/ktor/benchmarks/Hello.kt | 48 +++++++++++-------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/frameworks/Kotlin/ktor/ktor.dockerfile b/frameworks/Kotlin/ktor/ktor.dockerfile index 076a51a1416..522fa558851 100644 --- a/frameworks/Kotlin/ktor/ktor.dockerfile +++ b/frameworks/Kotlin/ktor/ktor.dockerfile @@ -10,4 +10,4 @@ COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-net EXPOSE 9090 -CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"] +CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseG1GC", "-XX:+AlwaysPreTouch", "-XX:+UseStringDeduplication", "-jar", "app.jar"] 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 a83a16f326b..bfe580c3b36 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 @@ -30,15 +30,26 @@ data class World(val id: Int, var randomNumber: Int) @Serializable data class Fortune(val id: Int, var message: String) +// Optimized JSON instance with better performance settings +private val json = Json { + prettyPrint = false + isLenient = true + ignoreUnknownKeys = true + coerceInputValues = true +} + fun Application.main() { val dbRows = 10000 - val poolSize = 48 + val poolSize = Runtime.getRuntime().availableProcessors() * 2 val pool = HikariDataSource(HikariConfig().apply { configurePostgres(poolSize) }) - val databaseDispatcher = Dispatchers.IO + + // Create a dedicated dispatcher for database operations + val databaseDispatcher = Dispatchers.IO.limitedParallelism(poolSize) install(DefaultHeaders) val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain) + val jsonResponse = json.encodeToString(Message("Hello, world!")) routing { get("/plaintext") { @@ -46,7 +57,7 @@ fun Application.main() { } get("/json") { - call.respondText(Json.encodeToString(Message("Hello, world!")), ContentType.Application.Json) + call.respondText(jsonResponse, ContentType.Application.Json) } get("/db") { @@ -56,7 +67,6 @@ fun Application.main() { pool.connection.use { connection -> connection.prepareStatement(WORLD_QUERY).use { statement -> statement.setInt(1, random.nextInt(dbRows) + 1) - statement.executeQuery().use { rs -> rs.next() World(rs.getInt(1), rs.getInt(2)) @@ -65,7 +75,7 @@ fun Application.main() { } } - call.respondText(Json.encodeToString(world), ContentType.Application.Json) + call.respondText(json.encodeToString(world), ContentType.Application.Json) } fun Connection.selectWorlds(queries: Int, random: Random): List { @@ -73,14 +83,12 @@ fun Application.main() { prepareStatement(WORLD_QUERY).use { statement -> repeat(queries) { statement.setInt(1, random.nextInt(dbRows) + 1) - statement.executeQuery().use { rs -> rs.next() result += World(rs.getInt(1), rs.getInt(2)) } } } - return result } @@ -92,7 +100,7 @@ fun Application.main() { pool.connection.use { it.selectWorlds(queries, random) } } - call.respondText(Json.encodeToString(result), ContentType.Application.Json) + call.respondText(json.encodeToString(result), ContentType.Application.Json) } get("/fortunes") { @@ -137,22 +145,20 @@ fun Application.main() { withContext(databaseDispatcher) { pool.connection.use { connection -> result = connection.selectWorlds(queries, random) - result.forEach { it.randomNumber = random.nextInt(dbRows) + 1 } connection.prepareStatement(UPDATE_QUERY).use { updateStatement -> - for ((id, randomNumber) in result) { - updateStatement.setInt(1, randomNumber) - updateStatement.setInt(2, id) - updateStatement.addBatch() - } - - updateStatement.executeBatch() + for ((id, randomNumber) in result) { + updateStatement.setInt(1, randomNumber) + updateStatement.setInt(2, id) + updateStatement.addBatch() } + updateStatement.executeBatch() + } } } - call.respondText(Json.encodeToString(result), ContentType.Application.Json) + call.respondText(json.encodeToString(result), ContentType.Application.Json) } } } @@ -160,7 +166,6 @@ fun Application.main() { fun HikariConfig.configurePostgres(poolSize: Int) { jdbcUrl = "jdbc:postgresql://tfb-database/hello_world?useSSL=false" driverClassName = org.postgresql.Driver::class.java.name - configureCommon(poolSize) } @@ -172,9 +177,13 @@ fun HikariConfig.configureCommon(poolSize: Int) { addDataSourceProperty("useUnbufferedInput", "false") addDataSourceProperty("prepStmtCacheSize", "4096") addDataSourceProperty("prepStmtCacheSqlLimit", "2048") - connectionTimeout = 10000 + connectionTimeout = 5000 maximumPoolSize = poolSize minimumIdle = poolSize + idleTimeout = 300000 // 5 minutes + maxLifetime = 600000 // 10 minutes + validationTimeout = 5000 + leakDetectionThreshold = 60000 } fun HikariConfig.configureMySql(poolSize: Int) { @@ -186,7 +195,6 @@ fun HikariConfig.configureMySql(poolSize: Int) { fun ApplicationCall.queries() = request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1 - object Constants { const val WORLD_QUERY = "SELECT id, randomNumber FROM World WHERE id = ?" const val FORTUNES_QUERY = "SELECT id, message FROM fortune" From f0d67dadf69ef41c38599edd5d9a03a254ca79a2 Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Mon, 24 Mar 2025 13:30:59 +0700 Subject: [PATCH 06/15] improved r2dbc --- .../org/jetbrains/ktor/benchmarks/Hello.kt | 39 ++++++++++++------- .../ktor/benchmarks/models/Fortune.kt | 2 +- .../ktor/benchmarks/models/Message.kt | 7 +++- .../jetbrains/ktor/benchmarks/models/World.kt | 2 +- .../ktor-r2dbc/src/main/resources/logback.xml | 20 +++++----- 5 files changed, 44 insertions(+), 26 deletions(-) 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 index 461c6e303e8..8a8a2680b70 100644 --- 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 @@ -32,6 +32,14 @@ import org.jetbrains.ktor.benchmarks.models.World import reactor.core.publisher.Flux import reactor.core.publisher.Mono import kotlin.random.Random +import java.time.Duration + +private val json = Json { + prettyPrint = false + isLenient = true + ignoreUnknownKeys = true + coerceInputValues = true +} fun Application.main() { val config = ApplicationConfig("application.conf") @@ -40,6 +48,7 @@ fun Application.main() { install(DefaultHeaders) val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain) + val helloWorldMsg = Message("Hello, world!") routing { get("/plaintext") { @@ -47,7 +56,7 @@ fun Application.main() { } get("/json") { - call.respondText(Json.encodeToString(Message("Hello, world!")), ContentType.Application.Json) + call.respondText(json.encodeToString(helloWorldMsg), ContentType.Application.Json) } get("/db") { @@ -55,7 +64,7 @@ fun Application.main() { val request = getWorld(dbConnFactory, random) val result = request.awaitFirstOrNull() - call.respondText(Json.encodeToString(result), ContentType.Application.Json) + call.respondText(json.encodeToString(result), ContentType.Application.Json) } fun selectWorlds(queries: Int, random: Random): Flow = flow { @@ -74,7 +83,7 @@ fun Application.main() { } } - call.respondText(Json.encodeToString(result), ContentType.Application.Json) + call.respondText(json.encodeToString(result), ContentType.Application.Json) } get("/fortunes") { @@ -123,19 +132,20 @@ fun Application.main() { 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) + Mono.usingWhen(dbConnFactory.create(), { connection -> + val statement = connection.createStatement(UPDATE_QUERY) + worldsUpdated.forEach { world -> + statement.bind(0, world.randomNumber) + statement.bind(1, world.id) + statement.add() + } + Mono.from(statement.execute()) + }, Connection::close).awaitFirstOrNull() + + call.respondText(json.encodeToString(worldsUpdated), ContentType.Application.Json) } } } @@ -170,6 +180,9 @@ private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory val cp = ConnectionPoolConfiguration.builder(cf) .initialSize(config.property("db.initPoolSize").getString().toInt()) .maxSize(config.property("db.maxPoolSize").getString().toInt()) + .maxIdleTime(Duration.ofSeconds(30)) + .maxAcquireTime(Duration.ofSeconds(5)) + .validationQuery("SELECT 1") .build() return ConnectionPool(cp) 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 index 40b75ef4354..9484e01ab95 100644 --- 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 @@ -3,4 +3,4 @@ package org.jetbrains.ktor.benchmarks.models import kotlinx.serialization.Serializable @Serializable -class Fortune(val id: Int, var message: String) +data 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 index fc9bd1fada1..916169fbaf8 100644 --- 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 @@ -3,4 +3,9 @@ package org.jetbrains.ktor.benchmarks.models import kotlinx.serialization.Serializable @Serializable -class Message(val message: String) +data class Message(val message: String) + +// Cache common messages to reduce allocations +object MessageCache { + val helloWorld = Message("Hello, world!") +} 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 index 0c35be5c969..8b44731ccdf 100644 --- 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 @@ -3,4 +3,4 @@ package org.jetbrains.ktor.benchmarks.models import kotlinx.serialization.Serializable @Serializable -class World(val id: Int, var randomNumber: Int) +data class World(val id: Int, var randomNumber: Int) diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml index 9fd0f518971..3f5ba85f95f 100644 --- a/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml @@ -1,21 +1,21 @@ - %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + %msg%n - - true - - - + + true + + - + - - - + + + + From 28fc661d5c54956781eb575c6b947f62abf881cc Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Mon, 24 Mar 2025 13:33:05 +0700 Subject: [PATCH 07/15] upgrade kotlin version --- frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml | 2 +- frameworks/Kotlin/ktor/ktor/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml index fc814702884..75b1efc4cc0 100644 --- a/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml +++ b/frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml @@ -12,7 +12,7 @@ org.jetbrains.ktor tech-empower-framework-benchmark - 2.0.21 + 2.1.20 1.10.1 3.1.1 1.8.0 diff --git a/frameworks/Kotlin/ktor/ktor/pom.xml b/frameworks/Kotlin/ktor/ktor/pom.xml index 991f453b16a..93c927a496c 100644 --- a/frameworks/Kotlin/ktor/ktor/pom.xml +++ b/frameworks/Kotlin/ktor/ktor/pom.xml @@ -12,7 +12,7 @@ org.jetbrains.ktor tech-empower-framework-benchmark - 2.0.21 + 2.1.20 3.1.1 1.8.0 0.12.0 From 2e35849d91d3d4a8938dab7c64ac4cf0b3e25e47 Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Mar 2025 12:59:38 +0700 Subject: [PATCH 08/15] fixed update --- .../org/jetbrains/ktor/benchmarks/Hello.kt | 44 ++++++---- .../org/jetbrains/ktor/benchmarks/Hello.kt | 88 +++++++++++++++++++ 2 files changed, 116 insertions(+), 16 deletions(-) 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 index 8a8a2680b70..a2900fd83f3 100644 --- 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 @@ -127,22 +127,27 @@ fun Application.main() { val random = Random.Default val worlds = selectWorlds(queries, random) + val worldsUpdated = ArrayList(queries) - val worldsUpdated = buildList { - worlds.collect { world -> - world.randomNumber = random.nextInt(DB_ROWS) + 1 - add(world) - } + worlds.collect { world -> + world.randomNumber = random.nextInt(DB_ROWS) + 1 + worldsUpdated.add(world) } Mono.usingWhen(dbConnFactory.create(), { connection -> val statement = connection.createStatement(UPDATE_QUERY) worldsUpdated.forEach { world -> - statement.bind(0, world.randomNumber) - statement.bind(1, world.id) - statement.add() + if (world.id != null && world.randomNumber != null) { + statement.bind(0, world.randomNumber) + statement.bind(1, world.id) + statement.add() + } + } + if (worldsUpdated.isNotEmpty()) { + Mono.from(statement.execute()) + } else { + Mono.empty() } - Mono.from(statement.execute()) }, Connection::close).awaitFirstOrNull() call.respondText(json.encodeToString(worldsUpdated), ContentType.Application.Json) @@ -153,13 +158,20 @@ fun Application.main() { 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)!! - ) - }) - } + Mono.from(connection.createStatement(WORLD_QUERY) + .bind(0, random.nextInt(DB_ROWS) + 1) + .execute()) + .flatMap { r -> + Mono.from(r.map { row, _ -> + val id = row.get(0, Int::class.java) + val randomNumber = row.get(1, Int::class.java) + if (id != null && randomNumber != null) { + World(id, randomNumber) + } else { + throw IllegalStateException("Database returned null values for required fields") + } + }) + } }, Connection::close) private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory { 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 bfe580c3b36..45097c6ba1b 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 @@ -20,6 +20,19 @@ import org.jetbrains.ktor.benchmarks.Constants.WORLD_QUERY import java.sql.Connection import java.util.concurrent.ThreadLocalRandom import kotlin.random.Random +import kotlinx.serialization.Contextual +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.contextual +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.encodeStructure +import kotlinx.serialization.encoding.decodeStructure @Serializable data class Message(val message: String) @@ -30,6 +43,65 @@ data class World(val id: Int, var randomNumber: Int) @Serializable data class Fortune(val id: Int, var message: String) +@Serializable +data class DynamicValue( + @Contextual + val value: Any +) + +class AnySerializer : KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any") { + element("type") + element("value") + } + + override fun serialize(encoder: Encoder, value: Any) { + encoder.encodeStructure(descriptor) { + when (value) { + is String -> { + encodeStringElement(descriptor, 0, "string") + encodeStringElement(descriptor, 1, value) + } + is Number -> { + encodeStringElement(descriptor, 0, "number") + encodeStringElement(descriptor, 1, value.toString()) + } + is Boolean -> { + encodeStringElement(descriptor, 0, "boolean") + encodeStringElement(descriptor, 1, value.toString()) + } + is List<*> -> { + encodeStringElement(descriptor, 0, "list") + encodeStringElement(descriptor, 1, json.encodeToString(value)) + } + is Map<*, *> -> { + encodeStringElement(descriptor, 0, "map") + encodeStringElement(descriptor, 1, json.encodeToString(value)) + } + else -> { + encodeStringElement(descriptor, 0, "string") + encodeStringElement(descriptor, 1, value.toString()) + } + } + } + } + + override fun deserialize(decoder: Decoder): Any { + return decoder.decodeStructure(descriptor) { + val type = decodeStringElement(descriptor, 0) + val value = decodeStringElement(descriptor, 1) + when (type) { + "string" -> value + "number" -> value.toDoubleOrNull() ?: value + "boolean" -> value.toBoolean() + "list" -> json.decodeFromString>(value) + "map" -> json.decodeFromString>(value) + else -> value + } + } + } +} + // Optimized JSON instance with better performance settings private val json = Json { prettyPrint = false @@ -38,6 +110,17 @@ private val json = Json { coerceInputValues = true } +// Create a custom JSON instance with the AnySerializer +private val jsonWithAny = Json { + serializersModule = SerializersModule { + contextual(AnySerializer()) + } + prettyPrint = false + isLenient = true + ignoreUnknownKeys = true + coerceInputValues = true +} + fun Application.main() { val dbRows = 10000 val poolSize = Runtime.getRuntime().availableProcessors() * 2 @@ -160,6 +243,11 @@ fun Application.main() { call.respondText(json.encodeToString(result), ContentType.Application.Json) } + + post("/dynamic-map") { + val dynamicMap = call.receive>() + call.respondText(jsonWithAny.encodeToString(dynamicMap), ContentType.Application.Json) + } } } From d16be8c5cb6f624d91d1612895fc949835f08522 Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Mar 2025 13:02:31 +0700 Subject: [PATCH 09/15] cleanup --- .../org/jetbrains/ktor/benchmarks/Hello.kt | 78 +------------------ 1 file changed, 1 insertion(+), 77 deletions(-) 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 45097c6ba1b..0b6a1f19532 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 @@ -21,18 +21,6 @@ import java.sql.Connection import java.util.concurrent.ThreadLocalRandom import kotlin.random.Random import kotlinx.serialization.Contextual -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.contextual -import kotlinx.serialization.modules.polymorphic -import kotlinx.serialization.modules.subclass -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.buildClassSerialDescriptor -import kotlinx.serialization.descriptors.element -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.encodeStructure -import kotlinx.serialization.encoding.decodeStructure @Serializable data class Message(val message: String) @@ -49,59 +37,6 @@ data class DynamicValue( val value: Any ) -class AnySerializer : KSerializer { - override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any") { - element("type") - element("value") - } - - override fun serialize(encoder: Encoder, value: Any) { - encoder.encodeStructure(descriptor) { - when (value) { - is String -> { - encodeStringElement(descriptor, 0, "string") - encodeStringElement(descriptor, 1, value) - } - is Number -> { - encodeStringElement(descriptor, 0, "number") - encodeStringElement(descriptor, 1, value.toString()) - } - is Boolean -> { - encodeStringElement(descriptor, 0, "boolean") - encodeStringElement(descriptor, 1, value.toString()) - } - is List<*> -> { - encodeStringElement(descriptor, 0, "list") - encodeStringElement(descriptor, 1, json.encodeToString(value)) - } - is Map<*, *> -> { - encodeStringElement(descriptor, 0, "map") - encodeStringElement(descriptor, 1, json.encodeToString(value)) - } - else -> { - encodeStringElement(descriptor, 0, "string") - encodeStringElement(descriptor, 1, value.toString()) - } - } - } - } - - override fun deserialize(decoder: Decoder): Any { - return decoder.decodeStructure(descriptor) { - val type = decodeStringElement(descriptor, 0) - val value = decodeStringElement(descriptor, 1) - when (type) { - "string" -> value - "number" -> value.toDoubleOrNull() ?: value - "boolean" -> value.toBoolean() - "list" -> json.decodeFromString>(value) - "map" -> json.decodeFromString>(value) - else -> value - } - } - } -} - // Optimized JSON instance with better performance settings private val json = Json { prettyPrint = false @@ -110,17 +45,6 @@ private val json = Json { coerceInputValues = true } -// Create a custom JSON instance with the AnySerializer -private val jsonWithAny = Json { - serializersModule = SerializersModule { - contextual(AnySerializer()) - } - prettyPrint = false - isLenient = true - ignoreUnknownKeys = true - coerceInputValues = true -} - fun Application.main() { val dbRows = 10000 val poolSize = Runtime.getRuntime().availableProcessors() * 2 @@ -246,7 +170,7 @@ fun Application.main() { post("/dynamic-map") { val dynamicMap = call.receive>() - call.respondText(jsonWithAny.encodeToString(dynamicMap), ContentType.Application.Json) + call.respondText(json.encodeToString(dynamicMap), ContentType.Application.Json) } } } From e083c6d02bee96293823ca3560c512f89ab6b782 Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Mar 2025 13:03:44 +0700 Subject: [PATCH 10/15] cleanup 2 --- .../kotlin/org/jetbrains/ktor/benchmarks/Hello.kt | 11 ----------- 1 file changed, 11 deletions(-) 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 0b6a1f19532..17e8a8d8429 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 @@ -31,12 +31,6 @@ data class World(val id: Int, var randomNumber: Int) @Serializable data class Fortune(val id: Int, var message: String) -@Serializable -data class DynamicValue( - @Contextual - val value: Any -) - // Optimized JSON instance with better performance settings private val json = Json { prettyPrint = false @@ -167,11 +161,6 @@ fun Application.main() { call.respondText(json.encodeToString(result), ContentType.Application.Json) } - - post("/dynamic-map") { - val dynamicMap = call.receive>() - call.respondText(json.encodeToString(dynamicMap), ContentType.Application.Json) - } } } From a7714ad88de6a807496121a63572e9c2966c30ab Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Mar 2025 13:46:14 +0700 Subject: [PATCH 11/15] fix db params --- .../main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 index a2900fd83f3..68667f57307 100644 --- 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 @@ -137,11 +137,9 @@ fun Application.main() { Mono.usingWhen(dbConnFactory.create(), { connection -> val statement = connection.createStatement(UPDATE_QUERY) worldsUpdated.forEach { world -> - if (world.id != null && world.randomNumber != null) { - statement.bind(0, world.randomNumber) - statement.bind(1, world.id) - statement.add() - } + statement.bind("$1", world.randomNumber) + statement.bind("$2", world.id) + statement.add() } if (worldsUpdated.isNotEmpty()) { Mono.from(statement.execute()) @@ -159,7 +157,7 @@ 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) + .bind("$1", random.nextInt(DB_ROWS) + 1) .execute()) .flatMap { r -> Mono.from(r.map { row, _ -> From aea722c7e6cc64c78a24ed590b42a785a05ff99f Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Mar 2025 14:02:31 +0700 Subject: [PATCH 12/15] simplify update --- .../org/jetbrains/ktor/benchmarks/Hello.kt | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) 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 index 68667f57307..564e6df69a3 100644 --- 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 @@ -134,19 +134,15 @@ fun Application.main() { worldsUpdated.add(world) } - Mono.usingWhen(dbConnFactory.create(), { connection -> - val statement = connection.createStatement(UPDATE_QUERY) - worldsUpdated.forEach { world -> - statement.bind("$1", world.randomNumber) - statement.bind("$2", world.id) - statement.add() - } - if (worldsUpdated.isNotEmpty()) { - Mono.from(statement.execute()) - } else { - Mono.empty() - } - }, Connection::close).awaitFirstOrNull() + // Execute updates sequentially + worldsUpdated.forEach { world -> + Mono.usingWhen(dbConnFactory.create(), { connection -> + Mono.from(connection.createStatement(UPDATE_QUERY) + .bind("$1", world.randomNumber) + .bind("$2", world.id) + .execute()) + }, Connection::close).awaitFirstOrNull() + } call.respondText(json.encodeToString(worldsUpdated), ContentType.Application.Json) } From 19b4f5b2654130c242264346b08d23ef3b6dc30a Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Mar 2025 14:22:50 +0700 Subject: [PATCH 13/15] added null checks --- .../kotlin/org/jetbrains/ktor/benchmarks/Hello.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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 index 564e6df69a3..a96cf8264c8 100644 --- 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 @@ -157,13 +157,14 @@ private fun getWorld( .execute()) .flatMap { r -> Mono.from(r.map { row, _ -> - val id = row.get(0, Int::class.java) - val randomNumber = row.get(1, Int::class.java) - if (id != null && randomNumber != null) { - World(id, randomNumber) - } else { + val id = row.get("id", Int::class.java) ?: row.get(0, Int::class.java) + val randomNumber = row.get("randomnumber", Int::class.java) ?: row.get(1, Int::class.java) + + if (id == null || randomNumber == null) { throw IllegalStateException("Database returned null values for required fields") } + + World(id, randomNumber) }) } }, Connection::close) From 6762569a8c3fce8d4a621d5aa7b0c96ba892f150 Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Mar 2025 14:53:06 +0700 Subject: [PATCH 14/15] revert /update change --- .../org/jetbrains/ktor/benchmarks/Hello.kt | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) 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 index a96cf8264c8..99e9495419b 100644 --- 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 @@ -134,14 +134,20 @@ fun Application.main() { worldsUpdated.add(world) } - // Execute updates sequentially - worldsUpdated.forEach { world -> - Mono.usingWhen(dbConnFactory.create(), { connection -> - Mono.from(connection.createStatement(UPDATE_QUERY) - .bind("$1", world.randomNumber) - .bind("$2", world.id) - .execute()) - }, Connection::close).awaitFirstOrNull() + 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) @@ -157,14 +163,13 @@ private fun getWorld( .execute()) .flatMap { r -> Mono.from(r.map { row, _ -> - val id = row.get("id", Int::class.java) ?: row.get(0, Int::class.java) - val randomNumber = row.get("randomnumber", Int::class.java) ?: row.get(1, Int::class.java) - - if (id == null || randomNumber == null) { + val id = row.get(0, Int::class.java) + val randomNumber = row.get(1, Int::class.java) + if (id != null && randomNumber != null) { + World(id, randomNumber) + } else { throw IllegalStateException("Database returned null values for required fields") } - - World(id, randomNumber) }) } }, Connection::close) From 12d0c5862928a69096f9888d7bf795035477bfed Mon Sep 17 00:00:00 2001 From: Ilya Nemtsev Date: Tue, 25 Mar 2025 15:06:49 +0700 Subject: [PATCH 15/15] cleanup update --- .../src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt | 6 ------ 1 file changed, 6 deletions(-) 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 index 99e9495419b..ac5d03fdbe8 100644 --- 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 @@ -127,12 +127,6 @@ fun Application.main() { val random = Random.Default val worlds = selectWorlds(queries, random) - val worldsUpdated = ArrayList(queries) - - worlds.collect { world -> - world.randomNumber = random.nextInt(DB_ROWS) + 1 - worldsUpdated.add(world) - } val worldsUpdated = buildList { worlds.collect { world ->