Skip to content

Commit c7591cf

Browse files
authored
update ktor to user Gradle (#10365)
* update ktor to user Gradle * fix r2dbc update * improve updates * use pipelining of r2dbc to improve perf * fixed and optimized pgclient * fixed jettyf
1 parent 932cf08 commit c7591cf

File tree

28 files changed

+990
-590
lines changed

28 files changed

+990
-590
lines changed

frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ plugins {
22
application
33
kotlin("jvm") version "2.0.21"
44
kotlin("plugin.serialization") version "2.0.0"
5-
id("com.github.johnrengelman.shadow") version "8.1.0"
5+
id("com.gradleup.shadow") version "8.3.9"
66
}
77

88
group = "org.jetbrains.ktor"

frameworks/Kotlin/ktor/ktor-cio.dockerfile

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
1+
FROM gradle:8.13-jdk21 AS build
22
WORKDIR /ktor
3-
COPY ktor/pom.xml pom.xml
4-
COPY ktor/src src
5-
RUN mvn clean package -q
3+
COPY ktor/ ./
4+
RUN chmod +x gradlew && ./gradlew --no-daemon clean cioBundle
65

76
FROM amazoncorretto:21-al2023-headless
87
WORKDIR /ktor
9-
COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-cio-bundle.jar app.jar
8+
COPY --from=build /ktor/build/libs/tech-empower-framework-benchmark-1.0-SNAPSHOT-cio-bundle.jar app.jar
109

1110
EXPOSE 9090
1211

frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
14
plugins {
25
application
36
kotlin("jvm") version "2.1.21"
47
kotlin("plugin.serialization") version "2.1.21"
5-
id("com.github.johnrengelman.shadow") version "8.1.0"
8+
id("com.gradleup.shadow") version "8.3.9"
69
}
710

811
repositories {
912
mavenCentral()
1013
}
1114

12-
val ktorVersion = "3.1.3"
15+
val ktorVersion = "3.3.3"
1316
val kotlinxSerializationVersion = "1.8.1"
1417
val exposedVersion = "0.61.0"
1518

@@ -31,3 +34,13 @@ dependencies {
3134
}
3235

3336
application.mainClass.set("AppKt")
37+
38+
kotlin {
39+
jvmToolchain(21)
40+
}
41+
42+
tasks.withType<KotlinCompile>().configureEach {
43+
compilerOptions {
44+
jvmTarget.set(JvmTarget.JVM_21)
45+
}
46+
}

frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import io.ktor.server.plugins.defaultheaders.*
1010
import io.ktor.server.response.*
1111
import io.ktor.server.routing.*
1212
import kotlinx.coroutines.Dispatchers
13-
import kotlinx.coroutines.withContext
1413
import kotlinx.html.*
1514
import kotlinx.serialization.Serializable
1615
import kotlinx.serialization.json.Json
@@ -22,8 +21,9 @@ import org.jetbrains.exposed.sql.Database
2221
import org.jetbrains.exposed.sql.ResultRow
2322
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
2423
import org.jetbrains.exposed.sql.Transaction
25-
import org.jetbrains.exposed.sql.transactions.transaction
26-
import org.jetbrains.exposed.sql.update
24+
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
25+
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
26+
import org.jetbrains.exposed.sql.transactions.TransactionManager
2727
import java.util.concurrent.ThreadLocalRandom
2828

2929
@Serializable
@@ -73,12 +73,12 @@ fun main(args: Array<String>) {
7373
}
7474

7575
fun Application.module(exposedMode: ExposedMode) {
76-
val dbRows = 10000
77-
val poolSize = 48
76+
val poolSize = Runtime.getRuntime().availableProcessors() * 2
7877
val pool = HikariDataSource(HikariConfig().apply { configurePostgres(poolSize) })
79-
Database.connect(pool)
80-
suspend fun <T> withDatabaseContextAndTransaction(statement: Transaction.() -> T) =
81-
withContext(Dispatchers.IO) { transaction(statement = statement) }
78+
val database = Database.connect(pool)
79+
val databaseDispatcher = Dispatchers.IO.limitedParallelism(poolSize)
80+
suspend fun <T> withDatabaseTransaction(statement: suspend Transaction.() -> T) =
81+
newSuspendedTransaction(context = databaseDispatcher, db = database, statement = statement)
8282

8383
install(DefaultHeaders)
8484

@@ -93,7 +93,7 @@ fun Application.module(exposedMode: ExposedMode) {
9393
Fortune(this[FortuneTable.id].value, this[FortuneTable.message])
9494

9595
fun ThreadLocalRandom.nextIntWithinRows() =
96-
nextInt(dbRows) + 1
96+
nextInt(DB_ROWS) + 1
9797

9898
fun selectSingleWorld(random: ThreadLocalRandom): World =
9999
selectWorldsWithIdQuery(random.nextIntWithinRows()).single().toWorld()
@@ -103,7 +103,7 @@ fun Application.module(exposedMode: ExposedMode) {
103103

104104
get("/db") {
105105
val random = ThreadLocalRandom.current()
106-
val result = withDatabaseContextAndTransaction {
106+
val result = withDatabaseTransaction {
107107
when (exposedMode) {
108108
Dsl -> selectSingleWorld(random)
109109
Dao -> WorldDao[random.nextIntWithinRows()].toWorld()
@@ -117,7 +117,7 @@ fun Application.module(exposedMode: ExposedMode) {
117117
val queries = call.queries()
118118
val random = ThreadLocalRandom.current()
119119

120-
val result = withDatabaseContextAndTransaction {
120+
val result = withDatabaseTransaction {
121121
when (exposedMode) {
122122
Dsl -> selectWorlds(queries, random)
123123
Dao -> //List(queries) { WorldDao[random.nextIntWithinRows()].toWorld() }
@@ -129,7 +129,7 @@ fun Application.module(exposedMode: ExposedMode) {
129129
}
130130

131131
get("/fortunes") {
132-
val result = withDatabaseContextAndTransaction {
132+
val result = withDatabaseTransaction {
133133
when (exposedMode) {
134134
Dsl -> FortuneTable.select(FortuneTable.id, FortuneTable.message)
135135
.asSequence().map { it.toFortune() }
@@ -164,23 +164,17 @@ fun Application.module(exposedMode: ExposedMode) {
164164
val random = ThreadLocalRandom.current()
165165
lateinit var result: List<World>
166166

167-
withDatabaseContextAndTransaction {
167+
withDatabaseTransaction {
168168
when (exposedMode) {
169169
Dsl -> {
170170
result = selectWorlds(queries, random)
171-
result.forEach { it.randomNumber = random.nextInt(dbRows) + 1 }
172-
result
173-
// sort the data to avoid data race because all updates are in one transaction
174-
.sortedBy { it.id }
175-
.forEach { world ->
176-
WorldTable.update({ WorldTable.id eq world.id }) {
177-
it[randomNumber] = world.randomNumber
178-
}
179-
/*
180-
// An alternative approach: commit every change to avoid data race
181-
commit()
182-
*/
183-
}
171+
result.forEach { it.randomNumber = random.nextIntWithinRows() }
172+
val batch = BatchUpdateStatement(WorldTable)
173+
result.sortedBy { it.id }.forEach { world ->
174+
batch.addBatch(EntityID(world.id, WorldTable))
175+
batch[WorldTable.randomNumber] = world.randomNumber
176+
}
177+
batch.execute(TransactionManager.current())
184178
}
185179

186180
Dao -> /*{
@@ -202,6 +196,8 @@ fun Application.module(exposedMode: ExposedMode) {
202196
}
203197
}
204198

199+
private const val DB_ROWS = 10_000
200+
205201
fun HikariConfig.configurePostgres(poolSize: Int) {
206202
jdbcUrl = "jdbc:postgresql://tfb-database/hello_world?useSSL=false"
207203
driverClassName = org.postgresql.Driver::class.java.name
@@ -224,3 +220,4 @@ fun HikariConfig.configureCommon(poolSize: Int) {
224220

225221
fun ApplicationCall.queries() =
226222
request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1
223+

frameworks/Kotlin/ktor/ktor-jetty.dockerfile

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
1+
FROM gradle:8.13-jdk21 AS build
22
WORKDIR /ktor
3-
COPY ktor/pom.xml pom.xml
4-
COPY ktor/src src
5-
RUN mvn clean package -q
3+
COPY ktor/ ./
4+
RUN chmod +x gradlew && ./gradlew --no-daemon clean jettyBundle
65

76
FROM amazoncorretto:21-al2023-headless
87
WORKDIR /ktor
9-
COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-jetty-bundle.jar app.jar
8+
COPY --from=build /ktor/build/libs/tech-empower-framework-benchmark-1.0-SNAPSHOT-jetty-bundle.jar app.jar
109

1110
EXPOSE 9090
1211

frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
14
plugins {
25
application
3-
kotlin("jvm") version "2.0.21"
4-
kotlin("plugin.serialization") version "2.0.0"
5-
id("com.github.johnrengelman.shadow") version "8.1.0"
6+
kotlin("jvm") version "2.1.21"
7+
kotlin("plugin.serialization") version "2.1.21"
8+
id("com.gradleup.shadow") version "8.3.9"
69
}
710

811
group = "org.jetbrains.ktor"
@@ -16,8 +19,8 @@ application {
1619
mainClass = "io.ktor.server.netty.EngineMain"
1720
}
1821

19-
val ktor_version = "3.1.2"
20-
val vertx_version = "4.5.11"
22+
val ktor_version = "3.3.3"
23+
val vertx_version = "5.0.5"
2124

2225
dependencies {
2326
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
@@ -36,6 +39,16 @@ java {
3639
}
3740
}
3841

42+
kotlin {
43+
jvmToolchain(21)
44+
}
45+
46+
tasks.withType<KotlinCompile>().configureEach {
47+
compilerOptions {
48+
jvmTarget.set(JvmTarget.JVM_21)
49+
}
50+
}
51+
3952
tasks.shadowJar {
4053
archiveBaseName.set("ktor-pgclient")
4154
archiveClassifier.set("")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import io.ktor.http.ContentType
2+
import io.ktor.http.content.TextContent
13
import io.ktor.server.application.*
24
import io.ktor.server.html.*
35
import io.ktor.server.plugins.defaultheaders.*
@@ -8,23 +10,32 @@ import io.vertx.pgclient.PgBuilder
810
import io.vertx.pgclient.PgConnectOptions
911
import io.vertx.sqlclient.PoolOptions
1012
import io.vertx.sqlclient.Tuple
13+
import kotlinx.coroutines.async
14+
import kotlinx.coroutines.awaitAll
15+
import kotlinx.coroutines.coroutineScope
1116
import kotlinx.html.*
1217
import java.util.concurrent.ThreadLocalRandom
1318

1419
val rand: ThreadLocalRandom
1520
get() = ThreadLocalRandom.current()
1621

22+
private const val HELLO_WORLD = "Hello, World!"
23+
private const val WORLD_ROWS = 10_000
24+
25+
private fun nextWorldId(): Int = rand.nextInt(1, WORLD_ROWS + 1)
26+
1727
interface Repository {
1828
suspend fun getWorld(): World
29+
suspend fun getWorlds(count: Int): List<World>
1930
suspend fun getFortunes(): List<Fortune>
2031
suspend fun updateWorlds(worlds: List<World>)
2132
}
2233

2334
class PgclientRepository : Repository {
2435
companion object {
2536
private const val FORTUNES_QUERY = "select id, message from FORTUNE"
26-
private const val SELECT_WORLD_QUERY = "SELECT id, randomnumber from WORLD where id=$1"
27-
private const val UPDATE_WORLD_QUERY = "UPDATE WORLD SET randomnumber=$1 WHERE id=$2"
37+
private const val SELECT_WORLD_QUERY = "SELECT id, randomnumber from WORLD where id=\$1"
38+
private const val UPDATE_WORLD_QUERY = "UPDATE WORLD SET randomnumber=\$1 WHERE id=\$2"
2839
}
2940

3041
private val connectOptions =
@@ -38,35 +49,43 @@ class PgclientRepository : Repository {
3849
pipeliningLimit = 100000
3950
}
4051

52+
private val poolSize = Runtime.getRuntime().availableProcessors() * 2
4153
private val poolOptions = PoolOptions()
54+
.setMaxSize(poolSize)
55+
.setMaxWaitQueueSize(poolSize * 2)
4256
private val client = PgBuilder.client()
4357
.with(poolOptions)
4458
.connectingTo(connectOptions)
4559
.build()
4660

61+
private val selectWorldStatement = client.preparedQuery(SELECT_WORLD_QUERY)
62+
private val updateWorldStatement = client.preparedQuery(UPDATE_WORLD_QUERY)
63+
private val fortunesStatement = client.preparedQuery(FORTUNES_QUERY)
64+
4765
override suspend fun getFortunes(): List<Fortune> {
48-
val results = client.preparedQuery(FORTUNES_QUERY).execute().coAwait()
66+
val results = fortunesStatement.execute().coAwait()
4967
return results.map { Fortune(it.getInteger(0), it.getString(1)) }
5068
}
5169

52-
override suspend fun getWorld(): World {
53-
val worldId = rand.nextInt(1, 10001)
54-
val result =
55-
client
56-
.preparedQuery(SELECT_WORLD_QUERY)
57-
.execute(Tuple.of(worldId))
58-
.coAwait()
70+
override suspend fun getWorld(): World =
71+
getWorlds(1).first()
72+
73+
override suspend fun getWorlds(count: Int): List<World> = coroutineScope {
74+
List(count) {
75+
async { fetchWorld(nextWorldId()) }
76+
}.awaitAll()
77+
}
78+
79+
private suspend fun fetchWorld(id: Int): World {
80+
val result = selectWorldStatement.execute(Tuple.of(id)).coAwait()
5981
val row = result.first()
6082
return World(row.getInteger(0), row.getInteger(1)!!)
6183
}
6284

6385
override suspend fun updateWorlds(worlds: List<World>) {
64-
// Worlds should be sorted before being batch-updated with to avoid data race and deadlocks.
86+
if (worlds.isEmpty()) return
6587
val batch = worlds.sortedBy { it.id }.map { Tuple.of(it.randomNumber, it.id) }
66-
client
67-
.preparedQuery(UPDATE_WORLD_QUERY)
68-
.executeBatch(batch)
69-
.coAwait()
88+
updateWorldStatement.executeBatch(batch).coAwait()
7089
}
7190
}
7291

@@ -115,11 +134,11 @@ fun Application.main() {
115134
install(DefaultHeaders)
116135
routing {
117136
get("/plaintext") {
118-
call.respondText("Hello, World!")
137+
call.respond(TextContent(HELLO_WORLD, ContentType.Text.Plain))
119138
}
120139

121140
get("/json") {
122-
call.respondJson(Message("Hello, World!"))
141+
call.respondJson(Message(HELLO_WORLD))
123142
}
124143

125144
get("/db") {
@@ -128,7 +147,7 @@ fun Application.main() {
128147

129148
get("/query") {
130149
val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
131-
val worlds = List(queries) { db.getWorld() }
150+
val worlds = db.getWorlds(queries)
132151
call.respondJson(worlds)
133152
}
134153

@@ -142,12 +161,12 @@ fun Application.main() {
142161

143162
get("/updates") {
144163
val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
145-
val worlds = List(queries) { db.getWorld() }
146-
val newWorlds = worlds.map { it.copy(randomNumber = rand.nextInt(1, 10001)) }
147-
148-
db.updateWorlds(newWorlds)
149-
150-
call.respondJson(newWorlds)
164+
val worlds = db.getWorlds(queries).map { world ->
165+
world.randomNumber = nextWorldId()
166+
world
167+
}
168+
db.updateWorlds(worlds)
169+
call.respondJson(worlds)
151170
}
152171
}
153172
}

0 commit comments

Comments
 (0)