Optimizing Play for Production – Create with Activator – Performance Pitfalls



Optimizing Play for Production – Create with Activator – Performance Pitfalls

0 4


optimizing-play-for-production


On Github jamesward / optimizing-play-for-production

@_JamesWard

Optimizing Play for Production

James Ward ~ @_JamesWard

Agenda

  • Create, Run, Start, Stage, Dist
  • Performance Pitfalls
  • Troubleshooting Tools
  • Thread Pool Configuration
  • Front-End Servers & Load Balancers
  • Static Assets

Create with Activator

$ activator new

Run in Dev Mode

Activator UI or CLI

$ activator run
$ activator ~run

Start in Prod Mode

Activator CLI

$ activator start

Stage

Activator CLI

$ activator stage

Publish

Activator CLI

$ activator publish-local

Dist

Activator CLI

$ activator dist

Performance Pitfalls

  • Web Tier State
  • Unnecessary Serialization
  • Blocking
  • Blocking Badly

Web Tier State

  • Play is stateless by default
  • State lives in cookies
  • Move state to the client or external data stores

Unnecessary Serialization

JSON Coast to Coast

val foo = Json.obj("name" -> "foo")
val transformer = (__ \ "name").json.put(JsString("bar"))
val newFoo = foo.transform(transformer)

Blocking

  • Threads are a precious resource
  • No more thread per connection

Reactive Requests

Blocking Request

def foo = Action {
    Ok("foo")
}

Async Request

def foo = Action.async {
    Future.successful(Ok("foo"))
}

Reactive Request (Async + Non-Blocking)

def pause(duration: Int) = Action.async {
  Promise.timeout(Ok(duration.toString), duration seconds)
}

Reactive WS Client

val f: Future[Response] = WS.url("http://www.foo.com").get

Reactive Composition

def foo = Action.async {
  val futureResponse = WS.url("http://www.foo.com").get
  futureResponse.map { response =>
    Ok(response.body)
  }
}

Reactive Composition

def foo = Action.async {
  val futureJW = WS.url("http://www.jamesward.com").get
  val futureTwitter = WS.url("http://www.twitter.com").get
  for {
    jw <- futureJW
    twitter <- futureTwitter
  } yield Ok(jw.response.body + twitter.response.body)
}

Non-Blocking is Better

  • Play's WS client lib
  • ReactiveMongo
  • Redis, Datomic, etc

Blocking Badly

  • Some APIs are only blocking (JDBC, Http Client, etc)
  • Threads can accommodate spikes
  • Push don't Pull (or Poll)
  • Use Actors

Actors: Scalability & Resilience

  • Event-Driven
  • Non-request based lifecycle
  • Managed Concurrency
  • Isolated Failure Handling (Supervision)

Watcher Pattern

Client A - Request for x Fetch x Client B - Request for x Client B - Watch for x Receive x Send x to A & B

Akka Routers

Troubleshooting Tools

  • Typesafe Console
  • App Dynamics & New Relic
  • jps, jstat, VisualVM, etc
  • jClarity
  • Gatling, etc

Thread Pool & JVM Configuration

Play's Thread Pools

  • Netty boss/worker thread pools
  • Iteratee thread pool
  • Play Internal Thread Pool
  • Play default thread pool
  • Akka thread pool

Block More with More Threads

play {
    akka {
        akka.loggers = ["akka.event.Logging$DefaultLogger", "akka.event.slf4j.Slf4jLogger"]
        loglevel = WARNING
        actor {
            default-dispatcher = {
                fork-join-executor {
                    parallelism-factor = 1.0
                    parallelism-max = 300
                }
            }
        }
    }
}

Front-End Servers & Load Balancers

  • Port Forwarding: IPTables
  • Proxies: Apache, nginx, etc
  • Load Balancers & Auto-Scaling: ELBs

Static Assets

  • Play is optimized for caching proxies
  • 304 - Not Modified
  • Asset Fingerprinting

CDNs & Caching Proxies

def getUrl(file: String) = {
  Play.configuration.getString("contenturl") match {
    case Some(contentUrl) => contentUrl + routes.RemoteAssets.getAsset(file).url
    case None => controllers.routes.RemoteAssets.getAsset(file)
  }
}
Reverse Router Wrapped
<script src='@RemoteAssets.getUrl("jquery.min.js")'></script>

Last Modified & ETag

  • Out of the box
  • Still hits the server

Asset Fingerprinting

The hacky way:
object StaticAssets extends Controller {
  val versionStamp: String = new Date().getTime.toString + "/"

  def at(file: String) = CustomNotFound {
    val actualFile = file.replaceAll(versionStamp, "")
    Assets.at("/public", actualFile)
  }

  def getUrl(file: String) = {
    val versionedFile = versionStamp + file
    controllers.routes.StaticAssets.at(versionedFile)
  }
}

Far Future Expires

  • Requires fingerprinting
result.withHeaders("Cache-Control" -> "max-age=290304000, public")

Reactive Scales!