Browse Source

added r2dbc

Ilya Nemtsev 7 months ago
parent
commit
7286bd06d2

+ 23 - 0
frameworks/Kotlin/ktor/benchmark_config.json

@@ -25,6 +25,29 @@
         "notes": "http://ktor.io/",
         "versus": "netty"
       },
+      "r2dbc": {
+        "plaintext_url": "/plaintext",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "fortune_url": "/fortunes",
+
+        "port": 9090,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "ktor",
+        "language": "Kotlin",
+        "orm": "Raw",
+        "platform": "Netty",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "ktor-netty-r2dbc",
+        "notes": "http://ktor.io/",
+        "versus": "netty"
+      },
       "jetty": {
         "plaintext_url": "/plaintext",
         "json_url": "/json",

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

@@ -9,7 +9,7 @@ repositories {
     mavenCentral()
 }
 
-val ktorVersion = "3.0.1"
+val ktorVersion = "3.0.3"
 val kotlinxSerializationVersion = "1.7.3"
 val exposedVersion = "0.56.0"
 

+ 1 - 1
frameworks/Kotlin/ktor/ktor-exposed/gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
 networkTimeout=10000
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 13 - 0
frameworks/Kotlin/ktor/ktor-r2dbc.dockerfile

@@ -0,0 +1,13 @@
+FROM maven:3.9.9-amazoncorretto-21-debian-bookworm as maven
+WORKDIR /ktor
+COPY ktor/pom.xml pom.xml
+COPY ktor/src src
+RUN mvn clean package -q
+
+FROM amazoncorretto:21-al2023-headless
+WORKDIR /ktor
+COPY --from=maven /ktor/target/tech-empower-framework-benchmark-1.0-SNAPSHOT-netty-bundle.jar app.jar
+
+EXPOSE 9090
+
+CMD ["java", "-server","-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AlwaysPreTouch", "-jar", "app.jar"]

+ 50 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/README.md

@@ -0,0 +1,50 @@
+# Ktor
+
+Ktor is a framework for building servers and clients in connected systems using Kotlin programming language.
+More information is available at [ktor.io](http://ktor.io). 
+
+# Setup
+
+* Java 21
+* Postgres server
+
+# Requirements
+
+* Maven 3
+* JDK 21
+* Kotlin
+* ktor
+* netty
+* R2DBC
+
+Maven is downloaded automatically via Maven Wrapper script (`mvnw`), add dependencies are specified in `pom.xml` so will be downloaded automatically from maven central and jcenter repositories.
+
+# Deployment
+
+Run maven to build a bundle
+
+```bash
+./mvnw package
+```
+
+Once bundle build complete and mysql server is running you can launch the application
+
+```bash
+java -jar target/tech-empower-framework-benchmark-1.0-SNAPSHOT.jar
+```
+
+Please note that the server holds tty so you may need nohup. See `setup.sh` for details.
+
+# Contact
+
+[Leonid Stashevsky](https://github.com/e5l)
+
+[Sergey Mashkov](https://github.com/cy6erGn0m)
+
+[Ilya Ryzhenkov](https://github.com/orangy)
+
+[Ilya Nemtsev](https://github.com/inemtsev)
+
+Slack ktor channel https://kotlinlang.slack.com/messages/ktor (you need an [invite](http://slack.kotlinlang.org/) to join)
+
+

+ 163 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/pom.xml

@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.jetbrains.ktor</groupId>
+    <artifactId>tech-empower-framework-benchmark</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <name>org.jetbrains.ktor tech-empower-framework-benchmark</name>
+
+    <properties>
+        <kotlin.version>2.0.21</kotlin.version>
+        <kotlin.coroutines.version>1.10.1</kotlin.coroutines.version>
+        <ktor.version>3.0.3</ktor.version>
+        <serialization.version>1.7.3</serialization.version>
+        <kotlinx.html.version>0.11.0</kotlinx.html.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <logback.version>1.2.13</logback.version>
+        <postgresql.version>42.7.4</postgresql.version>
+        <r2dbc.version>1.0.7.RELEASE</r2dbc.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-reflect</artifactId>
+            <version>${kotlin.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-serialization-core</artifactId>
+            <version>${serialization.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-serialization-json</artifactId>
+            <version>${serialization.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-html-jvm</artifactId>
+            <version>${kotlinx.html.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-coroutines-core</artifactId>
+            <version>${kotlin.coroutines.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlinx</groupId>
+            <artifactId>kotlinx-coroutines-reactor</artifactId>
+            <version>${kotlin.coroutines.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.ktor</groupId>
+            <artifactId>ktor-server-default-headers-jvm</artifactId>
+            <version>${ktor.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.ktor</groupId>
+            <artifactId>ktor-server-html-builder-jvm</artifactId>
+            <version>${ktor.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>r2dbc-postgresql</artifactId>
+            <version>${r2dbc.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>${logback.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.ktor</groupId>
+            <artifactId>ktor-server-netty-jvm</artifactId>
+            <version>${ktor.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <sourceDirectory>src/main/kotlin</sourceDirectory>
+
+        <plugins>
+            <plugin>
+                <groupId>org.jetbrains.kotlin</groupId>
+                <artifactId>kotlin-maven-plugin</artifactId>
+                <version>${kotlin.version}</version>
+                <executions>
+                    <execution>
+                        <id>compile</id>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>compile</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>test-compile</id>
+                        <phase>test-compile</phase>
+                        <goals>
+                            <goal>test-compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <compilerPlugins>
+                        <plugin>kotlinx-serialization</plugin>
+                    </compilerPlugins>
+                </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.jetbrains.kotlin</groupId>
+                        <artifactId>kotlin-maven-serialization</artifactId>
+                        <version>${kotlin.version}</version>
+                    </dependency>
+                </dependencies>
+            </plugin>
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>default-jar</id>
+                        <phase>none</phase>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.0.0</version>
+
+                <executions>
+                    <execution>
+                        <id>netty</id>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+
+                        <phase>package</phase>
+
+                        <configuration>
+                            <descriptors>
+                                <descriptor>src/main/assembly/netty-bundle.xml</descriptor>
+                            </descriptors>
+                            <archive>
+                                <manifest>
+                                    <mainClass>io.ktor.server.netty.EngineMain</mainClass>
+                                </manifest>
+                            </archive>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 30 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/assembly/cio-bundle.xml

@@ -0,0 +1,30 @@
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
+    <id>cio-bundle</id>
+    <formats>
+        <format>jar</format>
+    </formats>
+
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <dependencySets>
+        <dependencySet>
+            <unpack>true</unpack>
+            <scope>runtime</scope>
+
+            <useTransitiveDependencies>true</useTransitiveDependencies>
+
+            <excludes>
+                <exclude>*:ktor-server-netty</exclude>
+                <exclude>*:ktor-server-jetty</exclude>
+            </excludes>
+        </dependencySet>
+    </dependencySets>
+
+    <fileSets>
+        <fileSet>
+            <directory>${project.build.outputDirectory}</directory>
+            <outputDirectory>/</outputDirectory>
+        </fileSet>
+    </fileSets>
+</assembly>

+ 30 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/assembly/jetty-bundle.xml

@@ -0,0 +1,30 @@
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
+    <id>jetty-bundle</id>
+    <formats>
+        <format>jar</format>
+    </formats>
+
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <dependencySets>
+        <dependencySet>
+            <unpack>true</unpack>
+            <scope>runtime</scope>
+
+            <useTransitiveDependencies>true</useTransitiveDependencies>
+
+            <excludes>
+                <exclude>*:ktor-server-netty</exclude>
+                <exclude>*:ktor-server-cio</exclude>
+            </excludes>
+        </dependencySet>
+    </dependencySets>
+
+    <fileSets>
+        <fileSet>
+            <directory>${project.build.outputDirectory}</directory>
+            <outputDirectory>/</outputDirectory>
+        </fileSet>
+    </fileSets>
+</assembly>

+ 29 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/assembly/netty-bundle.xml

@@ -0,0 +1,29 @@
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
+    <id>netty-bundle</id>
+
+    <formats>
+        <format>jar</format>
+    </formats>
+
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <dependencySets>
+        <dependencySet>
+            <unpack>true</unpack>
+            <scope>runtime</scope>
+
+            <excludes>
+                <exclude>*:ktor-server-jetty</exclude>
+                <exclude>*:ktor-server-cio</exclude>
+            </excludes>
+        </dependencySet>
+    </dependencySets>
+
+    <fileSets>
+        <fileSet>
+            <directory>${project.build.outputDirectory}</directory>
+            <outputDirectory>/</outputDirectory>
+        </fileSet>
+    </fileSets>
+</assembly>

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

@@ -0,0 +1,169 @@
+package org.jetbrains.ktor.benchmarks
+
+import io.ktor.http.*
+import io.ktor.http.content.*
+import io.ktor.server.application.*
+import io.ktor.server.config.*
+import io.ktor.server.html.*
+import io.ktor.server.plugins.defaultheaders.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import io.r2dbc.spi.ConnectionFactories
+import io.r2dbc.spi.ConnectionFactory
+import io.r2dbc.spi.ConnectionFactoryOptions
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import kotlinx.html.*
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.jetbrains.ktor.benchmarks.Constants.DB_ROWS
+import org.jetbrains.ktor.benchmarks.Constants.FORTUNES_QUERY
+import org.jetbrains.ktor.benchmarks.Constants.UPDATE_QUERY
+import org.jetbrains.ktor.benchmarks.Constants.WORLD_QUERY
+import org.jetbrains.ktor.benchmarks.models.Fortune
+import org.jetbrains.ktor.benchmarks.models.Message
+import org.jetbrains.ktor.benchmarks.models.World
+import reactor.core.publisher.Flux
+import reactor.core.publisher.Mono
+import kotlin.random.Random
+
+fun Application.main() {
+    val config = ApplicationConfig("application.conf")
+    val dbConnFactory = configurePostgresR2DBC(config)
+
+    install(DefaultHeaders)
+
+    val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
+
+    routing {
+        get("/plaintext") {
+            call.respond(helloWorldContent)
+        }
+
+        get("/json") {
+            call.respondText(Json.encodeToString(Message("Hello, world!")), ContentType.Application.Json)
+        }
+
+        get("/db") {
+            val random = Random.Default
+            val request = getWorld(dbConnFactory, random)
+            val result = request.awaitFirstOrNull()
+
+            call.respondText(Json.encodeToString(result), ContentType.Application.Json)
+        }
+
+        suspend fun selectWorlds(queries: Int, random: Random): List<World> = coroutineScope {
+            val result = ArrayList<Deferred<World?>>(queries)
+
+            repeat(queries) {
+                val deferred = async {
+                    getWorld(dbConnFactory, random).awaitFirstOrNull()
+                }
+                result.add(deferred)
+            }
+
+            result.awaitAll().filterNotNull()
+        }
+
+        get("/queries") {
+            val queries = call.queries()
+            val random = Random.Default
+
+            val result = selectWorlds(queries, random)
+
+            call.respondText(Json.encodeToString(result), ContentType.Application.Json)
+        }
+
+        get("/fortunes") {
+            val result = mutableListOf<Fortune>()
+
+            val request = Flux.usingWhen(dbConnFactory.create(), { connection ->
+                Flux.from(connection.createStatement(FORTUNES_QUERY).execute()).flatMap { r ->
+                    Flux.from(r.map { row, _ ->
+                        Fortune(
+                            row.get(0, Int::class.java)!!, row.get(1, String::class.java)!!
+                        )
+                    })
+                }
+            }, { connection -> connection.close() })
+
+            request.collectList().awaitFirstOrNull()?.let { result.addAll(it) }
+
+            result.add(Fortune(0, "Additional fortune added at request time."))
+            result.sortBy { it.message }
+            call.respondHtml {
+                head { title { +"Fortunes" } }
+                body {
+                    table {
+                        tr {
+                            th { +"id" }
+                            th { +"message" }
+                        }
+                        for (fortune in result) {
+                            tr {
+                                td { +fortune.id.toString() }
+                                td { +fortune.message }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        get("/updates") {
+            val queries = call.queries()
+            val random = Random.Default
+
+            val result = coroutineScope {
+                val worlds = selectWorlds(queries, random)
+
+                worlds.forEach { it.randomNumber = random.nextInt(DB_ROWS) + 1 }
+
+                val updateRequests = worlds.map { world ->
+                    Mono.usingWhen(dbConnFactory.create(), { connection ->
+                        Mono.from(
+                            connection.createStatement(UPDATE_QUERY).bind(0, world.randomNumber).bind(1, world.id)
+                                .execute()
+                        ).flatMap { Mono.from(it.rowsUpdated) }
+                    }, { connection -> connection.close() })
+                }
+
+                Flux.merge(updateRequests).collectList().awaitFirstOrNull()
+                worlds
+            }
+
+            call.respondText(Json.encodeToString(result), ContentType.Application.Json)
+        }
+    }
+}
+
+private fun getWorld(
+    dbConnFactory: ConnectionFactory, random: Random
+): Mono<World> = Mono.usingWhen(dbConnFactory.create(), { connection ->
+    Mono.from(connection.createStatement(WORLD_QUERY).bind(0, random.nextInt(DB_ROWS) + 1).execute()).flatMap { r ->
+        Mono.from(r.map { row, _ ->
+            World(
+                row.get(0, Int::class.java)!!, row.get(1, Int::class.java)!!
+            )
+        })
+    }
+}, { connection -> connection.close() })
+
+private fun configurePostgresR2DBC(config: ApplicationConfig): ConnectionFactory {
+    val options = ConnectionFactoryOptions.builder().option(ConnectionFactoryOptions.DRIVER, "database.driver")
+        .option(ConnectionFactoryOptions.DATABASE, config.property("database.url").getString())
+        .option(ConnectionFactoryOptions.USER, config.property("database.user").getString())
+        .option(ConnectionFactoryOptions.PASSWORD, config.property("database.password").getString()).build()
+
+    return ConnectionFactories.get(options)
+}
+
+private fun ApplicationCall.queries() = request.queryParameters["queries"]?.toIntOrNull()?.coerceIn(1, 500) ?: 1
+
+
+object Constants {
+    const val WORLD_QUERY = "SELECT id, randomNumber FROM World WHERE id = ?"
+    const val FORTUNES_QUERY = "SELECT id, message FROM fortune"
+    const val UPDATE_QUERY = "UPDATE World SET randomNumber = ? WHERE id = ?"
+    const val DB_ROWS = 10000
+}

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

@@ -0,0 +1,6 @@
+package org.jetbrains.ktor.benchmarks.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class Fortune(val id: Int, var message: String)

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

@@ -0,0 +1,6 @@
+package org.jetbrains.ktor.benchmarks.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class Message(val message: String)

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

@@ -0,0 +1,6 @@
+package org.jetbrains.ktor.benchmarks.models
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class World(val id: Int, var randomNumber: Int)

+ 20 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/application.conf

@@ -0,0 +1,20 @@
+ktor {
+    deployment {
+        port = 9090
+        autoreload = false
+        watch = [ ]
+        shareWorkGroup = true
+    }
+
+    application {
+        modules = [ org.jetbrains.ktor.benchmarks.HelloKt.main ]
+    }
+}
+
+database {
+    driver = "org.postgresql.Driver"
+    url = "url: r2dbc:pool://tfb-database:5432/hello_world?loggerLevel=OFF&sslmode=disable"
+    poolsize = 512
+    username = "benchmarkdbuser"
+    password = "benchmarkdbpass"
+}

+ 21 - 0
frameworks/Kotlin/ktor/ktor-r2dbc/src/main/resources/logback.xml

@@ -0,0 +1,21 @@
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+     <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
+         <neverBlock>true</neverBlock>
+         <appender-ref ref="STDOUT" />
+     </appender>
+
+
+    <root level="INFO">
+        <appender-ref ref="ASYNC"/>
+    </root>
+
+    <logger name="org.eclipse.jetty" level="INFO"/>
+    <logger name="io.netty" level="INFO"/>
+
+</configuration>

+ 3 - 3
frameworks/Kotlin/ktor/ktor/README.md

@@ -5,13 +5,13 @@ More information is available at [ktor.io](http://ktor.io).
 
 # Setup
 
-* Java 17
-* MySQL server
+* Java 21
+* Postgres server
 
 # Requirements
 
 * Maven 3
-* JDK 17
+* JDK 21
 * Kotlin
 * ktor
 * netty 

+ 1 - 1
frameworks/Kotlin/ktor/ktor/pom.xml

@@ -13,7 +13,7 @@
 
     <properties>
         <kotlin.version>2.0.21</kotlin.version>
-        <ktor.version>3.0.2</ktor.version>
+        <ktor.version>3.0.3</ktor.version>
         <serialization.version>1.7.3</serialization.version>
         <kotlinx.html.version>0.11.0</kotlinx.html.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

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

@@ -38,7 +38,7 @@ fun Application.main() {
 
     install(DefaultHeaders)
 
-    val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain).also { it.contentLength }
+    val helloWorldContent = TextContent("Hello, World!", ContentType.Text.Plain)
 
     routing {
         get("/plaintext") {