Google App Engine入門:実行効率を犠牲にせずに開発効率だけを上げるテクニック
2009.11.27
一つ前の富豪プログラミングのエントリーともつながる話だが、Google App Engineは「ちゃんとスケーラビリティを考慮してアプリケーションを作るには何に気をつけなければならないか」を勉強するには絶好の環境だ。そこで今回は、その「ケチな大富豪的なプログラミング」の実践編。
Google App Engine上のアプリをいくつか書いているうちに、必要に迫られて自然発生的にできてきたのが、gdispatchという数十行のコードからなる小さなモジュール(ソースコードはgithubに置いてある)。これをGoogle App Engineに標準で付いて来るwebappと組み合わせてフレームワークとして使っている。
gdispatchを設計する上で重視したのは、
(1)Google App Engine上でのアプリの開発を効率化する上で「明らかにこれがあると開発効率が格段に向上する」というもの以外は含めない
(2)実行効率を最優先に考え、初期化時にできることと毎回のアクセス時のみにしかできないことを明確に分け、毎回のアクセス時にすることを極力少なくして実行時のオーバーヘッドを最小にする
の二つである。
その結果、gdispatchには以下の二つの関数と二つのデコレータのみが入っている。
memoize (デコレータ)
これはPythonを使う人たちの間では良く使われるデコレータのパターンで、関数の戻り値をクロージャを使ってキャッシュすることにより、実行効率を上げる仕組みである。Google App Engineの場合、サーバーが一度メモリーにロードしたモジュール(およびそれに関連するグローバル変数やクロージャ)は、アプリへのアクセスが途絶えてアプリそのものがサーバーから追い出されない限りはメモリーに常駐していてくれるので、memoizeとはとても相性が良い。
kwargs(デコレータ)
これは、HTTPリクエストをプロセスするRequestHandlerのメソッドを実装する際に、getやpostに渡されたパラメータを、関数へのnamed argumentとして渡された形でのコーディングを可能にするデコレータである。
このデコレータを使うと、
class NewAccountHandler(webapp.RequestHandler):
def post(self):
email = self.request.get('email')
message = self.request.get('password')
account = self.request.get('account')
...
と書く代わりに、
class NewAccountHandler(webapp.RequestHandler):
@gdispatch.kwargs
def post(self, email, password, account):
...
と書けるのである。
このデコレータのソースコードは以下の通りである。inspectモジュールを使って元の関数(original_func)の引数を調べて、それを元にself.request.get()で取得すべきパラメータの名前のタプル(args)を作っている部分が(getメソッドを置き換える)decorated_func の外側であることに注目して欲しい。こうしておけば、inspect.getargspec()を呼ぶのはモジュールがメモリに読み込まれた時一度だけで、それ以降のHTTPリクエストの処理の際には、クロージャの形でキャッシュされたargsを参照するだけである。
def kwargs(original_func):
""" This decorator allows RequestHandlers to receive get/post parameters as named arguments """
import inspect
argspec = inspect.getargspec(original_func)
args = tuple(argspec[0][1:])
def decorated_func(rh):
kwargs = dict([(arg, rh.request.get(arg)) for arg in args])
return original_func(rh, **kwargs)
return decorated_func
route、run (ファンクション)
URLマッピングのルールの記述をしやすくするroute()とrun()はセットで使う。webappフレームワークでプログラムを書く場合、正規表現で記述したURLとRequestHandlerの関連づけは
def main():
application = webapp.WSGIApplication([('/', MainHandler),
('/new', NewAccountHandler),
('/post', NewMessageHandler),
],
debug=True)
wsgiref.handlers.CGIHandler().run(application)
のようにWSGIApplicationに渡すリストで指定するのが一般的な方法だが、マッピングのルールは各RequestHandlerクラスと一緒に記述した方が便利である。そこで作ったのが、route()という関数でこれを使えば、
gdispatch.route(lambda: ('/new', NewAccountHandler))
class NewAccountHandler(webapp.RequestHandler):
def post(self):
...
のように記述することが可能になる。そして、main() では
def main():
gdispatch.run()
とするだけで良い。
このケースでも、route() 関数を呼ぶのはモジュールをメモリにロードした時だけで、その後のHTTPリクエストの処理時には、初回に作ったリストを使ってマッピングを実現することにより実行時のオーバーヘッドを最小にしている。
gdispatchが提供している機能のいずれにも共通するのは、Google App Engineの「一度メモリに読み込んだモジュールは(グローバルメモリやクロージャも含めて)アプリがサーバーから追い出されないかぎりメモリに存在し続ける」という性質を最大限に利用して、実行効率を犠牲にせずに開発効率だけを上げるているという点である。
こんな風に痒いところに手がとどくようなプログラミングが実際のデプロイメント環境を使って簡単にテスト・実践できてしまうところがGoogle App Engineのすばらしいところだと思う今日この頃である。
先日gdispatchを使い始めました。なかなか気持ちいいです!^_^
2つのControllerにgdispatch.run()を使うと、2番目にcallされたControllerにあるマッピングはキャッシュに入っていないようです。1つControllerだけを利用するという理念で作られているのでしょうか?
Posted by: RJ | 2010.12.05 at 17:53
def _url_mapping() についているmemoize (デコレータ)を外したら、問題が解消したようです。
一応ご報告します。宜しくお願いします。
Posted by: RJ | 2010.12.05 at 19:58