Skip to content

Commit 7c3a5bf

Browse files
authored
Runtime improvements (#9722)
* upgrade ktor depds * Update build.gradle.kts * Update pom.xml * Update pom.xml * some optimizations * improved r2dbc * upgrade kotlin version * fixed update * cleanup * cleanup 2 * fix db params * simplify update * added null checks * revert /update change * cleanup update --------- Co-authored-by: Ilya Nemtsev <[email protected]>
1 parent 0ec7207 commit 7c3a5bf

File tree

9 files changed

+80
-47
lines changed

9 files changed

+80
-47
lines changed

frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<name>org.jetbrains.ktor tech-empower-framework-benchmark</name>
1313

1414
<properties>
15-
<kotlin.version>2.0.21</kotlin.version>
15+
<kotlin.version>2.1.20</kotlin.version>
1616
<kotlin.coroutines.version>1.10.1</kotlin.coroutines.version>
1717
<ktor.version>3.1.1</ktor.version>
1818
<serialization.version>1.8.0</serialization.version>

frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ import org.jetbrains.ktor.benchmarks.models.World
3232
import reactor.core.publisher.Flux
3333
import reactor.core.publisher.Mono
3434
import kotlin.random.Random
35+
import java.time.Duration
36+
37+
private val json = Json {
38+
prettyPrint = false
39+
isLenient = true
40+
ignoreUnknownKeys = true
41+
coerceInputValues = true
42+
}
3543

3644
fun Application.main() {
3745
val config = ApplicationConfig("application.conf")
@@ -40,22 +48,23 @@ fun Application.main() {
4048
install(DefaultHeaders)
4149

4250
val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
51+
val helloWorldMsg = Message("Hello, world!")
4352

4453
routing {
4554
get("/plaintext") {
4655
call.respond(helloWorldContent)
4756
}
4857

4958
get("/json") {
50-
call.respondText(Json.encodeToString(Message("Hello, world!")), ContentType.Application.Json)
59+
call.respondText(json.encodeToString(helloWorldMsg), ContentType.Application.Json)
5160
}
5261

5362
get("/db") {
5463
val random = Random.Default
5564
val request = getWorld(dbConnFactory, random)
5665
val result = request.awaitFirstOrNull()
5766

58-
call.respondText(Json.encodeToString(result), ContentType.Application.Json)
67+
call.respondText(json.encodeToString(result), ContentType.Application.Json)
5968
}
6069

6170
fun selectWorlds(queries: Int, random: Random): Flow<World> = flow {
@@ -74,7 +83,7 @@ fun Application.main() {
7483
}
7584
}
7685

77-
call.respondText(Json.encodeToString(result), ContentType.Application.Json)
86+
call.respondText(json.encodeToString(result), ContentType.Application.Json)
7887
}
7988

8089
get("/fortunes") {
@@ -135,21 +144,28 @@ fun Application.main() {
135144
}
136145
}
137146

138-
call.respondText(Json.encodeToString(worldsUpdated), ContentType.Application.Json)
147+
call.respondText(json.encodeToString(worldsUpdated), ContentType.Application.Json)
139148
}
140149
}
141150
}
142151

143152
private fun getWorld(
144153
dbConnFactory: ConnectionFactory, random: Random
145154
): Mono<World> = Mono.usingWhen(dbConnFactory.create(), { connection ->
146-
Mono.from(connection.createStatement(WORLD_QUERY).bind(0, random.nextInt(DB_ROWS) + 1).execute()).flatMap { r ->
147-
Mono.from(r.map { row, _ ->
148-
World(
149-
row.get(0, Int::class.java)!!, row.get(1, Int::class.java)!!
150-
)
151-
})
152-
}
155+
Mono.from(connection.createStatement(WORLD_QUERY)
156+
.bind("$1", random.nextInt(DB_ROWS) + 1)
157+
.execute())
158+
.flatMap { r ->
159+
Mono.from(r.map { row, _ ->
160+
val id = row.get(0, Int::class.java)
161+
val randomNumber = row.get(1, Int::class.java)
162+
if (id != null && randomNumber != null) {
163+
World(id, randomNumber)
164+
} else {
165+
throw IllegalStateException("Database returned null values for required fields")
166+
}
167+
})
168+
}
153169
}, Connection::close)
154170

155171
private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory {
@@ -170,6 +186,9 @@ private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory
170186
val cp = ConnectionPoolConfiguration.builder(cf)
171187
.initialSize(config.property("db.initPoolSize").getString().toInt())
172188
.maxSize(config.property("db.maxPoolSize").getString().toInt())
189+
.maxIdleTime(Duration.ofSeconds(30))
190+
.maxAcquireTime(Duration.ofSeconds(5))
191+
.validationQuery("SELECT 1")
173192
.build()
174193

175194
return ConnectionPool(cp)

frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Fortune.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ package org.jetbrains.ktor.benchmarks.models
33
import kotlinx.serialization.Serializable
44

55
@Serializable
6-
class Fortune(val id: Int, var message: String)
6+
data class Fortune(val id: Int, var message: String)

frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/Message.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,9 @@ package org.jetbrains.ktor.benchmarks.models
33
import kotlinx.serialization.Serializable
44

55
@Serializable
6-
class Message(val message: String)
6+
data class Message(val message: String)
7+
8+
// Cache common messages to reduce allocations
9+
object MessageCache {
10+
val helloWorld = Message("Hello, world!")
11+
}

frameworks/Kotlin/ktor/ktor-r2dbc/src/main/kotlin/org/jetbrains/ktor/benchmarks/models/World.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ package org.jetbrains.ktor.benchmarks.models
33
import kotlinx.serialization.Serializable
44

55
@Serializable
6-
class World(val id: Int, var randomNumber: Int)
6+
data class World(val id: Int, var randomNumber: Int)
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
<configuration>
22
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
33
<encoder>
4-
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
4+
<pattern>%msg%n</pattern>
55
</encoder>
66
</appender>
77

8-
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
9-
<neverBlock>true</neverBlock>
10-
<appender-ref ref="STDOUT" />
11-
</appender>
12-
8+
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
9+
<neverBlock>true</neverBlock>
10+
<appender-ref ref="STDOUT" />
11+
</appender>
1312

14-
<root level="INFO">
13+
<root level="WARN">
1514
<appender-ref ref="ASYNC"/>
1615
</root>
1716

18-
<logger name="org.eclipse.jetty" level="INFO"/>
19-
<logger name="io.netty" level="INFO"/>
20-
17+
<logger name="org.eclipse.jetty" level="WARN"/>
18+
<logger name="io.netty" level="WARN"/>
19+
<logger name="io.r2dbc" level="WARN"/>
20+
<logger name="reactor" level="WARN"/>
2121
</configuration>

frameworks/Kotlin/ktor/ktor.dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-net
1010

1111
EXPOSE 9090
1212

13-
CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"]
13+
CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseG1GC", "-XX:+AlwaysPreTouch", "-XX:+UseStringDeduplication", "-jar", "app.jar"]

frameworks/Kotlin/ktor/ktor/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<name>org.jetbrains.ktor tech-empower-framework-benchmark</name>
1313

1414
<properties>
15-
<kotlin.version>2.0.21</kotlin.version>
15+
<kotlin.version>2.1.20</kotlin.version>
1616
<ktor.version>3.1.1</ktor.version>
1717
<serialization.version>1.8.0</serialization.version>
1818
<kotlinx.html.version>0.12.0</kotlinx.html.version>

frameworks/Kotlin/ktor/ktor/src/main/kotlin/org/jetbrains/ktor/benchmarks/Hello.kt

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import org.jetbrains.ktor.benchmarks.Constants.WORLD_QUERY
2020
import java.sql.Connection
2121
import java.util.concurrent.ThreadLocalRandom
2222
import kotlin.random.Random
23+
import kotlinx.serialization.Contextual
2324

2425
@Serializable
2526
data class Message(val message: String)
@@ -30,23 +31,34 @@ data class World(val id: Int, var randomNumber: Int)
3031
@Serializable
3132
data class Fortune(val id: Int, var message: String)
3233

34+
// Optimized JSON instance with better performance settings
35+
private val json = Json {
36+
prettyPrint = false
37+
isLenient = true
38+
ignoreUnknownKeys = true
39+
coerceInputValues = true
40+
}
41+
3342
fun Application.main() {
3443
val dbRows = 10000
35-
val poolSize = 48
44+
val poolSize = Runtime.getRuntime().availableProcessors() * 2
3645
val pool = HikariDataSource(HikariConfig().apply { configurePostgres(poolSize) })
37-
val databaseDispatcher = Dispatchers.IO
46+
47+
// Create a dedicated dispatcher for database operations
48+
val databaseDispatcher = Dispatchers.IO.limitedParallelism(poolSize)
3849

3950
install(DefaultHeaders)
4051

4152
val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
53+
val jsonResponse = json.encodeToString(Message("Hello, world!"))
4254

4355
routing {
4456
get("/plaintext") {
4557
call.respond(helloWorldContent)
4658
}
4759

4860
get("/json") {
49-
call.respondText(Json.encodeToString(Message("Hello, world!")), ContentType.Application.Json)
61+
call.respondText(jsonResponse, ContentType.Application.Json)
5062
}
5163

5264
get("/db") {
@@ -56,7 +68,6 @@ fun Application.main() {
5668
pool.connection.use { connection ->
5769
connection.prepareStatement(WORLD_QUERY).use { statement ->
5870
statement.setInt(1, random.nextInt(dbRows) + 1)
59-
6071
statement.executeQuery().use { rs ->
6172
rs.next()
6273
World(rs.getInt(1), rs.getInt(2))
@@ -65,22 +76,20 @@ fun Application.main() {
6576
}
6677
}
6778

68-
call.respondText(Json.encodeToString(world), ContentType.Application.Json)
79+
call.respondText(json.encodeToString(world), ContentType.Application.Json)
6980
}
7081

7182
fun Connection.selectWorlds(queries: Int, random: Random): List<World> {
7283
val result = ArrayList<World>(queries)
7384
prepareStatement(WORLD_QUERY).use { statement ->
7485
repeat(queries) {
7586
statement.setInt(1, random.nextInt(dbRows) + 1)
76-
7787
statement.executeQuery().use { rs ->
7888
rs.next()
7989
result += World(rs.getInt(1), rs.getInt(2))
8090
}
8191
}
8292
}
83-
8493
return result
8594
}
8695

@@ -92,7 +101,7 @@ fun Application.main() {
92101
pool.connection.use { it.selectWorlds(queries, random) }
93102
}
94103

95-
call.respondText(Json.encodeToString(result), ContentType.Application.Json)
104+
call.respondText(json.encodeToString(result), ContentType.Application.Json)
96105
}
97106

98107
get("/fortunes") {
@@ -137,30 +146,27 @@ fun Application.main() {
137146
withContext(databaseDispatcher) {
138147
pool.connection.use { connection ->
139148
result = connection.selectWorlds(queries, random)
140-
141149
result.forEach { it.randomNumber = random.nextInt(dbRows) + 1 }
142150

143151
connection.prepareStatement(UPDATE_QUERY).use { updateStatement ->
144-
for ((id, randomNumber) in result) {
145-
updateStatement.setInt(1, randomNumber)
146-
updateStatement.setInt(2, id)
147-
updateStatement.addBatch()
148-
}
149-
150-
updateStatement.executeBatch()
152+
for ((id, randomNumber) in result) {
153+
updateStatement.setInt(1, randomNumber)
154+
updateStatement.setInt(2, id)
155+
updateStatement.addBatch()
151156
}
157+
updateStatement.executeBatch()
158+
}
152159
}
153160
}
154161

155-
call.respondText(Json.encodeToString(result), ContentType.Application.Json)
162+
call.respondText(json.encodeToString(result), ContentType.Application.Json)
156163
}
157164
}
158165
}
159166

160167
fun HikariConfig.configurePostgres(poolSize: Int) {
161168
jdbcUrl = "jdbc:postgresql://tfb-database/hello_world?useSSL=false"
162169
driverClassName = org.postgresql.Driver::class.java.name
163-
164170
configureCommon(poolSize)
165171
}
166172

@@ -172,9 +178,13 @@ fun HikariConfig.configureCommon(poolSize: Int) {
172178
addDataSourceProperty("useUnbufferedInput", "false")
173179
addDataSourceProperty("prepStmtCacheSize", "4096")
174180
addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
175-
connectionTimeout = 10000
181+
connectionTimeout = 5000
176182
maximumPoolSize = poolSize
177183
minimumIdle = poolSize
184+
idleTimeout = 300000 // 5 minutes
185+
maxLifetime = 600000 // 10 minutes
186+
validationTimeout = 5000
187+
leakDetectionThreshold = 60000
178188
}
179189

180190
fun HikariConfig.configureMySql(poolSize: Int) {
@@ -186,7 +196,6 @@ fun HikariConfig.configureMySql(poolSize: Int) {
186196
fun ApplicationCall.queries() =
187197
request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1
188198

189-
190199
object Constants {
191200
const val WORLD_QUERY = "SELECT id, randomNumber FROM World WHERE id = ?"
192201
const val FORTUNES_QUERY = "SELECT id, message FROM fortune"

0 commit comments

Comments
 (0)