関数としてのサーバー(仮)を

読んでみた(前編)

発端

とあるお祭りの会場で聞いた話で紹介された論文を知って読んでみたいと思った。







http://www.slideshare.net/debasishg/functional-and-algebraic-domain-modeling

これ↓

https://monkey.org/~marius/funsrv.pdf

誰が書いた論文?

 

Marius Eriksen

 

。。。誰?

Who is Marius Eriksen?

  • Twitterの中の人(開発エンジニア)
  • 紹介ページ
    https://monkey.org/~marius/
  • wiredの記事とか
    http://www.wired.com/2013/06/twitter-marius-eriksen-finagle/

その後(裏話)

カジュアルトークのネタにと一週間前からチラ見(果汁3%感)

前日くらいから概要をつまんで


当日お昼休み前後に集中して読んだ


これらの伏線が示す結論は、

みんなとコラボしたい!!(笑)

そう、いざとなったら、巻き込み力大事!!

なおここで話す関数とは・・・

  • 「与えられた入力の値のみから出力となる値をただ1つに決める規則」
  • 数学的な意味で、入力値(x)は不変であり、帰結としての出力(y)は、xを所与とした時点で確定(例: y = f(x))

では本題ー>

どんなことが書いてあるの?

同時実行性が高く、環境変動に耐えうる大規模なサーバーソフトを作るための考察。

効率性、安全性、堅牢性を備えたソフトを構築するため、

安全、モジュール性を備え、効率的なサーバソフトを作るための強力なプログラミングモデルを提供する3つの抽象化を紹介している。

3つの抽象化

  • Futures
  • Services
  • Filters

Futures

非同期操作(network RPC, a timeout, or a disk I/O等)間における依存関係を表現する構成として、包括した結果を内包する

All operations returning futures are expected to be asynchronous,

though this is not enforced.


// 個別の検索結果を提供するために検索クエリを書き換えるサービスを考えてみる。
def rewrite(user: String, query: String): Future[String]
def search(query: String): Future[Set[Result]]


// 検索するのにまず個別のクエリをrewriteして検索する(パイプ(|)で表現) 
def psearch(user, query) = rewrite(user, query) | search( ); 


// futureに変形することも可能(resultとrewriteをsearchに適用する)
// flatMap combinator はこの変形をとり行なう
trait Future[T] {
    def flatMap[U](f: T => Future[U]): Future[U]
    ...
}


// flatMapの引数は関数であり、Futureを引き継ぐときに、依存するFutureを生成するために呼び出される。
def psearch(user: String, query: String) = rewrite(user, query) flatMap { 
    pquery => search(pquery)
}


//  関数リテラルはpsearchの戻り値としてFuture[Set[Result]]を導き出す。
val result: Future[Set[Result]] = psearch("marius", "finagle")

データ依存に起因する2つの非同期操作の例はよくある例(ex:検索エンジンのフロント)。ここではデータ依存性を解決するのにflatMapがsearchとrewrite間で使用されています。psearchは単にこれを表現している媒体。


//  rescue

trait Future[T] {
    ...
   def rescue[B](
       f: PartialFunction[Throwable, Future[B]]
    ): Future[B]
}

//  rescueサンプル(タイムアウトの例)
def psearch(user: String, query: String) = 
    rewrite(user, query).within(50.milliseconds) rescue { 
        case _: TimeoutError => Future.value(query)
    } flatMap { pquery =>
        search(pquery)
    }

エラーハンドリングは?エラー時に復旧するためにリトライや値をフォールバックするのはよくあることだが、rescureコンビネーターがこれを担う。flatMapは結果の成功時に動作し、resqueは結果の失敗時に動作する。


// 値が型Aであるfutureシーケンスを、型Aのシーケンスを内包するfurureに変換する
def collect[A](fs: Seq[Future[A]]): Future[Seq[A]]

//  scatter-gather操作(連続転送)
def querySegment(id: Int, query: String): Future[Set[Result]]

// 複数の依存関係を関連づけるのにcollectを使う
def search(query: String): Future[Set[Result]] = {
    val queries: Seq[Future[Result]] =
    for (id <- 0 until NumSegments) yield {
        querySegment(id, query)
    }

    collect(queries) flatMap { results: Seq[Set[Result]] =>
        Future.value(results.flatten.toSet)
    }
}

// あるfutureのエラーはcollectに引数として渡され、合成されたfutureにすぐ伝わり引数として渡される。

複数の依存性の合成は?collectコンビネーターは複数の依存関係を解決します。

psearchを起動すると、副操作の数で合成されたfutureを返す。その結果が上記のデータグラフ図。


// 循環合成

def permute(query: String): String

def rsearch(user: String, query: String, results: Set[Results], n: Int): Future[Set[Result]] =
    if (results.size >= n)
        Future.value(results)
    else {
        val nextQuery = permute(query)
        psearch(user, nextQuery) flatMap { newResults =>
        if (newResults.size > 0)
            rsearch(user, nextQuery, results ++ newResults, n)
        else
            Future.value(results)
        }
    }

// The error handling semantics of futures are such that rsearch will
// short-circuit and fail should any of its constitutent operations fail.
// flatMap merges futures, implementing a form of tail-call elimination:
// the above example will not produce any space leaks; it is
// safe to define recursive relationships of indefinite length.

futureコンビネータを再帰的に使ってみる。

Services and Filters

次回の後半にて!!


// 後半戦

type Service[Req, Rep] = Req => Future[Rep]

val client: Service[HttpReq, HttpRep] =
Http.newService("twitter.com:80")
val f: Future[HttpRep] = client(HttpReq("/"))


Http.serve(":80", { req: HttpReq =>
Future.value(HttpRep(Status.OK, req.body))
})

Http.serve(":8080",
Http.newService("twitter.com:80"))

type Filter[Req, Rep] =
(Req, Service[Req, Rep]) => Future[Rep]

val identityFilter =
{ (req, service) => service(req) }


def timeoutFilter(d: Duration) =
{ (req, service) => service(req).within(d) }




val httpClient: Service[HttpReq, HttpRep] = ...
val httpClientWithTimeout: Service[HttpReq, HttpRep] =
timeoutFilter(10.seconds) andThen httpClient

def authReq(req: HttpReq): Future[AuthHttpReq]

val auth: (HttpReq, Service[AuthHttpReq, HttpRes])
=> Future[HttpRep] = {
(req, service) =>
authReq(req) flatMap { authReq =>
service(authReq)
}
}

val authedService: Service[AuthHttpReq, HttpRep] = ...
val service: Service[HttpReq, HttpRep] =
auth andThen authedService

関数としてのサーバー(仮)

By Akira Tamai

関数としてのサーバー(仮)

発表済み。

  • 56