Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frameworks/Kotlin/ktor/ktor-asyncdb/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ plugins {
application
kotlin("jvm") version "2.0.21"
kotlin("plugin.serialization") version "2.0.0"
id("com.github.johnrengelman.shadow") version "8.1.0"
id("com.gradleup.shadow") version "8.3.9"
}

group = "org.jetbrains.ktor"
Expand Down
9 changes: 4 additions & 5 deletions frameworks/Kotlin/ktor/ktor-cio.dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
FROM gradle:8.13-jdk21 AS build
WORKDIR /ktor
COPY ktor/pom.xml pom.xml
COPY ktor/src src
RUN mvn clean package -q
COPY ktor/ ./
RUN chmod +x gradlew && ./gradlew --no-daemon clean cioBundle

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

EXPOSE 9090

Expand Down
17 changes: 15 additions & 2 deletions frameworks/Kotlin/ktor/ktor-exposed/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
application
kotlin("jvm") version "2.1.21"
kotlin("plugin.serialization") version "2.1.21"
id("com.github.johnrengelman.shadow") version "8.1.0"
id("com.gradleup.shadow") version "8.3.9"
}

repositories {
mavenCentral()
}

val ktorVersion = "3.1.3"
val ktorVersion = "3.3.3"
val kotlinxSerializationVersion = "1.8.1"
val exposedVersion = "0.61.0"

Expand All @@ -31,3 +34,13 @@ dependencies {
}

application.mainClass.set("AppKt")

kotlin {
jvmToolchain(21)
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
49 changes: 23 additions & 26 deletions frameworks/Kotlin/ktor/ktor-exposed/app/src/main/kotlin/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import io.ktor.server.plugins.defaultheaders.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.html.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
Expand All @@ -22,8 +21,9 @@ import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.util.concurrent.ThreadLocalRandom

@Serializable
Expand Down Expand Up @@ -73,12 +73,12 @@ fun main(args: Array<String>) {
}

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

install(DefaultHeaders)

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

fun ThreadLocalRandom.nextIntWithinRows() =
nextInt(dbRows) + 1
nextInt(DB_ROWS) + 1

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

get("/db") {
val random = ThreadLocalRandom.current()
val result = withDatabaseContextAndTransaction {
val result = withDatabaseTransaction {
when (exposedMode) {
Dsl -> selectSingleWorld(random)
Dao -> WorldDao[random.nextIntWithinRows()].toWorld()
Expand All @@ -117,7 +117,7 @@ fun Application.module(exposedMode: ExposedMode) {
val queries = call.queries()
val random = ThreadLocalRandom.current()

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

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

withDatabaseContextAndTransaction {
withDatabaseTransaction {
when (exposedMode) {
Dsl -> {
result = selectWorlds(queries, random)
result.forEach { it.randomNumber = random.nextInt(dbRows) + 1 }
result
// sort the data to avoid data race because all updates are in one transaction
.sortedBy { it.id }
.forEach { world ->
WorldTable.update({ WorldTable.id eq world.id }) {
it[randomNumber] = world.randomNumber
}
/*
// An alternative approach: commit every change to avoid data race
commit()
*/
}
result.forEach { it.randomNumber = random.nextIntWithinRows() }
val batch = BatchUpdateStatement(WorldTable)
result.sortedBy { it.id }.forEach { world ->
batch.addBatch(EntityID(world.id, WorldTable))
batch[WorldTable.randomNumber] = world.randomNumber
}
batch.execute(TransactionManager.current())
}

Dao -> /*{
Expand All @@ -202,6 +196,8 @@ fun Application.module(exposedMode: ExposedMode) {
}
}

private const val DB_ROWS = 10_000

fun HikariConfig.configurePostgres(poolSize: Int) {
jdbcUrl = "jdbc:postgresql://tfb-database/hello_world?useSSL=false"
driverClassName = org.postgresql.Driver::class.java.name
Expand All @@ -224,3 +220,4 @@ fun HikariConfig.configureCommon(poolSize: Int) {

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

9 changes: 4 additions & 5 deletions frameworks/Kotlin/ktor/ktor-jetty.dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
FROM gradle:8.13-jdk21 AS build
WORKDIR /ktor
COPY ktor/pom.xml pom.xml
COPY ktor/src src
RUN mvn clean package -q
COPY ktor/ ./
RUN chmod +x gradlew && ./gradlew --no-daemon clean jettyBundle

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

EXPOSE 9090

Expand Down
23 changes: 18 additions & 5 deletions frameworks/Kotlin/ktor/ktor-pgclient/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
application
kotlin("jvm") version "2.0.21"
kotlin("plugin.serialization") version "2.0.0"
id("com.github.johnrengelman.shadow") version "8.1.0"
kotlin("jvm") version "2.1.21"
kotlin("plugin.serialization") version "2.1.21"
id("com.gradleup.shadow") version "8.3.9"
}

group = "org.jetbrains.ktor"
Expand All @@ -16,8 +19,8 @@ application {
mainClass = "io.ktor.server.netty.EngineMain"
}

val ktor_version = "3.1.2"
val vertx_version = "4.5.11"
val ktor_version = "3.3.3"
val vertx_version = "5.0.5"

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
Expand All @@ -36,6 +39,16 @@ java {
}
}

kotlin {
jvmToolchain(21)
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}

tasks.shadowJar {
archiveBaseName.set("ktor-pgclient")
archiveClassifier.set("")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
67 changes: 43 additions & 24 deletions frameworks/Kotlin/ktor/ktor-pgclient/src/main/kotlin/main.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import io.ktor.http.ContentType
import io.ktor.http.content.TextContent
import io.ktor.server.application.*
import io.ktor.server.html.*
import io.ktor.server.plugins.defaultheaders.*
Expand All @@ -8,23 +10,32 @@ import io.vertx.pgclient.PgBuilder
import io.vertx.pgclient.PgConnectOptions
import io.vertx.sqlclient.PoolOptions
import io.vertx.sqlclient.Tuple
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.html.*
import java.util.concurrent.ThreadLocalRandom

val rand: ThreadLocalRandom
get() = ThreadLocalRandom.current()

private const val HELLO_WORLD = "Hello, World!"
private const val WORLD_ROWS = 10_000

private fun nextWorldId(): Int = rand.nextInt(1, WORLD_ROWS + 1)

interface Repository {
suspend fun getWorld(): World
suspend fun getWorlds(count: Int): List<World>
suspend fun getFortunes(): List<Fortune>
suspend fun updateWorlds(worlds: List<World>)
}

class PgclientRepository : Repository {
companion object {
private const val FORTUNES_QUERY = "select id, message from FORTUNE"
private const val SELECT_WORLD_QUERY = "SELECT id, randomnumber from WORLD where id=$1"
private const val UPDATE_WORLD_QUERY = "UPDATE WORLD SET randomnumber=$1 WHERE id=$2"
private const val SELECT_WORLD_QUERY = "SELECT id, randomnumber from WORLD where id=\$1"
private const val UPDATE_WORLD_QUERY = "UPDATE WORLD SET randomnumber=\$1 WHERE id=\$2"
}

private val connectOptions =
Expand All @@ -38,35 +49,43 @@ class PgclientRepository : Repository {
pipeliningLimit = 100000
}

private val poolSize = Runtime.getRuntime().availableProcessors() * 2
private val poolOptions = PoolOptions()
.setMaxSize(poolSize)
.setMaxWaitQueueSize(poolSize * 2)
private val client = PgBuilder.client()
.with(poolOptions)
.connectingTo(connectOptions)
.build()

private val selectWorldStatement = client.preparedQuery(SELECT_WORLD_QUERY)
private val updateWorldStatement = client.preparedQuery(UPDATE_WORLD_QUERY)
private val fortunesStatement = client.preparedQuery(FORTUNES_QUERY)

override suspend fun getFortunes(): List<Fortune> {
val results = client.preparedQuery(FORTUNES_QUERY).execute().coAwait()
val results = fortunesStatement.execute().coAwait()
return results.map { Fortune(it.getInteger(0), it.getString(1)) }
}

override suspend fun getWorld(): World {
val worldId = rand.nextInt(1, 10001)
val result =
client
.preparedQuery(SELECT_WORLD_QUERY)
.execute(Tuple.of(worldId))
.coAwait()
override suspend fun getWorld(): World =
getWorlds(1).first()

override suspend fun getWorlds(count: Int): List<World> = coroutineScope {
List(count) {
async { fetchWorld(nextWorldId()) }
}.awaitAll()
}

private suspend fun fetchWorld(id: Int): World {
val result = selectWorldStatement.execute(Tuple.of(id)).coAwait()
val row = result.first()
return World(row.getInteger(0), row.getInteger(1)!!)
}

override suspend fun updateWorlds(worlds: List<World>) {
// Worlds should be sorted before being batch-updated with to avoid data race and deadlocks.
if (worlds.isEmpty()) return
val batch = worlds.sortedBy { it.id }.map { Tuple.of(it.randomNumber, it.id) }
client
.preparedQuery(UPDATE_WORLD_QUERY)
.executeBatch(batch)
.coAwait()
updateWorldStatement.executeBatch(batch).coAwait()
}
}

Expand Down Expand Up @@ -115,11 +134,11 @@ fun Application.main() {
install(DefaultHeaders)
routing {
get("/plaintext") {
call.respondText("Hello, World!")
call.respond(TextContent(HELLO_WORLD, ContentType.Text.Plain))
}

get("/json") {
call.respondJson(Message("Hello, World!"))
call.respondJson(Message(HELLO_WORLD))
}

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

get("/query") {
val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
val worlds = List(queries) { db.getWorld() }
val worlds = db.getWorlds(queries)
call.respondJson(worlds)
}

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

get("/updates") {
val queries = call.parameters["queries"]?.toBoxedInt(1..500) ?: 1
val worlds = List(queries) { db.getWorld() }
val newWorlds = worlds.map { it.copy(randomNumber = rand.nextInt(1, 10001)) }

db.updateWorlds(newWorlds)

call.respondJson(newWorlds)
val worlds = db.getWorlds(queries).map { world ->
world.randomNumber = nextWorldId()
world
}
db.updateWorlds(worlds)
call.respondJson(worlds)
}
}
}
Loading
Loading