|
@@ -1,7 +1,6 @@
|
|
|
package org.jetbrains.ktor.benchmarks
|
|
|
|
|
|
import com.google.gson.*
|
|
|
-import com.mysql.jdbc.*
|
|
|
import com.zaxxer.hikari.*
|
|
|
import io.ktor.application.*
|
|
|
import io.ktor.content.*
|
|
@@ -14,8 +13,6 @@ import kotlinx.coroutines.experimental.*
|
|
|
import kotlinx.coroutines.experimental.scheduling.*
|
|
|
import kotlinx.html.*
|
|
|
import java.util.concurrent.*
|
|
|
-import java.util.concurrent.atomic.*
|
|
|
-import javax.sql.*
|
|
|
|
|
|
data class Message(val message: String = "Hello, World!")
|
|
|
data class World(val id: Int, var randomNumber: Int)
|
|
@@ -23,12 +20,11 @@ data class Fortune(val id: Int, var message: String)
|
|
|
|
|
|
fun Application.main() {
|
|
|
val gson = GsonBuilder().create()
|
|
|
- val DbRows = 10000
|
|
|
- val pool by lazy {
|
|
|
- hikari()
|
|
|
- }
|
|
|
|
|
|
- val databaseDispatcher by lazy { ExperimentalCoroutineDispatcher().blocking(32) }
|
|
|
+ val dbRows = 10000
|
|
|
+ val poolSize = 48
|
|
|
+ val pool by lazy { HikariDataSource(HikariConfig().apply { configurePostgres(poolSize) }) }
|
|
|
+ val databaseDispatcher by lazy { ExperimentalCoroutineDispatcher().blocking(poolSize) }
|
|
|
|
|
|
install(DefaultHeaders)
|
|
|
|
|
@@ -40,44 +36,40 @@ fun Application.main() {
|
|
|
}
|
|
|
|
|
|
get("/json") {
|
|
|
- val content = TextContent(gson.toJson(Message()), ContentType.Application.Json, HttpStatusCode.OK)
|
|
|
- call.respond(content)
|
|
|
+ call.respondText(gson.toJson(Message()), ContentType.Application.Json, HttpStatusCode.OK)
|
|
|
}
|
|
|
|
|
|
get("/db") {
|
|
|
- val response = withContext(databaseDispatcher) {
|
|
|
- pool.connection.use { connection ->
|
|
|
- val random = ThreadLocalRandom.current()
|
|
|
- val queries = call.queries()
|
|
|
- val result = mutableListOf<World>()
|
|
|
+ val random = ThreadLocalRandom.current()
|
|
|
+ val queries = call.queries()
|
|
|
+ val result = ArrayList<World>(queries ?: 1)
|
|
|
|
|
|
- connection.prepareStatement("SELECT * FROM World WHERE id = ?").use { statement ->
|
|
|
+ withContext(databaseDispatcher) {
|
|
|
+ pool.connection.use { connection ->
|
|
|
+ connection.prepareStatement("SELECT id, randomNumber FROM World WHERE id = ?").use { statement ->
|
|
|
for (i in 1..(queries ?: 1)) {
|
|
|
- statement.setInt(1, random.nextInt(DbRows) + 1)
|
|
|
-
|
|
|
+ statement.setInt(1, random.nextInt(dbRows) + 1)
|
|
|
statement.executeQuery().use { rs ->
|
|
|
while (rs.next()) {
|
|
|
- result += World(rs.getInt("id"), rs.getInt("randomNumber"))
|
|
|
+ result += World(rs.getInt(1), rs.getInt(2))
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- TextContent(gson.toJson(when (queries) {
|
|
|
- null -> result.single()
|
|
|
- else -> result
|
|
|
- }), ContentType.Application.Json, HttpStatusCode.OK)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- call.respond(response)
|
|
|
+ call.respondText(gson.toJson(when (queries) {
|
|
|
+ null -> result.single()
|
|
|
+ else -> result
|
|
|
+ }), ContentType.Application.Json, HttpStatusCode.OK)
|
|
|
}
|
|
|
|
|
|
get("/fortunes") {
|
|
|
val result = mutableListOf<Fortune>()
|
|
|
withContext(databaseDispatcher) {
|
|
|
pool.connection.use { connection ->
|
|
|
- connection.prepareStatement("select id, message from fortune").use { statement ->
|
|
|
+ connection.prepareStatement("SELECT id, message FROM fortune").use { statement ->
|
|
|
statement.executeQuery().use { rs ->
|
|
|
while (rs.next()) {
|
|
|
result += Fortune(rs.getInt(1), rs.getString(2))
|
|
@@ -109,15 +101,15 @@ fun Application.main() {
|
|
|
}
|
|
|
|
|
|
get("/updates") {
|
|
|
- val t = withContext(databaseDispatcher) {
|
|
|
- pool.connection.use { connection ->
|
|
|
- val queries = call.queries()
|
|
|
- val random = ThreadLocalRandom.current()
|
|
|
- val result = mutableListOf<World>()
|
|
|
+ val queries = call.queries()
|
|
|
+ val random = ThreadLocalRandom.current()
|
|
|
+ val result = ArrayList<World>(queries ?: 1)
|
|
|
|
|
|
+ withContext(databaseDispatcher) {
|
|
|
+ pool.connection.use { connection ->
|
|
|
connection.prepareStatement("SELECT id, randomNumber FROM World WHERE id = ?").use { statement ->
|
|
|
for (i in 1..(queries ?: 1)) {
|
|
|
- statement.setInt(1, random.nextInt(DbRows) + 1)
|
|
|
+ statement.setInt(1, random.nextInt(dbRows) + 1)
|
|
|
|
|
|
statement.executeQuery().use { rs ->
|
|
|
while (rs.next()) {
|
|
@@ -128,7 +120,7 @@ fun Application.main() {
|
|
|
|
|
|
}
|
|
|
|
|
|
- result.forEach { it.randomNumber = random.nextInt(DbRows) + 1 }
|
|
|
+ result.forEach { it.randomNumber = random.nextInt(dbRows) + 1 }
|
|
|
|
|
|
connection.prepareStatement("UPDATE World SET randomNumber = ? WHERE id = ?").use { updateStatement ->
|
|
|
for ((id, randomNumber) in result) {
|
|
@@ -139,34 +131,46 @@ fun Application.main() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- TextContent(gson.toJson(when (queries) {
|
|
|
- null -> result.single()
|
|
|
- else -> result
|
|
|
- }), ContentType.Application.Json, HttpStatusCode.OK)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- call.respond(t)
|
|
|
+ call.respondText(gson.toJson(when (queries) {
|
|
|
+ null -> result.single()
|
|
|
+ else -> result
|
|
|
+ }), ContentType.Application.Json, HttpStatusCode.OK)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+fun HikariConfig.configurePostgres(poolSize: Int) {
|
|
|
+ jdbcUrl = "jdbc:postgresql://tfb-database/hello_world?useSSL=false"
|
|
|
+ driverClassName = org.postgresql.Driver::class.java.name
|
|
|
+
|
|
|
+ configureCommon(poolSize)
|
|
|
+}
|
|
|
+
|
|
|
+fun HikariConfig.configureCommon(poolSize: Int) {
|
|
|
+ username = "benchmarkdbuser"
|
|
|
+ password = "benchmarkdbpass"
|
|
|
+ addDataSourceProperty("cacheServerConfiguration", true)
|
|
|
+ addDataSourceProperty("cachePrepStmts", "true")
|
|
|
+ addDataSourceProperty("useUnbufferedInput", "false")
|
|
|
+ addDataSourceProperty("prepStmtCacheSize", "4096")
|
|
|
+ addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
|
|
+ connectionTimeout = 10000
|
|
|
+ maximumPoolSize = poolSize
|
|
|
+ minimumIdle = poolSize
|
|
|
+}
|
|
|
+
|
|
|
+fun HikariConfig.configureMySql(poolSize: Int) {
|
|
|
+ jdbcUrl = "jdbc:mysql://tfb-database:3306/hello_world?useSSL=false"
|
|
|
+ driverClassName = com.mysql.jdbc.Driver::class.java.name
|
|
|
+ configureCommon(poolSize)
|
|
|
+}
|
|
|
+
|
|
|
fun ApplicationCall.queries() = try {
|
|
|
request.queryParameters["queries"]?.toInt()?.coerceIn(1, 500)
|
|
|
} catch (nfe: NumberFormatException) {
|
|
|
1
|
|
|
}
|
|
|
|
|
|
-private fun hikari(): DataSource {
|
|
|
- val config = HikariConfig()
|
|
|
- config.jdbcUrl = "jdbc:mysql://tfb-database:3306/hello_world?useSSL=false"
|
|
|
- config.username = "benchmarkdbuser"
|
|
|
- config.password = "benchmarkdbpass"
|
|
|
- config.addDataSourceProperty("cachePrepStmts", "true")
|
|
|
- config.addDataSourceProperty("prepStmtCacheSize", "250")
|
|
|
- config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
|
|
- config.driverClassName = Driver::class.java.name
|
|
|
- config.connectionTimeout = 10000
|
|
|
- config.maximumPoolSize = 100
|
|
|
- return HikariDataSource(config)
|
|
|
-}
|