4

一部のグローバル アプリ設定を保存するために使用されるエンティティがあります。これらの設定は管理 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()最初の呼び出しはおそらくSettingsmemcache からエンティティを取得し (エンティティはめったに更新されないため)、同じ要求ハンドラー内の後続のすべての呼び出しはそれを取得するため、要求ハンドラーごとに複数回呼び出すとかなり高速になると想定していました。 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.GetAppstats で への 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、問題が修正されます。

4

1 に答える 1

2

トップレベルが新しい ndb コンテキストを作成するようです。

正確には、デコレーターを持つ各ハンドラーにはtoplevel独自のコンテキストがあり、したがって個別のキャッシュがあります。toplevel以下のリンクのコードを参照してください。関数のドキュメントには、 toplevel「新しいデフォルト コンテキストを設定する同期タスクレット」と記載されています。

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

于 2013-08-28T19:58:46.093 に答える