こんにちは、t-hiroyoshiです。
最近はScala Days 2016 NYCで発表されたScala Nativeにコントリビュートしています。Scalaのような強力な言語がbare metalで動くのは素晴らしい事だと思っているので早く実用レベルで動くようにこれからも積極的に貢献していきたいです。

そんな訳で今回は布教を兼ねてScala NativeのデモをDocker上で簡単に動かす方法を紹介したいと思います。

Scala Native

Scala Nativeとは何かと言うと、ScalaのコードをLLVMのIR経由でコンパイルしてNative実行可能にするAOTコンパイラ(AheadOfTimeCompiler)実装です。
一部低級な処理向けにScalaを拡張しているのである意味別言語と言っても過言ではないかもしれません。
これらの拡張によってScalaをSystem level languageに落とし込めるのでその気になればScalaで何でも作れるわけで、とても未来が楽しみです。

ただREADMEにpre-release stageとあるようにまだ完全にScala-likeには動きません、ですが活発にIssueやPull reqが飛んでいるので近いうちにまともに動くようになると思っています。(まだ完成していない今ならコントリビュートのチャンス?)

現在はLLVM(Clang)とBOEHM GCをローカル環境に依存しているのでデモを動かすまでにそれらのパスが通らなかったり、依存解決で結構躓く人が多いみたいです。
そこでScala Nativeのレポジトリ内にあるDockerfileを使って作ったコンテナ内でデモを動かしてみたいと思います。管理用のスクリプトもきちんとあるので比較的簡単に動かす事が可能です。

手順

docker-machineを立てる(Option)

なるべくローカルの環境を汚染したくないのでdocker-machineでDockerホストを立てます。
driverはvirtualboxを使っていますがvmwarefusionで立てた方がもしかしたら速いかもです(Scalaはcompile時間が長いと言われている)。
メモリを指定しないと1Gしか割り当てられないので増やしておきます。

$ docker-machine create -d virtualbox --virtualbox-memory 2048 scalanative

docker-machineが立ち上がったらsshで繋ぎます。

$ docker-machine ssh scala-native

管理用のスクリプトはbashで動くのですが、Boot2Dockerは標準でbashが入っていないので次に進む前にここを参考にbashをインストールしてください。

Scala Nativeのコンテナイメージをビルドする

まずはともかくScala Nativeのソースをcloneしてきます。

$ git clone https://github.com/scala-native/scala-native.git

scala-native/dockerの中にDockerfileがあるので移動して管理用のスクリプトを叩きます。

$ cd scala-native/docker
$ ./manage.sh build

色々ダウンロードしたりするので結構時間がかかります。

時間がかかります辛抱強く待って下さい、コーヒーでも飲んでいれば良いと思います。
ビルドが終わったらいよいよデモを動かします。

Scala Nativeのデモを実行する

スクリプトを叩いてビルドしたコンテナの中に入ります。

$ ./manage.sh run

ここでエラーが出てsbtプロジェクトが読み込まれない方はscala-native/配下のtargetフォルダを全て削除してみてください。
コンテナに入れたらsbtを起動してデモがあるdemoNativeプロジェクトを指定します。

$ sbt
> project demoNative
> run

後はrunすればデモが動き出してコンパイルされたバイナリがscala-native/demo/native/target/scala-2.11/demonative-outに吐き出されているはずです。

これはそのまま実行できるのでいろいろ試してみてください。

Docker Machineめっちゃ便利ですがBoot2Docker上で作業をしている時、コンテナをShell Scriptで管理していて困ったことありませんか?

docker@dev:/Users/t-hiroyoshi/git/some-project/docker$ ./manage.sh build
env: can't execute 'bash': No such file or directory

bashがBoot2Dockerに入ってない!これは困る!ということでBoot2Dockerにbashを入れる方法です。
Boot2DockerはTiny Core Linuxベースで動いているのでそのパッケージ管理システム(tce-load)を利用してあげれば万事解決です。

docker@dev:/Users/t-hiroyoshi/git/some-project/docker$ tce-load -wi bash

これでbashが使えるようになったのでbash Shell Scriptが使えます。
Docker Machine(Boot2Docker)上に状態を持つのはあまり良くない気がしますがまあbashぐらいはあってもいいのではと思いました。

こんにちは、t-hiroyoshiです。
今回は前回紹介したAkka HTTPの性能が気になったのでざっくりベンチマークを取ってみました。
ソースコードはGithubにあげました。

それぞれのバージョン等は以下の通りです。

// sbt.version = 0.13.11
// addSbtPlugin("io.spray" % "sbt-revolver" % "0.8.0")
// addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.2")
lazy val commonSettings = Seq(
name := "akka-http-vs-spray",
version := "1.0.0",
scalaVersion := "2.11.8",
scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8", "-Xlint")
)
val akkaV = "2.4.4"
val sprayV = "1.3.3"
lazy val spraySettings = Seq(
libraryDependencies ++= {
Seq(
"com.typesafe.akka" %% "akka-actor" % akkaV,
"io.spray" %% "spray-can" % sprayV,
"io.spray" %% "spray-routing" % sprayV
)
}
)
lazy val akkaSettings = Seq(
libraryDependencies ++= {
Seq(
"com.typesafe.akka" %% "akka-actor" % akkaV,
"com.typesafe.akka" %% "akka-stream" % akkaV,
"com.typesafe.akka" %% "akka-http-experimental" % akkaV
)
}
)
lazy val sprayServer = (project in file("spray-server"))
.settings(commonSettings: _*)
.settings(spraySettings: _*)
.settings(assemblyOutputPath in assembly := file("./spray-server.jar"))
lazy val akkaServer = (project in file("akka-server"))
.settings(commonSettings: _*)
.settings(akkaSettings: _*)
.settings(assemblyOutputPath in assembly := file("./akka-server.jar"))
Revolver.settings
java version "1.8.0_71"
Java(TM) SE Runtime Environment (build 1.8.0_71-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, mixed mode)

sbt assemblyしたjarをローカルでjava -jarしてベンチマークをとりました。

ローカル環境

MacBook Pro (Retina, 13-inch, Early 2015)
OS: OSX 10.11.4
Processor: 3.1 GHz Intel Core i7
Memory: 16 GB 1867 MHz DDR3

ベンチマークにはApacheBenchを用いました。

ab -k -c 10 -n 10000 http://localhost:8080/

以下それぞれの結果です。

Akka HTTP

Code

package akka.server
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
object Boot extends App {
implicit lazy val system = ActorSystem("akka-server")
implicit lazy val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
private val route =
pathSingleSlash {
get {
complete("Hello world!")
}
}
Http().bindAndHandle(route, "localhost", 8080)
}

Result

This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Server Software: akka-http/2.4.4
Server Hostname: localhost
Server Port: 8080
Document Path: /
Document Length: 12 bytes
Concurrency Level: 10
Time taken for tests: 0.465 seconds
Complete requests: 10000
Failed requests: 0
Keep-Alive requests: 10000
Total transferred: 1780000 bytes
HTML transferred: 120000 bytes
Requests per second: 21490.49 [#/sec] (mean)
Time per request: 0.465 [ms] (mean)
Time per request: 0.047 [ms] (mean, across all concurrent requests)
Transfer rate: 3735.65 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 0 0 1.0 0 42
Waiting: 0 0 1.0 0 42
Total: 0 0 1.0 0 42
Percentage of the requests served within a certain time (ms)
50% 0
66% 0
75% 0
80% 0
90% 1
95% 1
98% 2
99% 3
100% 42 (longest request)

Spray-can

Code

package spray.server
import akka.actor._
import akka.io.IO
import spray.can.Http
import spray.routing._
import spray.http._
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
object Boot extends App {
implicit val system = ActorSystem("spray-server")
implicit val timeout = Timeout(5.seconds)
val service = system.actorOf(Props[ServiceActor], "spray-service")
IO(Http) ? Http.Bind(service, "localhost", 8080)
}
class ServiceActor extends Actor with HttpService {
private val route =
pathSingleSlash {
get {
complete("Hello world!")
}
}
def actorRefFactory = context
def receive = runRoute(route)
}

Result

This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Server Software: spray-can/1.3.3
Server Hostname: localhost
Server Port: 8080
Document Path: /
Document Length: 12 bytes
Concurrency Level: 10
Time taken for tests: 0.430 seconds
Complete requests: 10000
Failed requests: 0
Keep-Alive requests: 10000
Total transferred: 1780000 bytes
HTML transferred: 120000 bytes
Requests per second: 23230.85 [#/sec] (mean)
Time per request: 0.430 [ms] (mean)
Time per request: 0.043 [ms] (mean, across all concurrent requests)
Transfer rate: 4038.18 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 0 0 0.9 0 20
Waiting: 0 0 0.9 0 20
Total: 0 0 0.9 0 20
Percentage of the requests served within a certain time (ms)
50% 0
66% 0
75% 0
80% 0
90% 1
95% 1
98% 2
99% 3
100% 20 (longest request)

まとめ

Akka HTTPが21490.49 req/sec、Spray-canが23230.85 req/secと割と近い数値が出た気がします。
Akka HTTPは前もっと使い物にならないくらい遅かったイメージだったんですがこれなら十分なリクエストがさばけそうです。

Akka-HTTP

Akka-HTTPはAkka-ActorとAkka-StreamsをベースとしたAkkaのHTTP moduleです。
異なるレベルでのAPIを提供してくれているので高レベルから低レベルなところまでカスタムすることができて良いです。
あとはSprayの開発チームがLightbend(旧Typesafe)にそのまま移行して開発している(?)ので割とSprayのAPI、DSLに似ている部分が多かったりしてSpray使ったことがある人は移行しやすいと思います。パフォーマンスは最近はSprayに迫るところまで来ていて実用に足るところまで来ているのでは無いでしょうか。
Akka-HTPPは幾つかのmoduleから成り立っていてそれぞれを軽く説明すると以下の感じになります。

  • akka-http-core: ほとんどが低レベルで構成されていてhttp server, clientのためのmodule(WebSocketsも含む)
  • akka-http: 高レベルな関数やhttp serverのAPI定義などに使用出来るDSLを含んでいるmodule
  • akka-http-testkit: http serverのためのテストツールmodule
  • akka-http-spray-json: JSONのシリアライズ、デシリアライズのためのmodule
  • akka-http-xml: XMLのシリアライズ、デシリアライズのためのmodule

実装

UserのCRUDができるREST APIを作ってみます。
ソースコードはGithubにあげておきました。

sbt

まずはplugin.sbtを書きます。
sbt-assemblyはStand-aloneなjarを作るのに使用します。

logLevel := Level.Warn
addSbtPlugin("io.spray" % "sbt-revolver" % "0.8.0")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.2")

次にbuild.sbtに必要なライブラリを追記しておきます。

name := "akka-http-standalone"
version := "1.0"
scalaVersion := "2.11.8"
scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8", "-Xlint")
assemblyOutputPath in assembly := file("./akka-http-standalone.jar")
libraryDependencies ++= {
val akkaV = "2.4.2"
Seq(
"com.typesafe.akka" %% "akka-actor" % akkaV,
"com.typesafe.akka" %% "akka-stream" % akkaV,
"com.typesafe.akka" %% "akka-http-experimental" % akkaV,
"com.typesafe.akka" %% "akka-http-spray-json-experimental" % akkaV,
"com.typesafe.akka" %% "akka-http-xml-experimental" % akkaV,
"com.typesafe.akka" %% "akka-http-testkit" % akkaV
)
}
Revolver.settings

Scala

Routeを書く前にデータやリクエストを定義します、JSONを使いたいのでさらにJsonProtocolも定義します。
jsonFormatの後ろについている1や2はClassのパラメータ数を表しています。

import spray.json.DefaultJsonProtocol
object MyData {
case class User(id: Int, name: String)
case class ErrorResponse(message: String)
case class CreateUserRequest(name: String)
case class UpdateUserRequest(name: String)
}
object JsonProtocol extends DefaultJsonProtocol {
import MyData._
implicit lazy val userFormat = jsonFormat2(User)
implicit lazy val errorResponse = jsonFormat1(ErrorResponse)
implicit lazy val createUserRequestFormat = jsonFormat1(CreateUserRequest)
implicit lazy val updateUserRequest = jsonFormat1(UpdateUserRequest)
}

データの定義が完了したので実際にRouteを書いていきます、ほぼSprayの書き方と同じです。
本来であればRouteとServiceは分離してあった方が良い気がしますが今回は気にせず一体型にします。
JSONを使いたいのでSprayJsonSupportを継承しています、この辺まだSprayのままみたいですね。
先に定義したJsonProtocolをimportしないとcompileが通らないので注意してください。

import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
trait Route extends SprayJsonSupport {
import MyData._
import JsonProtocol._
implicit val db: DB
val routes =
pathSingleSlash {
get {
// GET localhost:8080
index()
}
} ~
path("ping") {
get {
// GET localhost:8080/ping
complete("pong")
}
} ~
pathPrefix("users") {
pathEndOrSingleSlash {
get {
// GET localhost:8080/users
getUsers()
} ~
post {
// POST localhost:8080/users
entity(as[CreateUserRequest]) { request =>
createUser(request.name)
}
}
} ~
path(IntNumber) { id =>
get {
// GET localhost:8080/users.:id
getUser(id)
} ~
patch {
// PATCH localhost:8080/users.:id
entity(as[UpdateUserRequest]) { request =>
updateUser(id, request.name)
}
} ~
delete {
// DELETE localhost:8080/users.:id
deleteUser(id)
}
}
}
private def index() = complete(
HttpResponse(
entity = HttpEntity(
ContentTypes.`text/html(UTF-8)`,
<html>
<body>
<h1>Welcome to <i>akka-http</i>!</h1>
</body>
</html>.toString
)
)
)
private val userNotFound = "user_not_found"
private def createUser(name: String)(implicit db: DB) =
db.createUser(name) match {
case Left(err) =>
failWith(err)
case Right(user) =>
complete(user)
}
private def getUsers()(implicit db: DB) =
db.getUsers match {
case Left(err) =>
failWith(err)
case Right(users) =>
complete(users)
}
private def getUser(id: Int)(implicit db: DB) =
db.getUser(id) match {
case Left(err) =>
failWith(err)
case Right(None) =>
complete(StatusCodes.NotFound -> ErrorResponse(userNotFound))
case Right(Some(user)) =>
complete(user)
}
private def updateUser(id: Int, name: String)(implicit db: DB) =
db.updateUser(id, name) match {
case Left(err) =>
failWith(err)
case Right(None) =>
complete(StatusCodes.NotFound -> ErrorResponse(userNotFound))
case Right(Some(user)) =>
complete(user)
}
private def deleteUser(id: Int)(implicit db: DB) =
db.deleteUser(id) match {
case Left(err) =>
failWith(err)
case Right(()) =>
complete(StatusCodes.OK)
}
}

indexとpingは関係ないですがサンプルとして追加しました。
リクエストの処理が完了して手っ取り早くOKだけ返したい時はStatusCodes.OKを使ってあげると簡単です。
全体的に特に難しいところもなく割と直感的に書けるところは良いですね。
他に便利なrouting DSLもたくさんあるので公式ドキュメントで適宜探してみると良いと思います。
次にDBのモックをこんな感じに作っておきます。

import MyData.User
trait DB {
def createUser(name: String): Either[Throwable, User]
def getUsers: Either[Throwable, Seq[User]]
def getUser(id: Int): Either[Throwable, Option[User]]
def updateUser(id: Int, name: String): Either[Throwable, Option[User]]
def deleteUser(id: Int): Either[Throwable, Unit]
}
object MockDB extends DB {
private var user_table = Seq[User]()
private var next_user_id = 0
private def nextId() = {
next_user_id += 1
next_user_id
}
def createUser(name: String) = {
val user = User(nextId(), name)
user_table = user_table :+ user
Right(user)
}
def getUsers = Right(user_table)
def getUser(id: Int) = Right(user_table.find(_.id == id))
def updateUser(id: Int, name: String) = {
user_table = user_table.map(u =>
if (u.id == id) u.copy(name = name)
else u
)
getUser(id)
}
def deleteUser(id: Int) = {
user_table = user_table.filterNot(_.id == id)
Right(())
}
}

Boot(Main)を書いていきます。この辺はSprayとちょっと異なる部分なので注意しながらやってみてください。
bindingが失敗したときのためにloggerを仕込んでいます。

import akka.actor.ActorSystem
import akka.event.Logging
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
object Boot extends App with Route {
implicit lazy val system = ActorSystem("my-system")
implicit lazy val materializer = ActorMaterializer()
override implicit val db = MockDB
implicit val ec = system.dispatcher
val interface = "localhost"
val port = 8080
val logger = Logging(system, getClass)
val binding = Http().bindAndHandle(routes, interface, port)
binding.onFailure {
case err: Exception =>
logger.error(err, s"Failed to bind to $interface $port")
}
}

ここまできたらsbtでreStartしてみてください、API Serverが立ち上がると思います。
jarが欲しい場合はsbt assemblyでjarが生成されます、Stand-aloneなjarなので扱いやすいのが嬉しいです。

Akka-HTTPは本当にSprayとほぼ同じ感じでかけるので使ったことある人はとっつきやすいと思いました。
現在はパフォーマンスチューニングを中心に開発しているようなのでどこまでSprayに近づけるのか楽しみですね。

こんにちは、t-hiroyoshiです。
ハッカソンデビュー!ということでCyberAgentさん主催のアドテクコンペに参加してきた話です。

今回のテーマはアドテクで広告入札の部分に関わるDSP(Demand-Side Platform)を作るというものでした。
DSPとは飛んでくる入札のリクエストに対しいくらでどんな広告を出したいかを返すものです、なので求められるものとしては高トラフィック(今回は2000QPS)をさばきつつ配信する広告を選び適切な価格で入札できるものといった感じです。さらにDSPには応答速度も問われ今回は100ms以内にレスポンスが返せないものは無効になるという設定でした。最終的な評価はCPC(クリックあたりのコスト)と予算の消化率で行われました。

参加した理由ですがそろそろプログラミングやり始めて1年だしハッカソン的なのに出てみたいなって思っていた時にScalaMatsuri2016で見かけたCyberAgentさんのホームページ見てみてみたら学生向けのハッカソンの参加者を募集していて、何か面白そうだし応募してみたところ参加権を貰えたというのが理由です。

作ったもの(作ろうとしたもの)

僕のチームはエンジニアの自分とデータサイエンティストのSou Konishi, Kouichi Takayamaの3人チームでした。お互いの強みを生かしつつなるべく依存し合わないような設計をしようと以下のようなアーキテクチャにしました。

architecture

最初はAWSに依存しすぎるのが嫌だったのでKinesisではなくKafka Cluster使いたかったのですが、時間的に厳しいと判断して大人しくKinesis使いました。またConsumer側はマイクロサービス群なので拡張性があり、新たな機能を追加しやすいかなとか考えました。
イメージとしてはScalaをマイクロサービスプラットフォーム的な立ち位置で置いてスケールさせたかった感じです。
Producer~Consumer間で評価モデルの受け渡しをどうするのか迷っていましたが、Kinesisを使うことにしたのでAWSどっぷりで良いやってなってめっちゃ早と噂のMemcachedをElastiCache上で使うことにしました。Memcached使ったことなかったのでどういう感じに使えば良いか分からずConsumer側(Python)でセットすると余計なヘッダが付いてくるとかに悩まされScalaのパーサー書くのが辛かったです。
評価モデルの生成はデータサイエンティストの2人がKinesisの強みを生かしたオンライン学習ベースで随時最新のモデルをExportできるようにしてくれました。僕はデータサイエンティストの2人がJupyter使ってデータこねくり回しているのを見てはーすっげってなってました。
AkkaにはKamonが仕込んであって、Actorの最適化や監視に使う予定でしたが時間切れで仕込んだだけになってしまいました。

最終的に骨組みを作りきりProducer側も上手く分散化ができたのですが、評価モデルの読み込みフォーマットと評価モデルの展開式をミスって計算が爆発してメッセージ消化しきれなくてActor死にまくるみたいになってまともに動きませんでした、Kamon生かせなかった…
完全にエンジニアリソースが足りておらず作り込み不足でした、ごめんなさい。

開発後にチームでプレゼンをする機会があったのですが、徹夜開発に加え本番でまともに動かなかった絶望感から頭おかしい感じのプレゼンになっていたと思います(笑)。
ですが、嬉しいことに技術選定や設計、オンライン学習などを評価して頂きなんと特別賞を頂くことができました。

感想と反省

今回はCyberAgentさん主催だったのでレッドブルを出してくれたりお菓子食べ放題だったりピザが食べられたり美味しいコーヒーを準備してくれたり環境としては至れり尽くせりで開発に集中することができました、感謝です。

チームメイトとの絆も(徹夜の)開発を通して生まれてとても濃いハッカソンになりました。
今回は開発時間的に忙しすぎてメンターの方々とあまり交流できなかったので少し残念でしたが、Kinesisの情報を頂いたりできたので良かったです。
KinesisやMemcachedといった技術は使ったことがなかったものなのでこの機会に使えることができて良かったですが、初ハッカソンということで張り切りすぎて技術選定を大きくしすぎた感があってエンジニアリソースが足りず最後まで作り込めませんでした。今回参加してみて実際のリソース感を体感できたので次回に生かしたいと思います。ハッカソンに限ってはベンダロックインを許容してどっぷり浸かった方が他に集中できて楽とか色々なことを学びました。

まとめると初ハッカソンは本番で動かないという絶望的な結果に終わりましたが、特別賞を貰えたので救われました。ハッカソン的な短期集中開発は自分の性格的にかなり楽しめたのでこれからもハッカソンに積極的に参加していきたいです。