@@ -10,7 +10,6 @@ import io.ktor.server.plugins.defaultheaders.*
1010import io.ktor.server.response.*
1111import io.ktor.server.routing.*
1212import kotlinx.coroutines.Dispatchers
13- import kotlinx.coroutines.withContext
1413import kotlinx.html.*
1514import kotlinx.serialization.Serializable
1615import kotlinx.serialization.json.Json
@@ -22,8 +21,9 @@ import org.jetbrains.exposed.sql.Database
2221import org.jetbrains.exposed.sql.ResultRow
2322import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
2423import 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
2727import java.util.concurrent.ThreadLocalRandom
2828
2929@Serializable
@@ -73,12 +73,12 @@ fun main(args: Array<String>) {
7373}
7474
7575fun 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+
205201fun 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
225221fun ApplicationCall.queries () =
226222 request.queryParameters[" queries" ]?.toIntOrNull()?.coerceIn(1 , 500 ) ? : 1
223+
0 commit comments