メニュー

2011年4月15日

Queryを非同期に使う

Queryは重い処理なので、複数回呼び出すときは非同期にするとよい。
Python版では、Query.run()を使うと非同期呼び出しができる。

例えば Diary というモデルがあったとして:

q = Diary().filter(...)
iterator = q.run()

このようにしてrunメソッドを呼び出すと、
裏では非同期にAPIが呼び出される。

runの戻り値はイテレータであり、
これを使おうとしたときに結果の待ち合わせが行われる。

よって、イテレータを使わないでおいて、
他のQuery.run()を呼び出せば、複数のAPIが並列にできる:

diary_query = Diary().filter(...)
diary_iterator = diary_query.run()

comment_query = Comment().filter(...)
comment_iterator = comment_query.run()

#これ以降でdiary_iterator,comment_iteratorを使う

Query.run()はイテレータを返すので、
Query.fetch()のようにlimitを指定できない。

そのままイテレーションすると、結果と時間の許す限り、
ずっとイテレーションしてしまう。

limitを指定したければイテレーション中に件数をカウント
すればいいが、面倒なので、自前のイテレータでラップする。


class QueryIterator(object):

  def __init__(self, query, limit=None):
    self.limit = limit
    self.count = 0
    if limit:
      config = datastore_query.QueryOptions(limit=limit, prefetch_size=limit)
    else:
      config = None
    self.iterator = query.run(config=config)

  def __iter__(self):
    return self

  def next(self):
    if self.limit and self.limit <= self.count:
      raise StopIteration()
    self.count += 1
    return self.iterator.next()
 
  def get_result(self):
    return [e for e in self]


これを使うと:

q = Diary().filter(...)
iterator = QueryIterator(q, limit=100)

for entity in iterator:
  ...

# 第一引数がq.run()じゃないことに注意

このイテレータは指定された件数に達するとイテレーションを終えるので、
際限なく処理が続いてしまうことはなくなる。

また、結果をリストで欲しい時は:

q = Diary().filter(...)
iterator = QueryIterator(q, limit=100)
entities = iterator.get_result()

のように使える。