Ratpack 1.0がローンチ,目標はJVMでの非同期プログラムを容易にすること

ハイパフォーマンスJava WebフレームワークのRatpackがバージョン1.0に到達した。今回のリリースではAPIが安定しており,実用レベルになったと考えてよいだろう。Ratpackを興味深い存在にしているのは,何といっても,JVM上での非同期プログラミングの簡略化を目的としたその実行モデルだ。“JVMの世界には,この点に関するソリューションがないという問題領域があります。” SpringOne2GXでのプレゼンテーションで,Dan Woods氏はこのように主張した。“それを担うのがRatpackなのです。”

Ratpackの処理モデルはnode.jsに近い。次の図はその概要を示している。

Ratpackアプリケーションが起動すると4つのコアがこのようにセットされて,それぞれにイベントループがバインドされる。パフォーマンスの観点から大部分のフレームワークがそうであるように,Ratpackはイベントループを起動すると,非ブロック処理を行うために,イベントループのスレッドをそれぞれシステムのコアに結び付けている。その意味でRatpackは,CPUバウンドであると言ってよいだろう。表現を変えれば,コアの数が多ければより多くのトラフィックを扱うことができる,ということになる。このような方法は,サーブレットが要求毎に新しいスレッドを必要とする点を除けば,サーブレットアプリケーションの動作方法に近いとも言える。サーブレットのアプローチには,プログラミングモデルが単純だというメリットはあるものの,あるスレッドから別のスレッドにデータを移動する必要のある場合には,当然ながらCPUコンテキストの切り替えと,データが別のコアに移動することによるパフォーマンス上のペナルティを被ることになる。

RatpackはNettyを通じて“イベントループグループ(EventLoopGroup)”を生成し,利用可能なCPUコアそれぞれにシングルスレッドのイベントループをバインドする。それぞれのイベントループは,アプリケーションから到着する非ブロック形式のネットワーク要求の処理を行う。非ブロックの要求を受信したイベントループは,Ratpackに対して,その処理を非同期的に委譲する。

非ブロックで非同期なWebアプリケーションをJVM上で実現するにはいくつかの問題がある,とWoods氏は指摘する。特に問題なのは,JVMライブラリの大部分 – JDBCを使用しているものなど – が,非同期の利用を念頭においていないことだ。そこで氏はRatpackの価値提案として,“任意のAPIを非同期化”できる点を取り上げる。すなわちRatpackは,その“実行モデル”を通じて,イベントループ(“計算スレッド”)処理中ないしブロック中のスレッドをシームレスにスケジュールすることができるのだ。非同期に対応していないJDBCのようなコールは,スレッドプール外で要求の処理を行う非同期フィクスチャでラップする。そこでの処理が完了すると,処理がイベントループに戻される仕組みだ。このフローの重要な性質は,ブロックされていたコールから戻る時に,その処理が元の計算スレッドに戻されることだ。処理を再開するために改めてコンテキストスイッチをする必要がないので,コール前に処理されていたデータはCPUキャッシュ内に残っている。これがパフォーマンスと,全体的な処理効率の向上に貢献する。その効果は非常に大きく,32コアのマシン上の“Ratpackを単一インスタンスのみ使用した単純な“hello world”アプリケーションで,毎秒1億リクエストに近い値を得ることができた”と,Woods氏は述べている。

JVMでの非同期プログラミングが難しいのは周知の事実だが,その理由のひとつは,この仮想マシンがコンティニュエーション(Continuation,継続)という概念を本質的に備えていない点にある。JVMの動作は非決定論的だ。タイムアウトが発生して初めて問題の発生が分かるというのでは,Webアプリケーションの理想からは程遠く,とても満足できるものではない。Ratpackには実行単位として処理ストリーム(Processing Stream)というものがあるが,これはある意味でコンティニュエーションに近い。非同期操作を実行するPromiseタイプ(後で使用可能になる値を表す)毎にフレームワークが個別の処理ストリームを生成するが,それ自体コンティニュエーションであるとも解釈できるからだ。ストリームのチェーン全体はFIFO形式で動作する。ストリームチェーンの最後に到達したことをフレームワークが判別できるように,ストリームの開始にはマーカが設定される。個々のサブストリームの中に問題があって,いわゆるサイレント障害が発生した場合,Ratpackは処理の状況を認識して,処理結果が得られないことをユーザに報告しなければならない。つまり,決定論的な動作を行う必要があるのだ。

Ratpack 1.0のコアはJavaで記述されていて,ビルドシステムはGradle 2.6を使用している。動作にはJava 8が必要だ。ベースには非ブロック型のイベント駆動ネットワークエンジンであるNettyを使用して,非同期APIを利用するための下位レベルの構造としている。また,マイクロサービスの開発を特に意識して,JSONのような特定のトランスポートを必要としない言語をファーストクラスでサポートするように設計されている。オプションとして,NetflixのサーキットブレーカライブラリであるHystrixのサポートが提供される他,レポート用にDropwizard Metricsを統合している。また,YAMLやJSON,Javaプロパティ,システムプロパティ,環境変数などをサポートした幅広い構成モデルを採用し,認証用にはPac4jを利用する。さらにGroovyとHandlebarsの両方を利用したHTMLテンプレートも提供する。

コアはJavaで記述されているが,Ratpack自体はGroovyのサポートも充実していて,Groovyコンパイラの高度な機能の一部を利用することができる。プレゼンテーションの中でWoods氏は,“処理を委譲するオブジェクトのタイプをクロージャに通知する機能も備えているので,例えば,静的型付けを持つDSLを使用することも可能です ... この点でGroovyは,十分な性能を備えていると言えます。静的コンパイルとinvokeDynamicのおかげで,Javaに匹敵するパフォーマンスを得ることが可能です。”と述べている。

Ratpackには長い歴史がある。2010年にGroovyのDSL実装の一例として出発したが,それが遺産として捨てられる前にJVMのSinatraクローンへと進化し,2012年にはNIO/パフォーマンスへとその中心を移している。そのような中で,初期の遺産の時代には親しんでいたが,それ以降のRatpackを知らないのであれば,今一度見直す価値は十分にあるだろう。