Google App Engine のdatastore readコストを極力まで減らす

だいたいgaeのdatastore関連の問題ってのはwriteのコストに起因することで、そこをイカに減らすかがノウハウ(あるいはバッドノウハウ)なんですが、今作ってるサービスでは珍しくreadのコストが問題になってきました。

で、readのコストを減らす定番が「keyを活用する」ということで、key読み込みだけならばコストは発生しない、ということ。

qiita.comこのあたりを参考にすると、なるほどcountでも1read単位で済むらしい。

q = act_db.query()
logging.info("actress_db count:%s" % q.count())
for a in q.fetch(keys_only=True):
logging.error("id -> %s" % a.id())

このコードのコストは管理コンソールの割当ページで見るところの「Datastore読み取り操作」で1件分である(もしかしたらcountでもう1件で2件かも)。forループのfetchもkeys_onlyなら0件である。当然ループの中でa.get()してしまうとこのループの件数だけreadが発生して大爆発になる。a.id()だけならkey(あるいはIDと言い方もある)を読むだけなので0コスト。

そこでdatastore writeの時にid(key)に意味を持たせてやれば最小コストに近づくというわけである。

keyに意味をもたせるノウハウはこの辺から↓

Google App Engine / Python 上での開発で最初から知ってればよかった、ってことをいくつか - Masatomo Nakano Blog

writeはこれで

act_db(id=nanika_uniq, name=nanika_name).put()

readはこんな

q = act_db.get_by_id(url)

これはentityを返すのでget()も兼ねている。

gqlを使うなら

gql_q = ndb.gql("select __key__ from act_db where __key__ = KEY('act_db', :1)", nanika_uniq)

これはkeyだけ返す。どちらもread1件である。keyだけを返すgqlもreadを食うのが謎であるが、そういうものなんだろう。

で、ここまでが調べればすぐに分かる話である。

今回ハマったのが「readの1件すら惜しい」ケース。keyだけで済むケース、例えば存在確認なんかの場合。

url = "yahoo.co.jp"
q = act_db.query()
for a in q.fetch(keys_only=True):
q = url_db.get_by_id(url) # q is entity
# or q = ndb.gql("select __key__ from url_db where __key__ = KEY('url_db', :1)", url).fetch(keys_only=True) #q is key
# or q = url_db.query(url_db.url == url).fetch(keys_only=True) #q is key
# 上記全てread1件であるのでどれを使っても同じ。keyかentityかの違いはあるが今回はkeyだけ欲しい状況
if q:
shori()

forループの中のget_by_id()はread1件ではあるが、ループの回数だけ塵も積もれば山となる。何しろ無料でreadできるのは5万件しか無い。これを十分と取るか少ないと取るか。

最終的にどうしたかというと、やっぱりmemcacheである。

#全部リードでread1件
q = url_db.query()
for a in q.fetch(keys_only=True):
memcache.set(a.id(), True)
 
url = "yahoo.co.jp"
 
#ここでもread1件
q = act_db.query()
for a in q.fetch(keys_only=True):
if memcache.get(url):
shori()

全部でread2件で済む。

いざ書いてみると、当たり前の話だが、key利用だけではコストが安くならない場合もあるということで、もうひと工夫必要なこともあるということで。

 

#追記

記事公開したら、ソース部分のインデントと改行がおかしくなった。if、forは適宜対応でお願いします。