tDiary を Google App Engine で動かしてみた
hsbtさんとこ経由で、kdmsnrさんがtDiaryをherokuで動かしていることを知った。 heroku は Ruby 専用のホスティングサイト。 Ruby だったら何でも動くということはなく、制限がある。
- Rackベースのアプリケーション
- ファイルへの書き出しは禁止(データベース前提)
そのため、 tDiary の Rack 対応版 + IOのDataMapper対応版で動いている。 と、ここまで動いているんだったら、 Google App Engine (GAE/JRuby) でも動くんじゃないかと思って試してみた。
結果
http://tdiary-machu.appspot.com/ で、一応動くところまではできた。でも、プラグインはすべて無効にしている。
- 最初の1回目(スピンアップ時)は表示までに10秒。場合によっては30秒制限でエラーに
- 2回目以降はまぁまぁの速度
- 普通に使うと無料分ではリソースの上限に引っかかりそう
- ソースは GitHub においている → http://github.com/machu/tdiary-core/tree/appengine
事前準備
10分ではじめる GAE/JRuby を読み返して手順を思い出す。
$ sudo gem install google-appengine $ sudo gem update $ gem list (以下抜粋) appengine-apis (0.0.16) appengine-jruby-jars (0.0.7) appengine-rack (0.0.9) appengine-sdk (1.3.4) appengine-tools (0.0.13) $ appcfg.rb generate_app tdiary-appengine
これでひな形ができた。 次に Rack 対応 tDiary と DataMapper IOを持ってくる。
$ git clone http://github.com/hsbt/tdiary-core/tree/testable $ git clone git://github.com/kdmsnr/tdiary_contrib.git
tdiary_contrib.git にある data_mapper_io.rb を tdiary-core/tdiary にコピーする。
トライ&エラー
いきなりは動かないと分かっていても、ローカルで試しながら少しずつ修正していく。
$ dev_appserver.rb .
config.ru に use ::Rack::Reloader を指定しておけば、ソースを書き換えればサーバの再起動無しに反映されるので便利。
試行錯誤の結果、修正した場所は以下の通り。
data_mapper_io.rb の修正
まず動かしたら、(Rubyではなく)JavaのNullPointerExceptionが発生してビビる。Ruby側のスタックトレースが表示されないので、場所が特定できない。 ソースのメイン処理をコメントアウトしながら、少しずつ問題箇所を特定していく。 どうやら、data_mapper_io.rb 内で、 tDiary の設定ファイルを保存している箇所で落ちているようだった。
config = DataMapper::Config.get(1) unless config config = DataMapper::Config.new( :id => 1 ) config.save ← ここでNullPointerException発生 end
DataMapperの使い方を分かってないと先に進めなさそうだったので、appengine-jrubyのUsingTheDatastoreを見ながら小さなサンプルを作って切り分け。 その結果、 Config クラスの定義で id に Integer でなく Serial 属性を指定すれば落ちなくなることが分かった。
他にも以下の場所を修正した。
- 初期化時に AppEngine の DataStore に保存するように指定
- auto_upgrade! メソッドがサポートされていないので記述を削除(DataStoreはスキーマレスなので削除しても問題ない)
- 日記データ(1ヶ月分)の取得にて、 like 演算子がサポートされていないので、月頭から月末までの範囲指定に書き換え
これでtdiary/data_mapper_io.rb の修正点をコミット。
AppEngineの制約(スレッド生成、ファイル書き込み禁止)への対応
次に、 tDiary にて AppEngine の制約に引っかかる箇所を対応。
- AppEngine はスレッドを生成できないため、 Thread.start を使っての SAFE レベル変更を無効にした (tdiary/appengine.rb)
- 1つの日記エンジンを複数の人で共有するときには問題になりそう
- ファイル書き込みができないので、キャッシュ処理を無効化 (tdiary/appengine.rb)
- 本来はmemcachedに書き込むように書き換えるべきだけど、動かすことを優先
- リファラの保存処理でやっぱりJavaのNullPointerExceptionが発生するので、リファラを保存しないようにした (tdiary/filter/appengine.rb)
- その他、ファイル操作をしているプラグインを無効化
同じく修正点をコミット。
起動スクリプトの修正
Rackの設定ファイル config.ru や、Gemライブラリの読み込みファイル (Gemfile) を AppEngine の環境に合わせて修正。 いつの間にか、 Gem ライブラリの呼び出し方法が新しくなっていたんだね。 bundler というライブラリを使うようになっているみたい。プロジェクトごとに使う gem を定義できるのが便利。
AppEngine の場合、 config.ru の読み込みが AppEngine::Rack 内で動く。 なので、以下のような指定だとうまく読み込めない。
use Rack::Reloader # ← AppEngine::Rack::Reloader を指定したことになる
ちょっと気持ち悪いけど、以下のようにルートから指定しなくてはダメだった。
use ::Rack::Reloader
これもGitHubにコミット
まとめ
AppEngineならではの制約もあったけど、Rack対応+DataMapper対応のおかげで、比較的簡単に AppEngine でも動かすことができた。 Low Level APIを使ってAppEngine用のIOを白紙から書くのも1つの案だけど、DataMapperを使わせてもらう方が汎用的だね。
今の時点の課題
- ファイルやディレクトリを作成するプラグイン系が動かない → プラグイン用のIOがあれば(herokuと同じ課題)
- スピンアップが遅い → AppEngineでJRubyを使う上では仕方ないかな
- キャッシュしていない → memcached?