お使いのブラウザは最新版ではありません。最新のブラウザでご覧ください。

CNET Japan ブログ

Lingr and Comet - 技術解説編

2006/09/22 14:31
  • このエントリーをはてなブックマークに追加

さて、お待たせしました。いよいよCometとLingrについての技術解説です。

■Comet解説

さて、まずはCometとは何で、どういう背景によって生まれたのか、についての解説から始めます。

まず前提として、Webアプリケーションにおいては、通信開始のトリガーは常にクライアント側が握っています。つまりURLを入力したりボタンをクリックしたときなどに通信が発生することになるわけですが、このようなアーキテクチャは、サーバ側で発生した変化をリアルタイムにクライアント側に通知することが原理的にできないことを意味します。

チャット・アプリケーションでは、複数のユーザから不定期にメッセージが送信され、それが他の参加者に一斉に配信されなければなりません。しかし、メッセージを受け取ったサーバ側では、それをクライアントに即座にプッシュで通知する方法がないのです。

そのため、一定期間ごとにブラウザがサーバに問い合わせを行い、サーバ側に変化がないかを定期的にチェックするというポーリング方式が一般的に使われるようになりました。しかし、このポーリング方式には大きな欠点があります。それは、

  1. 問い合わせの間隔が長いとタイムラグが長くなってリアルタイム性が損なわれる
  2. 間隔を短くするとブラウザやサーバやネットワークの負荷が大きくなる

という問題です。

これがどのぐらい深刻な問題かと言うと、1000ユーザが同時接続している状態で、1秒に1度の間隔でポーリングを行うと、計算してみるとわかりますが月間で26億ヒットとなり、「なにもせずブラウザを開いているだけで」グーグルのページビュー(12億)を超えるアクセス(!!)が殺到することになります。恐ろしいですね。

さぁ、そこで登場したのがCometです。Cometでは、まずブラウザ側があらかじめサーバに対してHTTPリクエストを発行しておき、サーバ側はそのリクエストに対してレスポンスを返さずにずっと掴んだままにしておきます。そして、別の経路でサーバがキック(メッセージを送信)されたら、それまで掴みっぱなしになっていた複数のリクエストに対して一斉にメッセージを乗せてレスポンスを返すことで、擬似的にサーバからのプッシュを実現するのです。そして、ブラウザはすぐさまリクエストを再発行してふたたび応答待ちの状態へと戻る。まさに逆転の発想なのです。

このCometベースのチャットの基本となる考え方は、AJA Chatのソースを解説した以下のダイアグラムが一番わかりやすくまとまっていると思いますのでここをご覧ください。

http://www.machu.jp/diary/20060411.html#p01

さて、Cometならせわしない1秒に1度のアクセス・ラッシュを回避でき、必要なときだけアクションを起こすという経済的なプロトコルになるので、万事一件落着でしょうか?ところが、そうは問屋が卸しません。今度は、サーバ側に問題が発生します。

通常、Apacheなどの一般的なウェブサーバは、短い応答時間で返せる処理を大量にこなすというスループット重視の前提で設計されています。このため、リクエストを受けたらそのリクエストに対してプロセスまたはスレッドをあてがい、最後まで面倒を見るという方式が一般的です。

ところが、先ほども言ったようにCometではコネクションはつなぎっぱなしになっていますから、いつまで経ってもプロセスやスレッド(およびメモリ資源)が解放されません。しかも、それらのスレッドは仕事もせずにアイドリングしており、メモリとCPUを浪費しているだけです。これは重大な問題です。どのぐらい重大かというと、そもそも千や万のオーダーの同時接続を実現することができません。

メモリを12GB搭載したSPARC SolarisのサーバでApacheのプロセスを6000個ぐらい上げた猛者がいるという実績も耳にしたことがありますが、一般的なLinuxサーバでは700あたりで挙動が不安定になり、実用に耐えません。これは、非常におおざっぱに言えば、ひとつのサーバ筐体でたかだか700人しか収容できないという不経済性を意味します。

この問題を克服するためには、かなり高度な技術が必要になります。一言でいえば「コネクションとスレッドを分離する」ということなのですが、こうしたテクニックを応用したレディメイドのサーバは世の中に存在しないので、独自のアーキテクチャのHTTPサーバをスクラッチから作るのに近い作業が必要になります。

幸い、ここ最近になって、こういったCometタイプのアプリケーションをサポートするアーキテクチャを備えた新世代のサーバが台頭しつつあります。Cometの名付け親でありdojo toolkitの作者でもあるAlex Russell氏は、その名もCometdという汎用Cometイベントルータの開発に着手し始めましたし、PerlベースのPerlbalPOE、PythonベースのTwistedなど、Cometアプリを書くのに役立つベースプラットフォームが出てきています。そうした選択肢のなかで、LingrはJavaベースのJetty 6を採用し、独自のメッセージング・ハブを構築する道を選びました。

Jetty 6では、新たにContinuationsという機能がサポートされ、Lingrが必要とするスケーラブルなCometモデルが実現できるようになったからです。

このJettyの新機構がどういうものかについての解説は、以下がわかりやすいと思います。

http://d.hatena.ne.jp/brazil/20050921/1127284397
http://d.hatena.ne.jp/brazil/20050920/1127178251

さらに、見過ごしがちな点をもう一つ。

チャットというアプリケーションでは、全体のうちかなりの割合の期間をアイドル状態(何もイベントが発生しない状態)が占めます。これは重要な目の付け所です。コンピュータサイエンスの世界では、キャッシュにおける「参照の局所性」のような統計的アプローチがしばしば重要な役割を果たしますが、チャットにおけるCometも全く同じで、この統計的な傾向が決定的な意味を持ちます。つまり、全体に占めるアイドル期間が長ければ長いほどCometを採用することによる利得が大きくなるということです。

これがもし、すべてのコネクションにおいて常時まったく休むことなくイベントが発生しつづけるタイプの用途なら、ポーリング+バッチ転送でまとめ処理の方が効率がよいという事態に逆転するのですが、チャットではそういうことは統計的にあり得ません。

従って、先にポーリングだと1000コネクションでグーグルのページビューを超えるほど恐ろしくリソース食いだという話をしましたが、Lingrの現在の見積もりでは、1000同時コネクション程度ならもろもろのアプリケーションオーバーヘッド込みでもLinuxサーバ数台で片付くのではないかと見ています。むしろデータベースへの負荷の方がよっぽど心配です。

ともかく、この採用技術の違いが生むコスト体質の差は決定的です。Comet自体は原理さえわかってしまえば割と気軽に実装できるのですが、ちゃんとスケールするように設計しないと、やたらマシンの台数が増え、それを保守する人員も増え、せっかくのチープ革命の恩恵を享受できなくなってしまいます。

というわけで、ここまでひとまずCometについての解説でした。

■Lingrの実装

さて、昨年末頃、このコネクション張りっぱなしでリアルタイムに双方向通信するという技術的なシード・アイデアの面白さと、それをスケーラビリティを担保した状態で実現することの困難さにすっかり魅了されていた頃、自然とLingrのアイデアは誕生しました。Cometの簡単なプロトタイプはダニーが1日かそこらで実装してしまい、1台のMac上で5000のコネクションへ一斉配信するのに2-3秒という初期データも得ました。

そんな頃、「ウェブ上でのIRCライクなオープンチャット」なんていう誰でも思いつきそうな当たり前のものが、世の中にこれといった形で存在していない理由が、その技術的ハードルの高さにあるのだということに気がついたのでした。技術的なブレイクスルーが求められるというのは、そこに参入障壁があるということであり、むしろものすごいチャンスだぞと。早く経験値を積んだものの勝ちだろうと。

というわけで、まずはLingrのアーキテクトであるダニーの解説と、最近行ったプロトコルの変更についてのポストにリンクを張っておきます。

変更後の現在のアーキテクチャは、簡単に図示すると以下のようになっています。

LingrBackendArchitecture20060920

以下、この図を見ながら読み進めていただければいいと思います。

まず、Lingrのバックエンドでは、メインの開発環境としてRuby on Railsが使われています。そして、Comet対応のためのLong-lived connectionをサポートする部分でJavaサーブレットエンジンのJettyを使っています。データベースへのアクセスは、すべてRails側から行います。

Railsに一目惚れしてしまい、もうRuby以外ではコードを書きたくないとのたまわる我がアーキテクトの方針により、Javaのコードは極限まで最小化されています。感覚的にいえば全体の5%ぐらいでしょうか。本当はチャットサーバも全部Rubyベースにしたかったのですが、Lingrの用途だとサーバからの一斉配信のときに大量のワーカースレッドが並行稼働するのでRubyの遅いグリーンスレッドは致命的なのと、Cometをちゃんと実装できそうな適切なフレームワークがなく実現の目処が立たなかったので、ここだけJavaベースになっています。(余談ですが、いまシリコンバレーではRubyとRailsのブームには恐ろしいぐらいの勢いがあります。少なくとも我がチームはMatzさんに足を向けて寝られません。ぼくなんてRubyのあまりの楽しさに刮目してプログラマーに戻ろうと決心したぐらいだし)

そして、クライアントからの一般的な非同期リクエストの送受信にはいわゆる普通のAjaxよろしくXMLHttpRequestを使っているのですが、先に述べた理由により、Jettyベースのチャットサーバをウェブサーバとは別に用意せねばならず、その場合に問題となるCross-Domain Restrictionを回避(XHRは同一ホストにしか送信できない)するためにJSONPライクな方式をバックチャネル経路として使っています。

図のように、チャットルームに入ったところでまず全員observeがチャットサーバに対して張りっぱなしになり(どのチャットサーバにつながるかはロードバランサが動的に決定)、ルームのなかの誰かがsayすれば、それがウェブサーバ経由でチャットサーバクラスタにnotify(ブロードキャスト)され、そのチャットサーバクラスタのそれぞれにぶらさがっているクライアントにメッセージが配信され、すぐまたobserveを発行する、というサイクルを繰り返す仕組みになっています。

と、概要レベルを言葉で説明するのは簡単ですが、現在のLingrのアーキテクチャに至るまでには様々な試行錯誤がありましたし、これからもどんどん変わっていくでしょう。

こんな感じでLingrは最近の流行どころの技術のショーケースと言えるぐらい、バラエティに富んだ技術がてんこ盛りです。JavaScriptやCSSの方でもトリッキーなテクを活用していますので、ソースを読んでどんどんパクれるところはパクっていただいて、技術者の皆さんのお役に立てたらなと思います。そして、もっといい方法があったら教えてください。(笑)

ユーザからの報告にもありましたが、37signalsのCampfireというビジネス向けのチャットサービスがあるのですが、こちらはポーリングしているので、ブラウザ側でもCPU負荷が高く、Lingrはこれに比べるとだいたい5分の1ぐらいです。常駐することを考えると、この差って結構大きいと思います。クライアントにもネットワークにもやさしいCometを流行らせよう!ということで。

というわけで、今回の内容に興味をもたれた方は、例によってこのポストの先頭に貼ったリンクから私の部屋に寄っていただければと思います。日本時間だと午前中か深夜にいることが多いです。

♪ King Crimson / 21st Century Schizoid Man Including Mirrors

※このエントリは CNET Japan ブロガーにより投稿されたものです。朝日インタラクティブ および CNET Japan 編集部の見解・意向を示すものではありません。
運営事務局に問題を報告

最新ブログエントリー

個人情報保護方針
利用規約
訂正
広告について
運営会社