【しばらく編集不可モードで運営します】 編集(管理者用) | 差分 | 新規作成 | 一覧 | RSS | FrontPage | 検索 | 更新履歴

WhatThreadsafeRailsMeans - スレッドセーフな Rails ってどういうこと?

目次

スレッドセーフな Rails ってどういうこと?

この文書について

Q/A: スレッドセーフな Rails ってどういうこと?

David Heinemier Hansson が Josh Peek の Rails コアチーム入りについて アナウンスを行い, ちょっとした話題になっている. Rails をようやくスレッドセーフにするという, GSoC での彼の成果をまとめ上げようというのだ. 正直なところ, 盛り上がりはまだ十分じゃない. それに, 普通にユーザにとってこれがどういうことなのか, 若干の誤解もある.

そこで, Rails がスレッドセーフになることが Rails 界隈にもたらす効果を, ごく手短な QA で説明してみよう. あたりまえだけれど, いくらかは私の意見が反映されている. けれど大半は正しい事実認識のはずだ. 間違いには訂正のコメントを貰えると信じてるよ.

Q: Rails がスレッドセーフになるってどういうこと?

A: 作業をとりこむ段になったらきっと, Josh か彼のメンターの Michael Koziarski が 更に詳しい説明をしてくれると思う. でも, 基本的には全てのリクエストで使っている単一の疎粒度なロックをなくして, 複数スレッドからアクセスされる資源だけをもっと細粒度にロックするよう直すということだ. たとえばログのサブシステムを例にとると, そこで使うデータ構造をスレッド間で共有しないようにするとか, 正しくロックしてスレッド同士が競合し, データ構造を無効なものにしたり, 壊したりしないようにするということ. Rails のインスタンス毎に単一のデータベース接続を使うかわりに接続をプールして, N 本のデータベース接続で M 個のリクエストを並列して捌けるようにするということ. こうなると, データベースの接続を消費せずにリクエストを処理できるかもしれない. おかげで利用中のアクティブな接続数は, 同時に処理するリクエスト数より少なくなる.

Q: なぜこれが重大なの? もう 複数プロセスで shared-nothing なRailsのアーキテクチャで 並列性はあるんじゃない?

A: たしかにプロセス群と shared-nothing は完全に並列だ. けれどこれは, 複数プロセスを管理するというコストがある. 多くのアプリケーションにとって, この並列性は "必要十分" だ. けれど同時リクエスト数と同じだけプロセスが必要という欠点がある: 共有資源の使い方が非効率なんだ. 典型的な Mongrel の設定で並列に 10 リクエストを処理すると, アプリケーションのコピーを 10 個ロードして, 10 個インメモリのデータキャッシュを持って, データベースの接続を 10 本張って... 同時に処理するリクエストを増やそうとすると, なにもかもが横並びで倍増してしまう. M 個のアプリケーション全てに N コピーをかけ算すると, 何倍もの大量のメモリが必要になる.

もちろんスレッドセーフなしでも部分的な解決策はある. ロードされたコードの大半やデータの一部は, 全てのインスタンスをまたいで同じものだから, Phusion 社の Passenger のような配置ソリューションを使えば, プロセスのフォークと Phusion の Enterprise 版 Ruby で改善されたメモリ モデルのおかげでメモリの一部分は共有できる. メモリ上のコードやデータのある程度は安全に共通化できる. コードやデータには, ふつう Rails 自身, アプリケーションの静的なコード, Rails やアプリケーションがロードした色々なライブラリが入っている. けれど起動後にロードしたり作ったりする データベース接続やアプリケーションのコード, インメモリのデータは相変わらず 重複したままだ. それに並列性は粗粒度なロックよりも "良くはない". なにしろ Enterprise 版 Ruby は普通の Ruby 同様ただのグリーンスレッドなのだ.

Q: じゃあ Ruby や Ruby EE, Rubinius みたいなグリーンスレッドの実装は, スレッド対応の恩恵は何もないの?

A: それはあまり正しくない. スレッドセーフな Rails では, たとえ実装がグリーンスレッドでも, 個々の独立したインスタンスが複数の接続を同時に処理できるようになる. 同時(at the same time)というのは, 並列に(concurrently)ということじゃなくて... グリーンスレッドが本当に並列に動いたり, マルチコアを活用したりはしない. ここで言いたいのは, リクエスト処理中に IO でブロックしたら, これは大半のリクエストでおこるんだけれど, (REST や DB やファイルシステムを叩くとかね.) Ruby が他のスレッドをスケジュールして実行する選択肢ができたということ. 別の言い方をすれば, 粗粒度のロックをなくせば, 少なくともグリーンスレッド実装の "ベスト" まで並列性が上がるということ. なかなか悪くない話だ.

実用的な含意としては, これからは同時処理したい数だけ Rails インスタンスの プロセスを動かすかわりに, システムのコアの数あたり定数個のインスタンス を動かせばよくなる. 効率的にコアをつかうには, コア数 (N) に対し インスタンス数は N+1 とか 2N + 1 という指標をあげている人もいる. コアあたり数個以上の Rails インスタンスが必要ということはもうないということ. もちろんみな自分のアプリケーションに最適なコア数の指標をみつけるよう試してみる だろうけれど, とにかくグリーンスレッド実装でも必要なインスタンス数を減らせるようになった.

Q. なるほど, じゃ JRuby みたいなネイティブスレッドの実装はどうなの ?

A. JRuby だと, グリーンスレッドの実装よりずっといい. JRuby は Ruby のスレッドをネイティブのカーネルレベルスレッドで実装しているから, Rails アプリは単一のインスタンスで並列リクエスト全部を全てのコアを使って処理できる. ここで 単一のインスタンスというのは "おおよそ単一のインスタンス" を意味している. 何かの共有リソースがボトルネックになるアプリケーション固有の都合はあるだろうからね. そういう時は 2, 3 個のインスタンスを使ってボトルネックを減らしたいかもしれない. けれど一般に, こういう場面はとてもレアだと思う. そういうのはほとんどが JRuby や Rails のバグで, 私達が直さなきゃいけないものだろう.

つまりこういう感じだ: JRuby を使った Rails の配備は今の 1/N 倍のメモリしか使わなくなる. N はいま並列リクエストをこなすのに必要なスレッド非安全な Rails インスタンスの数だ. スレッドセーフ Rails を動かしているグリーンスレッドな実装と比べても, 1/M 倍のメモリですむ M はコアの数. なぜなら JRuby は単一のインスタンスをめでたく並列化できるからだ.

Q: それってすごいの?

うん. それってすごい. 既存の JRuby on Rails ユーザは滅茶苦茶わくわくしてるんじゃないかな. もっと多くの人が JRuby on Rails を実環境で使ってみようと考えるようになればと思っている.

JRuby の場合, 資源活用はこれで終わりじゃない. 単一の Rails インスタンスでは, JRuby はもっと速く "warm up" できるようになる. 実行時にコンパイルと最適化をするコードが, 全てのリクエストですぐに使えるようになるからだ. ある種の最適化の都合(総メモリ消費量とか)でやっている "スロットリング(throttling)" も, もう必要なくなるかもしれない. いまある JDBC の接続プールは, インスタンス間のみならず, アプリケーション間で接続を共有し, もっと信頼性が高く効率的になる. そして, これは JRuby 上の Rails と同様に, 既に(たぶん)スレッドセーフな Merb, Groovy on Rails のようなフレームワークや, Java ベースのフレームワークみなが 共存できるようになるということだ.

こりゃもう興奮するでしょ :D