Browse Source

Update dependencies, switch to more efficient JSON marshaller, tune configuration and JVM options for better throughput (#3932)

* Update dependencies, switch to more efficient JSON marshaller, tune configuration add JVM options for better throughput

* Fix broken list of params in the command that launches a server
Andriy Plokhotnyuk 7 years ago
parent
commit
9c065a2bbe
19 changed files with 76 additions and 80 deletions
  1. 2 2
      frameworks/Scala/akka-http/akka-http.dockerfile
  2. 6 9
      frameworks/Scala/akka-http/build.sbt
  3. 1 1
      frameworks/Scala/akka-http/project/build.properties
  4. 1 2
      frameworks/Scala/akka-http/project/plugins.sbt
  5. 6 3
      frameworks/Scala/akka-http/src/main/resources/application.conf
  6. 7 6
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/App.scala
  7. 2 5
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/Bootstrap.scala
  8. 3 0
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/Infrastructure.scala
  9. 0 1
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/RequestMapping.scala
  10. 1 1
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/Templating.scala
  11. 3 1
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/datastore/DataStore.scala
  12. 6 5
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/datastore/MySqlDataStore.scala
  13. 5 4
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/entity/World.scala
  14. 4 4
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/DbHandler.scala
  15. 3 3
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/FortunesHandler.scala
  16. 9 12
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/JsonHandler.scala
  17. 8 11
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/PlaintextHandler.scala
  18. 4 5
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/QueriesHandler.scala
  19. 5 5
      frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/UpdatesHandler.scala

+ 2 - 2
frameworks/Scala/akka-http/akka-http.dockerfile

@@ -3,5 +3,5 @@ WORKDIR /akka-http
 COPY project project
 COPY src src
 COPY build.sbt build.sbt
-RUN sbt -batch 'universal:stage'
-CMD ["target/universal/stage/bin/akka-http-benchmark", "-Dakka.http.benchmark.mysql.dbhost=tfb-database"]
+RUN sbt -batch clean compile stage
+CMD ["target/universal/stage/bin/akka-http-benchmark", "-Dakka.http.benchmark.mysql.dbhost=tfb-database", "-J-server", "-J-Xms2g", "-J-Xmx2g", "-J-XX:NewSize=1g", "-J-XX:MaxNewSize=1g", "-J-XX:InitialCodeCacheSize=256m", "-J-XX:ReservedCodeCacheSize=256m", "-J-XX:+UseParallelGC", "-J-XX:-UseBiasedLocking", "-J-XX:+AlwaysPreTouch"]

+ 6 - 9
frameworks/Scala/akka-http/build.sbt

@@ -1,24 +1,21 @@
 enablePlugins(JavaAppPackaging)
 
-val akkaVersion = "2.5.7"
-val akkaHttpVersion = "10.0.11"
-
 organization := "com.typesafe.akka"
 
 name := "akka-http-benchmark"
 
 version := "0.0.1-SNAPSHOT"
 
-scalaVersion := "2.12.5"
+scalaVersion := "2.12.6"
 
 resolvers += "Akka Snapshot Repository" at "http://repo.akka.io/snapshots/"
 
 libraryDependencies ++= Seq(
-  "com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
-  "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
-  "com.typesafe.akka" %% "akka-stream" % akkaVersion,
-  "mysql" % "mysql-connector-java" % "5.1.45",
-  "com.zaxxer" % "HikariCP" % "2.7.4",
+  "com.typesafe.akka" %% "akka-http" % "10.1.3",
+  "com.typesafe.akka" %% "akka-stream" % "2.5.14",
+  "de.heikoseeberger" %% "akka-http-jsoniter-scala" % "1.21.0",
+  "mysql" % "mysql-connector-java" % "5.1.46",
+  "com.zaxxer" % "HikariCP" % "2.7.9",
   "org.scalatra.scalate" %% "scalate-core" % "1.8.0",
   "org.scalatest" %% "scalatest" % "3.0.4" % "test"
 )

+ 1 - 1
frameworks/Scala/akka-http/project/build.properties

@@ -1 +1 @@
-sbt.version=1.1.2
+sbt.version=1.1.6

+ 1 - 2
frameworks/Scala/akka-http/project/plugins.sbt

@@ -4,5 +4,4 @@ classpathTypes += "maven-plugin"
 
 addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2")
 addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.2")
-addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC13")
-
+addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.3")

+ 6 - 3
frameworks/Scala/akka-http/src/main/resources/application.conf

@@ -4,9 +4,9 @@ akka {
       fork-join-executor {
         parallelism-min = 1    # don't constrain parallelism statically
         parallelism-max = 64   # --
-
         parallelism-factor = 1 # one thread per core is enough
       }
+      throughput = 64
     }
   }
   http {
@@ -19,9 +19,12 @@ akka {
         dbuser: "benchmarkdbuser"
         dbpass: "benchmarkdbpass"
         jdbc-url: "jdbc:mysql://"${akka.http.benchmark.mysql.dbhost}":"${akka.http.benchmark.mysql.dbport}"/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true&useSSL=false"
-        connection-pool-size: 100
-        thread-pool-size: 100
+        connection-pool-size: 128
+        thread-pool-size: 128
       }
     }
+    server {
+      backlog = 1024
+    }
   }
 }

+ 7 - 6
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/App.scala

@@ -13,13 +13,14 @@ import org.fusesource.scalate.TemplateEngine
 
 import scala.concurrent.ExecutionContext
 
-class App extends Infrastructure with RandomGenerator with MySqlDataStore with PlaintextHandler with JsonHandler with DbHandler with QueriesHandler with FortunesHandler with UpdatesHandler with RequestMapping with BenchmarkBootstrap with Templating {
-  lazy val templateEngine = new TemplateEngine()
+class App extends Infrastructure with RandomGenerator with MySqlDataStore with PlaintextHandler with JsonHandler with DbHandler
+  with QueriesHandler with FortunesHandler with UpdatesHandler with RequestMapping with BenchmarkBootstrap with Templating {
 
-  implicit lazy val system: ActorSystem = ActorSystem("akka-http-benchmark")
-  lazy val executionContext: ExecutionContext = system.dispatcher
-  lazy val materializer: Materializer = ActorMaterializer()
-  lazy val appConfig: Config = ConfigFactory.load
+  val templateEngine = new TemplateEngine()
+  implicit val system: ActorSystem = ActorSystem("akka-http-benchmark")
+  val executionContext: ExecutionContext = system.dispatcher
+  val materializer: Materializer = ActorMaterializer()
+  val appConfig: Config = ConfigFactory.load
 
   def layout(uri: String, attributes: Map[String, Any], extraBindings: Traversable[Binding]): String =
     templateEngine.layout(uri, attributes, extraBindings)

+ 2 - 5
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/Bootstrap.scala

@@ -8,13 +8,10 @@ trait Bootstrap {
 }
 
 trait BenchmarkBootstrap extends Bootstrap { _: Infrastructure with RequestMapping =>
-  override def run(): Unit = {
-    val routeHandler = Route.asyncHandler(asRoute)
-
+  override def run(): Unit =
     Http().bindAndHandleAsync(
-      routeHandler,
+      Route.asyncHandler(asRoute),
       appConfig.getString("akka.http.benchmark.host"),
       appConfig.getInt("akka.http.benchmark.port"),
       parallelism = 16)
-  }
 }

+ 3 - 0
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/Infrastructure.scala

@@ -8,7 +8,10 @@ import scala.concurrent.ExecutionContext
 
 trait Infrastructure {
   implicit def system: ActorSystem
+
   implicit def executionContext: ExecutionContext
+
   implicit def materializer: Materializer
+
   def appConfig: Config
 }

+ 0 - 1
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/RequestMapping.scala

@@ -1,6 +1,5 @@
 package com.typesafe.akka.http.benchmark
 
-import akka.http.scaladsl.model.headers.Connection
 import akka.http.scaladsl.server.Directives._
 import akka.http.scaladsl.server.Route
 import com.typesafe.akka.http.benchmark.handlers._

+ 1 - 1
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/Templating.scala

@@ -3,5 +3,5 @@ package com.typesafe.akka.http.benchmark
 import org.fusesource.scalate.Binding
 
 trait Templating {
-  def layout(uri: String, attributes: Map[String, Any] = Map(), extraBindings: Traversable[Binding] = Nil): String
+  def layout(uri: String, attributes: Map[String, Any] = Map.empty, extraBindings: Traversable[Binding] = Nil): String
 }

+ 3 - 1
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/datastore/DataStore.scala

@@ -1,13 +1,15 @@
 package com.typesafe.akka.http.benchmark.datastore
 
-import scala.collection.immutable
 import com.typesafe.akka.http.benchmark.entity.{ Fortune, World }
 
+import scala.collection.immutable
 import scala.concurrent.Future
 
 trait DataStore {
   def findWorldById(id: Int): Future[Option[World]]
+
   def requireWorldById(id: Int): Future[World]
+
   def updateWorld(world: World): Future[Boolean]
 
   def getFortunes: Future[immutable.Seq[Fortune]]

+ 6 - 5
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/datastore/MySqlDataStore.scala

@@ -5,8 +5,8 @@ import java.sql.ResultSet
 import java.util.concurrent.Executors
 
 import com.typesafe.akka.http.benchmark.Infrastructure
-import com.typesafe.akka.http.benchmark.entity.Fortune
-import com.typesafe.akka.http.benchmark.entity.World
+import com.typesafe.akka.http.benchmark.entity.{ Fortune, World }
+import com.typesafe.config.Config
 import com.zaxxer.hikari._
 
 import scala.collection.immutable
@@ -15,7 +15,7 @@ import scala.concurrent.Future
 
 // TODO: use slick or similar here instead for more idiomatic usage
 trait MySqlDataStore extends DataStore { _: Infrastructure =>
-  lazy val config = appConfig.getConfig("akka.http.benchmark.mysql")
+  lazy val config: Config = appConfig.getConfig("akka.http.benchmark.mysql")
 
   private lazy val dataSource = new HikariDataSource {
     setJdbcUrl(config.getString("jdbc-url"))
@@ -30,12 +30,13 @@ trait MySqlDataStore extends DataStore { _: Infrastructure =>
     ExecutionContext.fromExecutor(threadPool)
   }
 
-  def requireWorldById(id: Int): Future[World] = findWorldById(id).map(_.getOrElse(throw new RuntimeException(s"Element with id $id was not found.")))(executionContext)
+  def requireWorldById(id: Int): Future[World] =
+    findWorldById(id).map(_.getOrElse(throw new RuntimeException(s"Element with id $id was not found.")))(executionContext)
+
   override def findWorldById(id: Int): Future[Option[World]] =
     withStatement("select id, randomNumber from World where id = ?") { stmt =>
       stmt.setInt(1, id)
       val rs = stmt.executeQuery()
-
       if (rs.next()) Some(World(rs.getInt("id"), rs.getInt("randomNumber")))
       else None
     }

+ 5 - 4
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/entity/World.scala

@@ -1,10 +1,11 @@
 package com.typesafe.akka.http.benchmark.entity
 
-import spray.json.DefaultJsonProtocol
-import spray.json.RootJsonFormat
+import com.github.plokhotnyuk.jsoniter_scala.core._
+import com.github.plokhotnyuk.jsoniter_scala.macros._
 
 case class World(id: Int, randomNumber: Int)
+
 object World {
-  import DefaultJsonProtocol._
-  implicit val worldFormat: RootJsonFormat[World] = jsonFormat(World.apply, "id", "randomNumber")
+  implicit val codec: JsonValueCodec[World] = JsonCodecMaker.make[World](CodecMakerConfig())
+  implicit val seqCodec: JsonValueCodec[Seq[World]] = JsonCodecMaker.make[Seq[World]](CodecMakerConfig())
 }

+ 4 - 4
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/DbHandler.scala

@@ -1,18 +1,18 @@
 package com.typesafe.akka.http.benchmark.handlers
 
 import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
 import com.typesafe.akka.http.benchmark.Infrastructure
 import com.typesafe.akka.http.benchmark.datastore.DataStore
 import com.typesafe.akka.http.benchmark.util.RandomGenerator
 
 trait DbHandler { _: Infrastructure with DataStore with RandomGenerator =>
-  import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
+  import de.heikoseeberger.akkahttpjsoniterscala.JsoniterScalaSupport._
 
-  def dbEndpoint =
+  def dbEndpoint: Route =
     get {
       path("db") {
-        val id = nextRandomIntBetween1And10000
-        complete(requireWorldById(id))
+        complete(requireWorldById(nextRandomIntBetween1And10000))
       }
     }
 }

+ 3 - 3
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/FortunesHandler.scala

@@ -4,6 +4,7 @@ import akka.http.scaladsl.model.HttpCharsets._
 import akka.http.scaladsl.model.MediaTypes._
 import akka.http.scaladsl.model._
 import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
 import com.typesafe.akka.http.benchmark.Infrastructure
 import com.typesafe.akka.http.benchmark.Templating
 import com.typesafe.akka.http.benchmark.datastore.DataStore
@@ -11,18 +12,17 @@ import com.typesafe.akka.http.benchmark.datastore.DataStore
 import scala.concurrent.Future
 
 trait FortunesHandler { _: Infrastructure with DataStore with Templating =>
-  def fortunesEndpoint =
+  def fortunesEndpoint: Route =
     get {
       path("fortunes") {
         complete(response)
       }
     }
 
-  def response: Future[HttpResponse] = {
+  def response: Future[HttpResponse] =
     getFortunes.map {
       fortunes =>
         val body = layout("/templates/fortunes.mustache", Map("fortunes" -> fortunes))
         HttpResponse(StatusCodes.OK, entity = HttpEntity(body).withContentType(`text/html`.withCharset(`UTF-8`)))
     }
-  }
 }

+ 9 - 12
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/JsonHandler.scala

@@ -1,24 +1,21 @@
 package com.typesafe.akka.http.benchmark.handlers
 
 import akka.http.scaladsl.server.Directives._
-import spray.json.DefaultJsonProtocol
-import spray.json.RootJsonFormat
+import akka.http.scaladsl.server.Route
+import com.github.plokhotnyuk.jsoniter_scala.core._
+import com.github.plokhotnyuk.jsoniter_scala.macros._
 
 case class JsonResponse(message: String)
+
 object JsonResponse {
-  import DefaultJsonProtocol._
-  implicit val responseFormat: RootJsonFormat[JsonResponse] = jsonFormat1(JsonResponse.apply)
+  implicit val codec: JsonValueCodec[JsonResponse] = JsonCodecMaker.make[JsonResponse](CodecMakerConfig())
 }
 
 trait JsonHandler {
-  import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
-
-  def jsonResponse = JsonResponse("Hello, World!") // domain object
+  import de.heikoseeberger.akkahttpjsoniterscala.JsoniterScalaSupport._
 
-  def jsonEndpoint =
-    get {
-      path("json") {
-        complete(jsonResponse)
-      }
+  def jsonEndpoint: Route =
+    (get & path("json")) {
+      complete(JsonResponse("Hello, World!"))
     }
 }

+ 8 - 11
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/PlaintextHandler.scala

@@ -1,22 +1,19 @@
 package com.typesafe.akka.http.benchmark.handlers
 
-import akka.http.scaladsl.model.HttpCharsets
+import akka.http.scaladsl.model.HttpCharsets._
 import akka.http.scaladsl.model.HttpEntity
 import akka.http.scaladsl.model.HttpResponse
 import akka.http.scaladsl.model.MediaType
 import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
 
 trait PlaintextHandler {
-  lazy val plainTextResponse = {
-    // akka-http will always generate a charset parameter for text/plain, so to be competitive, we create a custom
-    // one here to save a few bytes of headers for this particular test case.
-    //
-    // This is explicitly allowed in
-    // http://frameworkbenchmarks.readthedocs.org/en/latest/Project-Information/Framework-Tests/#specific-test-requirements
-    val simpleTextPlainMediaType = MediaType.customWithFixedCharset("text", "plain", HttpCharsets.`UTF-8`)
-    HttpResponse(entity = HttpEntity(simpleTextPlainMediaType, "Hello, World!"))
-  }
-  def plainTextEndpoint =
+  // akka-http will always generate a charset parameter for text/plain, so to be competitive, we create a custom
+  // one here to save a few bytes of headers for this particular test case. This is explicitly allowed in:
+  // http://frameworkbenchmarks.readthedocs.org/en/latest/Project-Information/Framework-Tests/#specific-test-requirements
+  val plainTextResponse = HttpResponse(entity = HttpEntity(MediaType.customWithFixedCharset("text", "plain", `UTF-8`), "Hello, World!"))
+
+  def plainTextEndpoint: Route =
     (get & path("plaintext")) {
       complete(plainTextResponse)
     }

+ 4 - 5
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/QueriesHandler.scala

@@ -1,5 +1,7 @@
 package com.typesafe.akka.http.benchmark.handlers
 
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
 import com.typesafe.akka.http.benchmark.Infrastructure
 import com.typesafe.akka.http.benchmark.datastore.DataStore
 import com.typesafe.akka.http.benchmark.util.RandomGenerator
@@ -9,18 +11,15 @@ import scala.util.Try
 
 trait QueriesHandler {
   _: Infrastructure with DataStore with RandomGenerator =>
-  import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
-  import akka.http.scaladsl.server.Directives._
-  import spray.json.DefaultJsonProtocol._
+  import de.heikoseeberger.akkahttpjsoniterscala.JsoniterScalaSupport._
 
-  def queriesEndpoint =
+  def queriesEndpoint: Route =
     get {
       path("queries") {
         parameter('queries.?) { numQueries =>
           // The queries parameter must be bounded to between 1 and 500. If the parameter is missing,
           // is not an integer, or is an integer less than 1, the value should be interpreted as 1;
           // if greater than 500, the value should be interpreted as 500.
-
           val realNumQueries = Try(numQueries.getOrElse("1").toInt).getOrElse(1).min(500).max(1)
           complete {
             Future.traverse(Seq.fill(realNumQueries)(nextRandomIntBetween1And10000))(requireWorldById)

+ 5 - 5
frameworks/Scala/akka-http/src/main/scala/com/typesafe/akka/http/benchmark/handlers/UpdatesHandler.scala

@@ -1,6 +1,7 @@
 package com.typesafe.akka.http.benchmark.handlers
 
 import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
 import com.typesafe.akka.http.benchmark.Infrastructure
 import com.typesafe.akka.http.benchmark.datastore.DataStore
 import com.typesafe.akka.http.benchmark.entity.World
@@ -10,10 +11,9 @@ import scala.concurrent.Future
 import scala.util.Try
 
 trait UpdatesHandler { _: Infrastructure with DataStore with RandomGenerator =>
-  import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
-  import spray.json.DefaultJsonProtocol._
+  import de.heikoseeberger.akkahttpjsoniterscala.JsoniterScalaSupport._
 
-  def updatesEndpoint =
+  def updatesEndpoint: Route =
     get {
       path("updates") {
         parameter('queries.?) { numQueries =>
@@ -23,8 +23,8 @@ trait UpdatesHandler { _: Infrastructure with DataStore with RandomGenerator =>
             for {
               world <- requireWorldById(id)
               newWorld = world.copy(randomNumber = nextRandomIntBetween1And10000)
-              wasUpdated <- updateWorld(newWorld)
-            } yield newWorld // ignore `wasUpdated`
+              _ <- updateWorld(newWorld) // ignore `wasUpdated`
+            } yield newWorld
 
           complete {
             Future.traverse(Seq.fill(realNumQueries)(nextRandomIntBetween1And10000))(mutateOne)