Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Scala(finagle)@SmartNews_English

2,350 views

Published on

Use case of Scala Finagle in SmartNews, Inc.

Published in: Technology

Scala(finagle)@SmartNews_English

  1. 1. SCALA @ SMARTNEWS High Performance Ad Server with Finagle @taketon_
  2. 2. Who? • @taketon_ • Ph.D in Psychology(Vision) • TypeSafe and Beer Lover
  3. 3. SmartNews • Mobile News App, which uses machine learning algorithms
 to discover and aggregate news • 10 million DL globally
  4. 4. Chikyukun
  5. 5. Ad • Started Ad delivery(only in Japan) from 2014/Dec • Policy: Ads as Content
  6. 6. Team members
  7. 7. Ad Server • Implemented within about 2 months
  8. 8. Finagle • OSS RPC Framework by Twitter, Inc. • Using Future like Akka • Wrapper of Netty • "Server as a function"
  9. 9. Finagle • We use Finagle for Movie Ad Server • avg. 20ms latency / Max 2000qps
  10. 10. 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
  11. 11. 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)
  12. 12. How to implement
 Ad Server
  13. 13. Handling Async • Type as a document • Future indicates IO call • Procedures in AdServer • Request -> JSON parse -> Data load -> Campaign Filtering -> Response
  14. 14. Server as a function • Server : Request => Response ! class Service[-Req, +Rep] extends (Req => Future[Rep])
  15. 15. 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)
  16. 16. JSON Parse • Jackson Streaming API • AdRequest is defined in .thrift file ! trait JsonParser { def read(request: Request):Option[AdRequest] }
  17. 17. Data Load • Future type indicates the function causes heavy IO task ! trait ContextLoader { def load(): Future[CampaignContext] }
  18. 18. 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] }
  19. 19. Campaign Filtering • Filtering campaigns based on Context and Request ! trait CampaignFilter { def filter( request: AdRequest, ctx: CampaignContext ): Option[AdResponse] }
  20. 20. 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]
  21. 21. Future Chain (flatMap) ! def apply(request: Request): Future[Response] = { val adReqOpt: Option[AdRequest] = JSONParser.read(request) val fAdResOpt: Future[Option[AdResponse]] = adReqOpt.map { adReq => val fCtx: Future[CampaignContext] = ContextLoader.load() fCtx.map { ctx => CampaignFilter.filter(adReq, ctx) } }.getOrElse { Future.value(None) } !
  22. 22. Future Chain (flatMap) ! fAdResOpt.flatMap { adResOpt => adResOpt.map { adRes => val fSessionKeyOpt = RedisService.getKey() fSessionKeyOpt.flatMap { sessionKeyOpt => sessionKeyOpt.map { sessionKey => Future.value(mkResponse(adRes, sessionKey)) }.getOrElse { Future.value(Response(NO_CONTENT)) } } }.getOrElse { Future.value(Response(NO_CONTENT)) } } }
  23. 23. 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)) } } !
  24. 24. 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) !
  25. 25. Routing val muxer = new HttpMuxer() .withHandler("/ad", new AdService()) ! val server = Http.server(":port", muxer) Await.result(server)
  26. 26. Tips for Performance • Basically the server will not be CPU-bound • Wrap IO-bound process by Future
  27. 27. 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)
  28. 28. 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)

×