プロセスの配置、WebLogic みたいなのと Rails みたいなの

Ruby がユーザースレッドで動作する、っていうところが引っかかっていた。果たしてそれで性能が出るのか?と。
それからしばらくもやもやしていて、Ruby on Rails の勉強をして、lighttpd + たくさんのFastCGI(とか)のアプリ処理プロセスという構成を知った。JavaWebLogic 等のプロセス構成しか知らなかったので、不思議な感じがした。

でかいプロセスと、1プロセス1スレッド

一般に、Java の世界ではデカイプロセスを一発立てて、中でスレッド(java.lang.Threadでの抽象化という意味で)がもさもさ動いている。WebLogic なんかだと、アプリ処理のワーカースレッドだけで50から100くらいが動いているのが普通だろうか。JVMの実装は、大抵、OSレベルのスレッドライブラリを使って動くので、OSレベルでスレッド(この言葉はビミョウだけど)が1プロセス内で動いていることになる。アプリケーションはJavaのスレッドが処理単位になり、まぁトランザクションとかもそこに関連付けられている。セッションはメモリ内で保持しておき、スレッド間で同期させるのが普通だろう。データベース接続も複数プーリングしておいて、同期(排他制御)させる。
一方、Rails のモデルは、1プロセス1スレッドで動くことを想定している。そもそも FastCGI がそういう物なのかどうかは知らない(調べなきゃ)。この場合、スレッド間の同期は必要なく、セッションの共有は外に追い出すことで実現している。

両方の特徴などを考えてみたい、ただしぼくの限られた知識の中で。

メモリ消費量と、同時処理の数

WebLogic なんかを使うとき、メモリ(Javaのヒープ領域)は、512MBから1GB 程度に取られることが多いだろう、ここでは 1GB としておく。少ないと多数のスレッドを立てるのが難しくなるし、2GBとかまでいくと GC が起きちゃった時にスコッと止まったりするはず。Eden+SS と Old の比は、大抵 1:2 とか 1.5:2 程度にするはず*1。で Eden と SS の比は 3:1 から 8:1 とかか。一時的なオブジェクト*2の格納領域は、大雑把に 300MB 程度になるのでしょう。そうすると、

100スレッドがホントに同時に走ったとすると、3MB/スレッド程度しか一時オブジェクトを作れない。

意外と少ないなぁ。ヘタなロジックで変数のスコープが大きかったりすると大変なことになりそうだ。

Railsでは、FastCGI のプロセスを立てると 30MB 程度を1プロセスで消費しているようだ。これは ruby (処理系) の実装なんだっけ、初期値 32MB かなんかだったような。あと、どこかに GC 拡張パッチが落ちてた気がするが思い出せない。ruby処理系自身がどの程度メモリを食っているか調べたこと無いんだけど、まぁ数MBだとおもうので、アプリ(rubyスクリプト)で 20数MB 使えるだろう。大雑把に、1GB を消費してよいとすると

1(GB) ÷ 30(MB/プロセス) = 30プロセス

が同時に立てれる。もうちょっとプロセスあたりのメモリを少なく出来れば、15MB/プロセスとして、60プロセスか。あとは、物理メモリ量とデータベースのパワーによる。
逆に言うと、FastCGIの層が水平分散のボトルネックになるわけではない、ということだ。

セッションの共有

WebLogic型の、でかいサーバでは、1サーバプロセスで足りない時、プロセスを追加することになる。この場合、セッション親和性を手前でハンドリングする必要がある。そうでなければ、セッションレプリケーションとか言って、メモリを同期させる仕組みを導入する。

サーバプロセス(WebLogicインスタンス)が1から2になるときに大きな飛びがあるってことですな

同期での共有とか非同期で反映とか色々あるみたいだけど、サーバプロセスを増やした時の性能の上がり具合は気になるところだ。可用性を考えた時にはクラスタ、とかいってやっぱりセッションの複製が必要になることが多いだろう。

Rails では、セッションは外に切り出しているので、共有自体にはそれほど問題が無いように思える。一方、セッション処理が重くなるんじゃないの? という疑問がある。DB アクセスがあるような処理の場合、(DBにセッションを入れとけば) 通常の処理とコンパラか小さいので気にしなくて良いはず。DBアクセスしない処理? あまりグイっと思い浮かばないけどその場合はちょっと気になるだろうなぁ。

水平分散に関しては、セッション管理部分の性能のみに依存することになる。

永続化が決定的でなければ 手軽にDRb とか、まじめに memcached とかになるのかな。MySQL を RAM ファイルシステム上で使う手も有りか。あ、シリアライズの性能も気になるところかも。あまり大きなデータを入れると良くないかもしれない。

フレームワーク部分のコーディング

フレームワーク部分」ってのは、WebLogic本体とか、FastCGI/Rails本体 あたりのこと。
WebLogic なら、本質的にマルチスレッドでプログラミングしなきゃいけない。

Java ではそれなりにマルチスレッドのプログラムが組みやすいとはいえ、効率的で頑健なコードベースを維持するのは結構大変

あと、メモリ同期は、パッと思いつくところで、ワーカースレッド、DBコネクションプール、セッション操作あたりで必要。JCA もか。

Railsは、スレッドセーフでは無いと割り切る

Java でだけど、マルチスレッド系のコードを書くのは、やっぱり難しい。Javaでは全てのオブジェクトがモニタになっているのも関係しているかもしれないけど、マルチコアでも効率的に動作するように作るには、かなり頭が痛い。
割り切り重要。

その他細かいこと、で、じゃあ・・・どっち?

運用してる時、例えばメモリリークしちゃったらどうするか?ということを考えてみる。ダダ漏れの場合は話にならないので、ちょっとずつ漏れてるような時。でかいプロセス一発、というのはプロセス再起動が結構大変だし、怖い、ビビる。あと、やってる処理が全部終わってから行儀良く再起動、ってのもいっぱい処理が走っているとムズイ。
FastCGIだったら、例えば1プロセスが、50MB 以上メモリ消費したところで、行儀良く再起動、みたいなことはそれなりに簡単そうな気がする。やったこと無いので、はまることもあるかもしれないけど。

と、様々に偏見が入っている気もしつつ、Rails型(FastCGI型)の処理方式にも利点はいくつかあるように見える。当然、そのときのハードウェアなり、プラットフォームの流行にも左右される部分はあるのでしょうが。
1回まじめに数字を取ってみるしかないんだろうな〜、しかし WebLogic は速いからな〜。VM からガッツリやってるし。

あ、今、気になっている事として、JRubyRails を動かせるようになったとすると、どういう処理方式を取るのであろうか? Java5から VM の一部を共有できるんだっけ? そうすると、Java処理系をたくさん起動することになるのかな? 早く動くようになるといいなぁ、JRuby on Rails、最近、メーリングリスト賑やかだし、とっても期待してます!

*1:あまりにEdenを大きくしすぎて、(Eden+SS) と Old が同じくらいになっちゃうと、EdenのGCが起きちゃっただけで、OldのGC が誘発されるので危険、Old は結構広くないといけない

*2:Oldまで行かないもの、くらいの意味です