なぜErlangなのか

最近はErlang関連のWeb書籍の翻訳をしてまして、Erlang関連のニュースなどを収集して読んでいます。そこでたまたま見つけた面白そうなブログがあって、翻訳しようかなと思っていたら@voluntasの兄貴ご推薦ということなので、日常生活を取り戻すべく翻訳してみました。 このエントリは inagist というサービスの公式ブログで、ErlangでWebサービスを作ることの利点を説明した良エントリです。多少補足や外部リンクが必要な部分は僕がリンクを貼ったりしているので、わからない場合はご参照ください。

なぜErlangなのか

よく私がinagistはErlangで書かれていると言うとおかしな目で見られることがあります。なので、ここでErlangが適している重要な点をいくつかあげようと思います。

私たちがしていること

inagistでは、リアルタイムにリアルタイムストリームを要約しようとしています。今のところは Twitter Streaming API を使って動作させています。要約することによって、リアルタイムにストリーム中で人気があるツイートをフィルタリングして、トレンドに応じてツイートをグループ分けしています。これを実際に justinbeiber.inagist.comlibya.inagist.com で見てみましょう。(WebSocketを使っているのでGoogle ChromeやSafariが最適です。)こういった要約を、いくつもの方法で組み合わせたストリームに対して行っています。ユーザ独自のストリーム( マイストリーム )、キーワード検索ストリーム( libya.inagist.com )、キーワード+ジオロケーションベースストリーム( sxsw.inaginst.com )がそれです。

軽量プロセス

ここではっきり区別しておかなければいけないのは、ツイートストリームを要約するときのリアルタイム性です。ストリーム中の個々のツイートを可能なかぎり永続化して、オフライン分析をするという方法もあります。しかしここでは代わりにユーザ毎に有限なキャッシュを作って、あるツイートが人気がでたり、トレンドを検知するときにストリーム中でキーワードが繰り返されたら、キャッシュに押し込んだり取り出したりしたりし続けています。これがErlangに適している部分なのです。ストリームのコンシューマはErlangプロセスとしてモデル化されています。Erlangプロセスは軽量で、独立しているからです。本質的にはユーザ毎に用意されたプロキシです。このErlangプロセスはストリームからツイートを受け取って、キャッシュを操作して、データを返すためにAPIクエリに応答します。これらのストリームコンシューマはそれぞれ gen_server による実装になっていて、 supervisor チェインに結びついています。コンシューマが落ちたときには、 supervisor が他のユーザに全く影響を与えずに再起動します。

メッセージ

では、どうやって取得したデータストリームとこの軽量プロセスを、メッセージを使って結びつけるのでしょうか。個々のツイートはコンシューマにStreaming APIクライアントからのメッセージとして届けられます。ツイートのコンシューマはそれぞれErlang VM上の全範囲に渡るプロセスツリーの一部となっています。ツイートがネットワークから取得された瞬間に、ツイートが別のプロセスとして起動されて、JSON化されたツイートを処理して、分散されたプロセスツリーにメッセージを投げます。メッセージはコンシューマプロセスに渡されて、コンシューマプロセスがキャッシュの更新を行います。非同期メッセージなので、クライアントではツイート一つに対してどれだけコンシューマがあるかは関係ありません。コンシューマプロセスがあるツイートに関連があるなら、ただ処理できるのです。メッセージは分散して送出されているので、取得するツイートの負荷が大きくなったときやコンシューマの数が増えたときにはスケールします。

分散Erlang

コンシューマの数が増えるにつれて、Erlangの他の重要な側面が役に立ってきます。マシン間での分散です。コンシューマプロセスは設計においてはプロセスIDでしか認識できないようになっています。ErlangではローカルプロセスIDと分散プロセスIDを同様に扱います。コンシューマが分散ツリーの一部であるかぎり、ツイートはプロセスに届けられます。これはスケールアウトを容易にします。個々のマシンはクラスター全体に影響を与えることなく独立して落ちることができ、ただそのマシンが落ちている間はそのマシン上に配置されたユーザがオフラインになるだけになります。

リアルタイム配布

出来るだけリアルタイムにするために、接続されたクライアントにはWebSocketを使ってメッセージを送ります。ここでメッセージが再び搭乗です。ストリームコンシューマはそれぞれメッセージを生成して、キャッシュが人気のあるツイートやトレンドを明らかにします。我々のアプリのGoogle Chrome用プラグインはWebSocketクライアントで、この配布モデルを使っています。Chrome拡張はトレンドがわかったり、ツイートにある程度以上人気が出たら通知をします。また拡張を使って別の角度からもリアルタイム検索をしています。トレンドがわかったときに、拡張が自動的にそのトレンドに沿った目立ったツイートを探し始めます。

ストリーミング検索

以前に検索ストレージとしてどのようにRiakを使っているかに触れました。それに加えて、ストリーミング検索を可能にする独自の拡張を行いました。Riakでドキュメントにインデクスを張るときに常にインデクスデータを検索基盤に送ります。またこのインデクス項を、ツイートが別のインデクスに取得されたらときにもすぐに送ります。ここでシステム上には、パターンマッチをするために <index, field, value> のタプル待っているプロセスや、検索基準に合致するドキュメントのプロセスを待っている通知があります。いまのところ ==, and, or, >, <, >=, =< といった演算子をサポートしていて、sxsw(==)、justin beiber(and)、文字列を含んだドキュメント、バウンディングボックス内の緯度経度など、どんなツイートでも検知できます。ストリームコンシューマは、これを使ってストリームからリアルタイムにフィルタされたツイートを取得します。Chromeプラグインでもこの検索ストリームをつついて、検出されたトレンドや検索クエリにあったのツイートがあったときにはいつでも、ユーザに通知をします。この機能は本当に強力です。なぜなら、これがあればトレンドを見ることでユーザがどんな話題に興味があるかとか自動的に知ることができ、ユーザにリアルタイムにこういった話題に合致するものがあったときにいつでも知らせることができます。このストリーム検索は、メッセージベースのアーキテクチャの性質によって、小さいオーバーヘッドで動作しています。この性質によってブラウザで1秒か2秒くらいでストリームを処理することができるのです。justinbieber.inagist.comなどのページのヘッダにある”Live Stream”をクリックすれば、実際に動作しているところを見ることが出来ます。

おわりに

以上、Erlangの差別化要因に関して俯瞰してお話しましたが、わざわざErlangを使っている理由はご理解いただけかと思います。もしもっと情報が必要ということであれば @jebui までコメントをください。喜んでお返事致します。

追伸: さっきツイートストリームで話題になっていたJustin BieberかLady Gagaのアルバムはまだ聞いてません。