ぱろっと・すたじお

技術メモなどをまったりと / my site : http://parrot-studio.com/

GoogleAppEngineでRubyアプリを動かすまで

大晦日の日、自分への挑戦・・・ってほどのことではないですが、
1日でGAEアプリを作るのに挑戦してみました


・・・と言っても、この上なくシンプルなCRUDアプリではあるのですが、
以前GAEでSinatraを動かしたときは、データのところまでは見てなかったので、
それを調べながらの作成でした


アプリ:
説明: RO的悪魔の辞典 - Angel, alone ~孤独な天使~


<追記:2010/01/14>コードを公開しました</追記>


アクセスしてもらえばわかると思いますが、異様に重いです
人によってはGAEの30秒ルールでエラーになるかと思います


その原因については後で考察しますが、
とりあえず作るまでのポイントを大雑把に

環境構築(GAEでSinatra


基本的に以前書いたことそのままです
GAEでJRubyのSinatraを動かすまでのメモ - どっかのBlogの前置きのような


前回不明確なままだったGemfileに関してですが、
ローカルでアプリを初回起動するときに自動で作られるようです

# Gemfile

# Critical default settings:
disable_system_gems
disable_rubygems
bundle_path ".gems/bundler_gems"

# List gems to bundle here:
gem "appengine-rack"
gem "dm-appengine"   # これと
gem "sinatra"        # これのコメントを外す

datastoreへの保存


最初に調べたとき、データのdeleteができない的なことを見た気がして、
もう一度調べてみたところ、他にもできないって記事がありました
(後述の「おまけ」参照)


とはいえ、それらはGoogleの推奨するDataMapper経由ではなく、
もっとローレベルのAPI(appengine-api)を叩いていたので、
それが原因かな・・・とも


結論から言えば、DataMapperを経由すれば、
問題なくCRUD操作が可能でした(`・ω・´) b

class Dictionary
  include DataMapper::Resource

  property :id, Serial
  property :word, String, :index => true
  property :explanation, Text

  def add_explanation(exp)
    @exp_list ||= []
    @exp_list << exp
  end

  def before_save
    @exp_list ||= []
    self.explanation = @exp_list.to_yaml
  end

  def explanation_list
    @exp_list ||= YAML.load(self.explanation)
    @exp_list ||= []
    @exp_list
  end
  
  def size
    explanation_list.size
  end
end


本来「用語」と「説明」で1:nの定義をすべきですが、
GAEのBigTableであまり関連は使いたくないので、
内部的には「説明リスト」をYAML化して保存してます


これだと説明文を検索するとき困るのですが、
今回はさくっと動かしたかったのでこれに


用語リストを取得するときも全部取ってきてしまうのが冗長ですが、
BigTableの読み込みは速いはずなので、
大丈夫だろうという読みもありました


ちなみに、wordに対してIndexを要求してみたのですが、
datastore上ではidにのみIndexが生成されていました


XMLで明示的に作ろうとしたらwarningが出たので、
おそらくGAE側で不要と判断されたようです

ユーザ認証


今回の「悪魔の辞典」は、一般ユーザは閲覧だけで、
登録・更新・削除は私だけが可能な仕様になってます
(あくまで現段階では、ですが)


そのためにGoogleのユーザ認証APIを使ったのですが、
それでいくつか詰まったポイントが

認証後の戻り先がおかしい


http://code.google.com/p/appengine-jruby/wiki/UsingTheUsersService


ここに書かれている方法で、「/xxx」に対して認証をかけると、
なぜか「/xxx/xxx」に飛ばされてエラーになりました


仕方ないので、AppEngine::Users.create_login_url('/') としてますが、
いまいちどう直したらいいのかわかりません

管理者権限の取得の仕方が変


Appの管理者であるかは、Java等だとUserオブジェクトに問い合わせればわかります
ただ、JRubyAPIだと、AppEngine::Users::User#admin?ではなく、
AppEngine::Users.admin?なのです


http://code.google.com/p/appengine-jruby/wiki/UsingTheUsersService

This is a separate function, and not a member function of the User class, because admin status is not persisted in the datastore. It only exists for the user making this request right now.


つまり、ログイン済みのユーザが管理者なのか、
このAPIではわからない、ということですΣ(゚Д゚)ガーン
仕方ないので、今はAppEngine::Users::User#emailで判断してます


でも、Java版のUserAPIでできることが、
JRubyでできないってのも変な話ですが・・・何でなんでしょう?

遅延の原因


とりあえず、ここまででローカル環境での動作はOKだったので、
早速本番にUploadして動かしたわけですが、
ごらんの通り、相当遅いです(´・ω・`)


考えられる原因は以下

  • JRubyが遅すぎる?
  • DataMapperが遅い?
  • YAML変換が重い?


これを確認するには、GAEの上でSinatraのサンプルをそのまま動かしてみればいいはず


http://3.latest.parrot-test.appspot.com/(いつ消すかわからないのでリンクなし)


この程度のを動かしても重いので、
GAEでのJRubyが重たい、ということになりそうです


何度かリクエストを投げると速くなるので、
たぶんVMの起動に時間がかかっているんでしょう


さらにJRubyの変換コストが上乗せされるため、
30秒ルールに引っかかるほどの遅さになってしまう・・・と


もしかしたらRackが重たいのかもしれませんが、
RailsにせよSinatraにせよ、JRubyのRackで動くわけで、
現時点ではちょっと実用に耐えない感じです


今後のチューニングに期待ですかね
一番いいのはRubyのネイティブ対応ですが・・・




おまけ:appengine-apisを使うと削除できない原因


http://www.machu.jp/diary/20090903.html
http://blog.livedoor.jp/maru_tak/archives/cat_10010330.html


appengine-apisを使って削除できない原因ですが、
AppEngine::Datastore.delete(key) ではなく、
AppEngine::Datastore.delete(nil, key) かな・・・と


APIドキュメントを見ると、
第1引数がトランザクションオブジェクトを要求していて、
デフォルトがnilになってます


http://appengine-jruby.googlecode.com/hg/rdoc/appengine-apis/classes/AppEngine/Datastore.html#M000006


なので、delete(key)だと、引数が足りないんじゃないかと