at posts/single.html

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?

関連する日記