Guidoがここ数ヶ月関わっているというGoogle App EngineのDatastore Plusについてブログで紹介しています。
App Engine標準のdbモジュールの代替として使われるべく開発されているモジュール。dbモジュールで提供されているKeyやqueryが変更可能(mutable)であるなど,デザイン上の不合理を解決したより明確な設計を採用(Guidoが直したかったんですね)。
またdatastoreへの非同期アクセスを可能にしています。重たいクエリ,相対的に遅い書き込みにかかる時間や,レイテンシを回避できるようになります。datastore非同期への非同期アクセスは,(アンドキュメンテッドな)低レベルAPIを使ったPythonのモジュールとして提供されています。実際の操作にはデコレータとジェネレータを組み合わせて使います。これがなかなか面白い:-)。
Datastore Plusはndbという名前のモジュールの形で提供されます。「from ndb import model, context, tasklets」のように必要なモジュールをimportします。
datastoreに非同期でアクセスしたいハンドラはtaskletsと呼ばれるデコレータでデコレーションをします。クエリの非同期アクセス用メソッドをyieldすることで,非同期のアクセスを実現しています。チュートリアルにある例からコードを拝借すると,こんな感じ。
class HomePage(webapp.RequestHandler):
@context.toplevel
def get(self):
msg = yield Greeting.get_or_insert_async('hello', message='Hello world')
self.response.out.write(msg.message)
太字にした部分が従来のコードと異なる部分ですが,コードの構造としてはほぼこれまでと同じですね。これだけで,DatastoreのAPIコールが非同期に動き,処理をするようになります。DatastoreのAPIコール中はCPUタイムを消費しなくなる(はず)なので,30秒ルールも気にしなくてよくなるし,その分課金も安くなる。また,DatastoreへのAPIコール完了を待つことなく,ユーザにレスポンスを返す,みたいなことも簡単にできるようになります。
デコレータ「context.toplevel」の部分がtaskletsと呼ばれるもの。メソッド中のyield文に続くModelのメソッドを受けて,APIコールを非同期で動かします。この例では,Modelのメソッドを使ってデータを登録していますが,queryを使って非同期コールを実行することもできます。
taskletsを使うとコールバック関数を作ることもできます。qryはクエリオブジェクトであることに注意してください。
@tasklets.tasklet
def callback(message):
key= Key('Account', message.userid)
acct = yield key.get_async()
raise tasklets.Return(message, acct)
results = yield qry.map_async(callback)
関数をtasklets.taskletでデコレートし,非同期メソッドをyieldで扱うようにするだけ。あとはクエリオブジェクトのmap_async()メソッドにコールバックを渡してやれば,簡単に非同期で動くコールバック関数を作ることができます。
その他,taskletsを使うとトランザクションも実行できるようになります。
Datastore APIコールにかかる時間やレイテンシを迂回する手法は他にもいくつかありますが,デコレータとジェネレータを使うDatastore Plusの手法は簡単だし,既存のコードにも組み込みやすいという利点があります。アプリの設計も簡単になり,かなりスマートな方法だといえますね。Google App Engineの開発にはGuidoを初めとしたPythonのチームが深く関わっていることはよく知られた事実ですが,このように便利なモジュールがいち早く使えることは,Pythonを使ってApp Engineの開発をすることの利点と言えるかも知れません。