JRuby on Rails on Google App Engine勝手訳

Google App EngineJava版が出ました。まぁJavaは比較的どうでもいいんですが、嬉しいことにこの上ではJRubyが動くようです。海外の先駆者ががんばってブログを書いてくれたので、いつものように勝手訳してみます。


http://olabini.com/blog/2009/04/jruby-on-rails-on-google-app-engine/

これは最近アナウンスされたGoogle App EngineJavaをサポートするというニュースに関する記事の3つ目だ。今回の記事ではJRuby on RailsアプリケーションをGAE/Jで動かすのに必要な手順をさらってみて、アプリケーションの特性がどのようになると予想すべきかに付いて書きたい。

まずJRubyは最新版が必要だ。JRuby1.2リリース以降にJRubyのtrunkに加えられた変更のほとんどが必要になるので、check outしてビルドしておこう。最新版のRailsもちゃんと動く。

最初に基本となるRailsアプリをセットアップしたら、その後やることがいくつかある。まずWarblerをpluginizeでインストールして、Warble configurationファイルを生成する。これには次のようにするといい。"jruby -S gem install warbler", "jruby -S warble pluginize", 最後に "jruby -S warble config"。後半の二つはRailsアプリケーションのルートで行うこと。

またRails gemのフリーズも必要だ。以上のことが終わったら全てのファイルを確認して不要なファイルは全て削除しておこう。公開されているとおりGAE/Jは1000ファイルまでしか扱えないように制限されているけれど、標準的なRailsアプリは最終的にそれ以上のサイズになっている。ActiveRecordは全て削除して構わないし、testディレクトリもいらないだろう。他にもいろいろと消せるファイルがあるはずだ。

GAE/Jを使う場合ActiveRecordは必要ないはずなので、config/environment.rbでそれをロードしないように気をつけよう。次のステップでwarble.rbファイルを変更していく。以下が君のやるべきことだ:

まず、次のようにして必要なGAE/Jファイルを確実に含まれるようにしよう:

config.includes = FileList["appengine-web.xml", "datastore-indexes.xml"]

また、ランタイムをいくつ起動するかパラメータを設定する必要もある。

config.webxml.jruby.min.runtimes = 1
config.webxml.jruby.max.runtimes = 1
config.webxml.jruby.init.serial = true

最後のオプションはJRuby-rackのtrunkバージョンで利用可能だ。もしmin=1, max=1に設定しないのであれば、このオプションが必要になる。そうしておかないとJRuby-rackはランタイムを初期化スレッドを複数立ち上げようとしてしまう。

最後により新しいバージョンのライブラリを使用可能にするため、どのJavaライブラリを使用するかという指定を空の配列にしておく:

config.java_libs = []

後で全てのjarファイルをこのlibディレクトリに追加しておく。

私が追加した最後の設定オプションはRailsにDataStoreをセッションストアとして使用させるためのものだ。YARBLを見ればどうやったか分かるだろう。

またappengine-web.xmlファイルにもいくつかのオプションを設定した。最も重要なのはJMXをオフにしてos.archを空にしたことだ。

<property name="jruby.management.enabled" value="false" />
<property name="os.arch" value="" />

これは見れば大体分かるだろう。

まだ私がうまく行っていないことも一つあって、"protect_from_forgery"ができないのでapp/controller/application.rbでその行をコメントアウトしておいてほしい。

jarファイルをいくつかlibディレクトリに置く必要があるけど、そのままではGAE/Jには大きすぎるのでjruby-complete jarは分割する必要がある。libに置く最初のファイルはappengine-api.jarだ。またjruby-rackの最新ビルドも必要。最後にjruby-complete.jarを分割しなおす。私は次のようなスクリプトを使っていくつかのjarファイルを切り出した。

#!/bin/sh

rm -rf jruby-core.jar
rm -rf ruby-stdlib.jar
rm -rf tmp_unpack
mkdir tmp_unpack
cd tmp_unpack
jar xf ../jruby-complete.jar
cd ..
mkdir jruby-core
mv tmp_unpack/org jruby-core/
mv tmp_unpack/com jruby-core/
mv tmp_unpack/jline jruby-core/
mv tmp_unpack/jay jruby-core/
mv tmp_unpack/jruby jruby-core/
cd jruby-core
jar cf ../jruby-core.jar .
cd ../tmp_unpack
jar cf ../ruby-stdlib.jar .
cd ..
rm -rf jruby-core
rm -rf tmp_unpack
rm -rf jruby-complete.jar

このスクリプトは次の二つのjarを生成する。jruby-core.jar, ruby-stdlib.jar

君が自分のアプリケーションをApp Engineにデプロイするために必要なのは、だいたいこの辺が全部だ。

YARBL

このインフラを試すために、YARBLという小さいアプリケーションを作成した。このアプリではブログを作成して記事を投稿できる。コメントやその他のステキな機能はなんにも用意されていない。それでもこれは実際に動作している。YARBLではBeeUとBumbleを使用した。BeeUを使って管理者としてログインしたユーザーだけが実際に記事投稿したりブログを変更できるようにしている。これらの機能のサポートはGoogle UserServiceを使えば非常に簡単だった。

実際に動いているものは(うまくいけば)こちらで見られる: http://yarubyblog.appspot.com 。またソースコードは私のGitHubリポジトリにある: http://github.com/olabini/yarbl

Bumble

BumbleはとてもシンプルなDataStoreのラッパーで、GoogleのDataStoreを使うデータモデルを作成できる。これはYARBLのバックエンドとして開発されたので、実際にはYARBLで必要な機能だけがサポートされている。

以下がYARBLのデータモデルがどのようになっているかだ。これを見ればBumbleでどのようにモデルを定義するかなんとなくわかるだろう。一点注意すべきは、DataStoreのエンティティはどんなプロパティ/属性も受け入れるので(訳おかしいかも)、Rubyのような言語にとてもよく合っている。

class Person
  include Bumble

  ds :given_name, :sur_name, :email
  has_many :blogs, Blog, :owner_id
end

class Blog
  include Bumble

  ds :name, :owner_id, :created_at
  belongs_to :owner, Person
  has_many :posts, :Post, :blog_id, :iorder => :created_at
end

class Post
  include Bumble

  ds :title, :content, :created_at, :blog_id
  belongs_to :blog, Blog
end

実際にモデルを使うには次のようにする:

Blog.all

Post.all({}, :limit => 15, :iorder => :created_at)

blog = Blog.get(params[:id])
posts = blog.posts

Blog.create :name => name, :owner => @person, :created_at => Time.now

Post.all.each do |p|
  p.delete!
end

これがサポートされているメソッドのほぼ全てだ。実装はとても小さいので迷うことはないだろう。もちろん、これは全くチューンされていないのであまりにたくさんフェッチするのはよしてほしい。パッチは大歓迎!コードは次の場所にある: http://github.com/olabini/bumble

BeeU

Googleのユーザーサービスを使うなら、BeeUが利用できる。これはとても小さなフレームワークでいくつかのことを手助け、要するに数種類のヘルパーメソッドが使えるようになる。フィルターメソッドとしてはassign_user, assign_admin_status, verify_admin_userの3つが利用できるようになる。最初の二つはそれぞれ@userと@adminというインスタンス変数を生成する。@user変数はUserServiceのUserオブジェクトを保持し、@adminは、ユーザーがログインしていてさらにそのユーザーが管理者であるときに真になる真偽値だ。最後の一つは現在のユーザーが管理者かどうかをチェックする。ログインしていない場合はログインページにリダイレクトされ、ログインしているが管理者ではなかったときにはNot Authorizedが返される。これらの3つのメソッドは全てbefore filterとして使用してほしい。

require_adminというハイレベルのメソッドもあって、これは管理者権限で守るべきメソッドを指定するために利用できる。This is really all you need.

最後にログインURLとログアウトURLを生成するメソッドが2つある。これらは両方ともURLが生成されたページにリダイレクトで戻ってくるようになっている。

BeeUは私のGitHubリポジトリにある: http://github.com/olabini/beeu

まとめ

まとめとして、いくつかの小さな問題を除けば、JRuby on RailsはApp Engine上でとてもうまく動作する。主な問題は起動時のコストとテストだ。起動にどのくらいかかるか、GAE/Jを使って事前に知ることはできない。その代わりに君は実際に一度目のリリースをして試してみる必要がある。また現在のGAE/Jはバイトコードの事前検証などをするので、通常のJDKと比べて起動に少し時間がかかる。あるランタイムは起動におよそ20秒間またされるので、初めてのアクセスには少し時間がかかった。良いニュースとしては、これは以前はもっとまずかったということだ。ここ数週間でインフラは相当高速になっており、今後も改善が見込めるだろうと私個人は確信している。ただ未だに問題なのは、ランタイムを事前に作成できないのでいくつかのリクエストで想像以上に時間がかかることがあることだ。

一度実行すれば、そのあとはパフォーマンスがぐっと改善する点は注目に値する。ページ内で何度DataStoreへのアクセスが発生するかによるけど、一リクエストに120msか500ms掛かるのを見たことがある。これはインフラが必要な作業を考えると、それほど悪い値でもない。データアクセスもかなり制約があるようだけど、memcachingを統合する時間さえとれれば、改善は十分可能だと思う。

最後まで私を悩ませているのはテストに付いてだ。どうすればいいのか明確な答えはない。先のポストで述べたように方法はいくつかあるけど、Railsアプリケーションに組込まれている方法ほどフィットする物はない。実際、テスト自動化のコストが大きそうなので、私は今回のアプリケーションではほとんどマニュアルでのテストしかしていない。

最後に、Google App EngineJRuby on Railsというテクノロジの組み合わせはとても魅力的だ。これらを組み合わせた最初のToughtWorksプロジェクトの登場を楽しみにしている。


上記手順を実際に試してみたのでよかったらこっちもどうぞ。
http://d.hatena.ne.jp/technohippy/20090409#1239217083