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

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

Watcher Pattern

  1. Client A - Request for x
  2. Fetch x
  3. Client B - Request for x
  4. Client B - Watch for x
  5. Receive x
  6. 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!