Finagle
• OSS RPC Framework by Twitter, Inc.
• Using Future like Akka
• Wrapper of Netty
• "Server as a function"
Finagle
• We use Finagle for Movie Ad Server
• avg. 20ms latency / Max 2000qps
Pros
• easy to build server
• High Performance (backed by Netty)
• Async Handling by Future type
• shared model by thrift IDL
• provides metrics via twitter-server and Tracer
Cons
• slightly slow to update (e.g. upgrade to Scala 2.11)
• (waiting for finagle-redis update)
• Different Behavior between twitter.util.Future and
scala.concurrent.Future (pop-quiz)
• limited documentation
• not a good fit with NewRelic (instead, use Tracer)
Handling Async
• Type as a document
• Future indicates IO call
• Procedures in AdServer
• Request -> JSON parse -> Data load ->
Campaign Filtering -> Response
Server as a function
• Server : Request => Response
!
class Service[-Req, +Rep] extends (Req => Future[Rep])
Ad Service
• Launches ListeningServer(backed by Netty)
on `:port`
!
class AdService extends Service[Request, Response] {
override def apply(request: Request): Future[Response] = {
// hogehoge
}
}
val server = Http.serve(":port", new AdService())
Await.result(server)
* caution: This code needs transformation of Request to HttpRequest
(also Response)
JSON Parse
• Jackson Streaming API
• AdRequest is defined in .thrift file
!
trait JsonParser {
def read(request: Request):Option[AdRequest]
}
Data Load
• Future type indicates the function causes
heavy IO task
!
trait ContextLoader {
def load(): Future[CampaignContext]
}
Data Load
• Campaign Master Info is stored in MySQL,
and be cached on JVM memories.
• Use AtomicReference to ConcurrentHashMap
as cache
!
trait ContextLoader {
def load(): Future[CampaignContext]
}
Campaign Filtering
• Filtering campaigns based on Context and
Request
!
trait CampaignFilter {
def filter(
request: AdRequest,
ctx: CampaignContext
): Option[AdResponse]
}
Future Chain
• add callback to the function with flatMap
• callback will receive the result of former
Future function, then returns new Future
!
def flatMap[B](f: A => Future[B]): Future[B]
Future Chain (optionT)
• optionT[A]: Future[Option[A]] => OptionT[Future, A]
• need to make Future as instance of Monad (gist)
!
def apply(request: Request): Future[Response] = {
(for {
adReq <- optionT(JSONParser.read(request).point[Future])
ctx <- LocalCache.load().liftM[OptionT]
adRes <- optionT(CampaignFilter.filter(adReq, ctx).point[Future])
sessionKey <- optionT(RedisService.getKey())
} yield mkResponse(adRes, sessionKey)).run.map {
_.getOrElse(Response(NO_CONTENT))
}
}
!
Filter
• Set filter for application-agnostic behavior, like
timeout
class HttpTimeoutFilter (
val timeOutMs: Int
) extends TimeoutFilter[Request, Response](
timeOutMs.milliseconds,
new GlobalRequestTimeoutException(timeOutMs.milliseconds),
DefaultTimer.twitter){
}
!
Http.serve(":port", new HttpTimeoutFilter(100) andThen AdService)
!
Routing
val muxer = new HttpMuxer()
.withHandler("/ad", new AdService())
!
val server = Http.server(":port", muxer)
Await.result(server)
Tips for Performance
• Basically the server will not be CPU-bound
• Wrap IO-bound process by Future
Tips for Performance
• The cost for creating instances for Future or
Option can be ignored
• For constructing List, use mutable data structure
(e.g. ArrayBuffer) and finally call toList
• For transforming List, use Iterator and finally call
toList
• Practice for Performance tuning in Scala (Japanese)
Summary
• Finagle will be a good fit for Ad Server
• We used mutable for data construction
• However, no need for sensitive for tuning
• (́-`).。oO(Hope Finagle will become more popular)