google-app-engine – 为什么没有从NDB的上下文缓存中获取实体?

我有一个实体用于存储一些全局应用程序设置.这些设置可以通过管理员
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

The in-context cache persists only for the duration of a single incoming HTTP request and is “visible” only to the code that handles that request. It’s fast; this cache lives in memory.

但是,AppStat显示我的Settings实体正在同一个请求处理程序中多次从memcache中检索.我通过在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创建了一个新的ndb上下文.

将嵌套的toplevel更改为synctasklet可以解决问题.

最佳答案

It seems like a toplevel creates a new ndb context.

确切地说,每个具有顶层装饰器的处理程序都有自己的上下文,因此具有单独的缓存.您可以在下面的链接中查看顶层代码,在函数文档中声明顶层是“设置新的默认上下文的同步tasklet”.

https://code.google.com/p/googleappengine/source/browse/trunk/python/google/appengine/ext/ndb/tasklets.py#1033

点赞