一部のグローバル アプリ設定を保存するために使用されるエンティティがあります。これらの設定は管理 HTML ページから編集できますが、変更されることはほとんどありません。このエンティティのインスタンスは 1 つしかなく (一種のシングルトン)、設定にアクセスする必要があるときは常にこのインスタンスを参照します。
要約すると、次のようになります。
class Settings(ndb.Model):
SINGLETON_DATASTORE_KEY = 'SINGLETON'
@classmethod
def singleton(cls):
return cls.get_or_insert(cls.SINGLETON_DATASTORE_KEY)
foo = ndb.IntegerProperty(
default = 100,
verbose_name = "Some setting called 'foo'",
indexed = False)
@ndb.tasklet
def foo():
# Even though settings has already been fetched from memcache and
# should be available in NDB's in-context cache, the following call
# fetches it from memcache anyways. Why?
settings = Settings.singleton()
class SomeHandler(webapp2.RequestHandler):
@ndb.toplevel
def get(self):
settings = Settings.singleton()
# Do some stuff
yield foo()
self.response.write("The 'foo' setting value is %d" % settings.foo)
Settings.singleton()
最初の呼び出しはおそらくSettings
memcache からエンティティを取得し (エンティティはめったに更新されないため)、同じ要求ハンドラー内の後続のすべての呼び出しはそれを取得するため、要求ハンドラーごとに複数回呼び出すとかなり高速になると想定していました。 NDB のインコンテキスト キャッシュから。ドキュメントから:
インコンテキスト キャッシュは、単一の着信 HTTP 要求の間だけ保持され、その要求を処理するコードにのみ「表示」されます。これは速い; このキャッシュはメモリ内にあります。
ただし、AppStat は、Settings
エンティティが同じリクエスト ハンドラー内で memcache から複数回取得されていることを示しています。これは、AppStat のリクエスト ハンドラの詳細ページをmemcache.Get
見て、各呼び出しの呼び出しトレースを展開し、取得されている memcahe キーを調べることでわかります。
リクエスト ハンドラーで多くのタスクレットを使用しSettings.singleton()
ており、設定へのアクセスが必要なタスクレット内から呼び出します。これが、設定エンティティがインコンテキスト キャッシュからではなく、memcache から再度フェッチされる理由でしょうか? もしそうなら、コンテキスト内キャッシュからエンティティをフェッチできるかどうか/いつフェッチできるかを管理する正確なルールは何ですか? NDB ドキュメントでこの情報を見つけることができませんでした。
2013/02/15 更新:ダミー テスト アプリケーションでこれを再現できません。テストコードは次のとおりです。
class Foo(ndb.Model):
prop_a = ndb.DateTimeProperty(auto_now_add = True)
def use_foo():
foo = Foo.get_or_insert('singleton')
logging.info("Function using foo: %r", foo.prop_a)
@ndb.tasklet
def use_foo_tasklet():
foo = Foo.get_or_insert('singleton')
logging.info("Function using foo: %r", foo.prop_a)
@ndb.tasklet
def use_foo_async_tasklet():
foo = yield Foo.get_or_insert_async('singleton')
logging.info("Function using foo: %r", foo.prop_a)
class FuncGetOrInsertHandler(webapp2.RequestHandler):
def get(self):
for i in xrange(10):
logging.info("Iteration %d", i)
use_foo()
class TaskletGetOrInsertHandler(webapp2.RequestHandler):
@ndb.toplevel
def get(self):
logging.info("Toplevel")
use_foo()
for i in xrange(10):
logging.info("Iteration %d", i)
use_foo_tasklet()
class AsyncTaskletGetOrInsertHandler(webapp2.RequestHandler):
@ndb.toplevel
def get(self):
logging.info("Toplevel")
use_foo()
for i in xrange(10):
logging.info("Iteration %d", i)
use_foo_async_tasklet()
テスト ハンドラーを実行する前に、キー名がsingletonFoo
のエンティティが存在することを確認します。
実稼働アプリで見ているものとは対照的に、これらのリクエスト ハンドラーはすべて、memcache.Get
Appstats で への 1 つの呼び出しを示しています。
更新 2013/02/21:やっとダミー テスト アプリケーションでこれを再現できるようになりました。テストコードは次のとおりです。
class ToplevelAsyncTaskletGetOrInsertHandler(webapp2.RequestHandler):
@ndb.toplevel
def get(self):
logging.info("Toplevel 1")
use_foo()
self._toplevel2()
@ndb.toplevel
def _toplevel2(self):
logging.info("Toplevel 2")
use_foo()
for i in xrange(10):
logging.info("Iteration %d", i)
use_foo_async_tasklet()
このハンドラーはmemcache.Get
、実稼働コードと同様に、Appstats に 2 つの呼び出しを表示します。
実際、私のプロダクション リクエスト ハンドラのコードパスには、toplevel
別の から呼び出されたがありtoplevel
ます。toplevel
新しいndbコンテキストを作成するようです。
ネストされたtoplevel
を a に変更するとsynctasklet
、問題が修正されます。