2010年04月30日

Google App EngineでWebサービスを作ってみての ”実際のところ”

先日、Amazonの価格をチェックしてメールでお知らせするWebサービス「マケプレ・フラグ」を公開してみた。
このWebサービスは、sinatra on GAE/JRuby という構成で作ってあるのだけど、実は4、5日程度でひととおりの機能が動作するくらいになっていた。
別にGAEバンザイと言いたい訳ではなくて、本題はここから。
GAEって制限が多くあるので、これを回避するのが結構大変。さらに「マケプレ・フラグ」は価格情報を得るためにAmazonのProduct Advertising APIを使っていて、実はこちらにも制限がある。


GAEは30秒以内にレスポンスを返さなくてはいけない

利用者が商品検索して30秒も待ってくれる訳はないので、それは問題にならない(というより30秒も待たせるならGAEに関係なく設計を見直すでしょ)。
問題は、cronで実行するようなバッチ処理も同様の制限があるということ。
「マケプレ・フラグ」では、登録されている全ての商品の価格を定期的をチェックする必要がある。当然30秒で全てチェックできない。
また、希望の割引率を上回ればメールを利用者に送る。1通だけなら問題ないけど、複数の利用者に送ることになるので、これも30秒で終わらない。

対処法

処理を分割してTaskQueueを利用する。
「マケプレ・フラグ」では、大量に価格チェックする処理があるので、商品毎に分割してTaskQueueを使ってみた。
GAE/JRubyでTaskQueueを使うには、cronと同様にURLを叩くだけ。


require 'appengine-apis/labs/taskqueue'

queue = AppEngine::Labs::TaskQueue::Queue.new
task = AppEngine::Labs::TaskQueue::Task.new(:url => '/taskqueue_sample')
queue.add task


これだけで使える。


GAEは負荷がかかっているときは10秒以内に処理しなければいけない

今回Webサービスを作るまで、30秒制限は有名なので知っていたけど、こっちは知らなかった。

"Request was aborted after waiting too long・・・"というエラーが出ている場合は、この制限に引っかかっている。
価格をチェックするためにTaskQueueを使ってAPI呼び出しを大量にしていたら、このエラーが発生していた。TaskQueueは失敗すると、勝手にリトライしてくれるので、動作自体はしているけど、リソースがもったいない。

対処法

10秒以内に処理が終わるように分割するか、負荷をかけないか、の2択を考えて、後者で対応した。
価格チェックはバッチ処理なので利用者のレスポンスには影響ないので、TaskQueueを間隔を空けて実行することにした。
TaskQueueはcountdownという引数で、その秒数分待ってから実行するように指定できる。


queue = AppEngine::Labs::TaskQueue::Queue.new

products = Product.all()
cnt = 0
for product in products
task = AppEngine::Labs::TaskQueue::Task.new(:url => '/taskqueue_sample', :countdown => cnt)
queue.add task
cnt += 3
end


今回は、このように、3秒おきに実行するようにしたら、エラーが出なくなった。


GAEのスピンアップに時間がかかる

もしかすると、GAEをpythonで利用していたり、フレームワークを挟まず利用していれば大丈夫なのかもしれない。
GAEは利用者からのアクセスがないとすぐスピンダウンする。スピンダウン後の初回アクセスはスピンアップの処理に時間がかかる。
今回、sinatra on GAE/JRubyという、JavaVM、Ruby、sinatraという3段構成で作ってしまったため、スピンアップの時間がすごいことになっている。20秒を余裕で超える。
条件が悪いと、スピンアップするだけで、先の30秒制限に引っかかるんじゃないかと思うときもあった。(sinatraでこんな状態なのにRailsで実運用できるのだろうか。)
バッチ処理のときにスピンアップするならともかく、利用者がアクセスしたタイミングでスピンアップされると、ページを表示するまで、20から25秒近く待たせる訳で、自分が利用者ならその時点で帰る。


対処法

重量級のフレームワークを使わないというのも対処法だけど今回はすでに時遅し。
「マケプレ・フラグ」では、仕方無く1、2分おきにcronで何もしないダミーのURLにアクセスすることで、スピンダウンしないようにしている。ただ、この方法はできればやらないで欲しいということらしい。
でも、他に回避策がないので、今のところはこのままで。


GAEの無料リソースだとメールを1分に8通までの送信にしないといけない

GAEを無料リソース内で利用する場合、メール送信は1分に8通までという制限がある。AmazonのAPIは1秒に1回以上のアクセスを多少しても大丈夫だったりするけど、GAEは速攻で例外になるので注意。


対処法

Memcacheに最後に送信した時刻を保持。メールを送信するときに、その時刻を確認して、8秒程度の間隔より短かい場合に待つようにする。
TaskQueueは並列処理なのでMemcacheを使うときに排他制御しておかないといけない。

Memcacheでスピンロックを実装してTask Queue処理結果を集約してみるテスト

排他制御は上記サイトのようにmutexを実装したacquireLockと、releaseLockを利用。


acquireLock('mail_lock')
last_send = memcache.get('last_sendmail')
if not last_send.nil? then
sleep(8 - (Time.now - last_send))
end
memcache.set('last_sendmail', Time.now, 8)
releaseLock('mail_lock')


こんな感じでMemcacheの有効期間を8秒でセットして、Memcacheに時刻が保持されていた場合に、その時刻から8秒過ぎるまで待つようにした。


Product Advertising APIは、1秒に1回以上リクエストしてはいけない

APIを呼びだしたら、1秒待つように作ればいいという訳じゃない。Webサービス全体で1秒に1回以上リクエストしてはいけないというところがポイント。
「マケプレ・フラグ」では、利用者が商品検索する、商品ページを表示するときやcronで定期的に価格をチェックするときにAPIを呼び出す。
例えば違うユーザが同時に商品を検索すれば、1秒に1回の制限に引っかかる可能性がある。cronで商品の価格をチェックしている最中にユーザがページにアクセスすれば、かなりの確率で引っかかるだろう。


対処法

Memcacheに最後にAPIでアクセスした時刻を保持。1秒以内の連続呼び出しの場合に待つようにする。
これは上記のメール送信と同様の仕組みなので以下省略。



まとめ

このように、結局、回避する設計を考えたり、変更するのに、さらに4日程度かかった。
GAEは簡単にサービスを作れるのだけど、こういう制約を回避しつつ、ユーザが不便にならないように設計するのは、実は大変という、そういう話。

これらの制限の内、解消される予定のものがあるので、期待している。

Google App Engineのロードマップ。半年以内に30秒制限もスピンアップ待ちも撤廃?

posted by cuckoo at 19:36 | Comment(0) | TrackBack(0) | プログラミング

2010年04月19日

sinatra on GAE/JRubyでAmazonの価格チェッカーを作ってみた

以前、アカウントだけ取って放置したあったGoogle App Engineで、Amazonの価格を定期的にチェックする仕組みをサービス化してみた。

私はよくAmazonのマーケットプレイスで本を買っているけど、もう少し価格が下ったら買おうと思ったものは、ことごとく忘れてしまって、後で後悔したりする。
価格チェッカーの類いを調べてみても、会員登録が面倒だったり、せどり用なものが多くて、気軽に使えるものがなく、結局自分用に作ってみた。
もともと自分用だったけど、周囲の評判も良いので、デザインを再設計して公開してみることにした。

マケプレ・フラグ

検索ボックスで商品を検索して、商品ページを開き、メールアドレスを入力して希望の割引率を選ぶだけ。
一度利用すればメールアドレスは入力済みになっているので、商品ページを開いて割引率を選ぶだけの2ステップで登録できる。
ブックマークレットも用意してAmazonの商品詳細ページからも一発で利用できるようにした。

ちなみに、このサービスはsinatra on GAE/JRubyという構成で作ってみた。
GAEはしょっちゅうスピンダウンされてしまうため、スピンアップの時間がばかにならない。
Rubyで作りたいってだけで余分なフレームワークを挟んでしまったのを後悔したのは秘密。

あまりよくないことだが、cronで定期的にurlを叩いてスピンダウンしないようにしている。

さて、次は何を作ろうかな。
posted by cuckoo at 20:53 | Comment(0) | TrackBack(0) | プログラミング
タグクラウド

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。