我有一个实体用于存储一些全局应用程序设置。这些设置可以通过管理员HTML页面进行编辑,但很少更改。我只有一个这个实体的实例(一个单独的实例),当我需要访问设置时总是引用这个实例。
以下是它归结为:
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()
会非常快,因为第一个调用最有可能从memcache中检索Settings
实体(因为实体很少更新)并且同一请求处理程序中的所有后续调用将从NDB的上下文缓存中检索它。来自documentation:
上下文缓存仅在单个传入HTTP请求的持续时间内持续存在,并且仅对处理该请求的代码“可见”。它很快;这个缓存存在于内存中。
但是,AppStat显示在同一个请求处理程序中多次从memcache检索我的Settings
实体。我通过在AppStat中查看请求处理程序的详细页面来了解这一点,将每个调用的调用跟踪扩展到memcache.Get
并查看正在重新存储的memcahe密钥。
我在请求处理程序中使用了很多tasklet,我从需要访问设置的tasklet中调用Settings.singleton()
。这可能是为什么再次从memcache中取出Settings实体而不是从上下文缓存中取出的原因?如果是这样,管理是否/何时可以从上下文缓存中获取实体的确切规则是什么?我无法在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()
在运行任何测试处理程序之前,我确保存在keyname singleton 的Foo
实体。
与我在生产应用中看到的相反,所有这些请求处理程序都在Appstats中显示对memcache.Get
的单个调用。
更新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()
此处理程序确实在Appstats中显示了对memcache.Get
的2次调用,就像我的生产代码一样。
实际上,在我的生产请求处理程序代码路径中,我有另一个toplevel
调用的toplevel
。似乎toplevel
创建了一个新的ndb上下文。
将嵌套的toplevel
更改为synctasklet
可以解决问题。
答案 0 :(得分:2)
似乎toplevel会创建一个新的ndb上下文。
确切地说,每个具有toplevel
装饰器的处理程序都有自己的上下文,因此具有单独的缓存。您可以在下面的链接中查看toplevel
的代码,在函数文档中指出toplevel
是“设置新默认上下文的同步tasklet”。