3

状態を操作するためのいくつかのインスタンスメソッドを公開するNDBモデルがあります。一部のリクエストハンドラでは、これらのインスタンスメソッドのいくつかを呼び出す必要があります。同じエンティティで複数回呼び出されないようにするためにput()、これまでに使用したパターンは次のようになります。

class Foo(ndb.Model):
    prop_a = ndb.StringProperty()
    prop_b = ndb.StringProperty()
    prop_c = ndb.StringProperty()

    def some_method_1(self):
        self.prop_a = "The result of some computation"
        return True

    def some_method_2(self):
        if some_condition:
            self.prop_b = "Some new value"
            return True
        return False

    def some_method_3(self):
        if some_condition:
            self.prop_b = "Some new value"
            return True
        if some_other_condition:
            self.prop_b = "Some new value"
            self.prop_c = "Some new value"
            return True
        return False

def manipulate_foo(f):
    updated = False
    updated = f.some_method_1() or updated
    updated = f.some_method_2() or updated
    updated = f.some_method_3() or updated
    if updated:
        f.put()

基本的に、エンティティを更新する可能性のある各メソッドは、エンティティが更新されたために保存する必要があるかどうかを示すブール値を返します。put()これらのメソッドを順番に呼び出すときは、いずれかのメソッドが返された場合は必ず呼び出すようにしますTrue

ただし、このパターンは、他のサブルーチンが関係する状況では実装が複雑になる可能性があります。その場合、サブルーチンから返された更新されたブール値をトップレベルのメソッドにバブルアップする必要があります。

現在、多くのリクエストハンドラーを最適化する過程にあり、AppStatによって報告されるウォーターフォールを可能な限り制限し、可能な限り多くの非同期APIを使用し、多くのメソッドをタスクレットに変換しようとしています。

この取り組みにより、NDB Asyncのドキュメントを読むことになりました。このドキュメントでは、NDBは、データストアへの1回のRPC呼び出しで複数のリクエストを組み合わせるオートバッチャーを実装していると述べています。これは異なるキーを含むリクエストに適用されることを理解していますが、同じエンティティへの冗長な呼び出しにも適用されますか?

言い換えれば、私の質問は、上記のコードパターンをこれに置き換えることができるかということです。

class FooAsync(ndb.Model):
    prop_a = ndb.StringProperty()
    prop_b = ndb.StringProperty()
    prop_c = ndb.StringProperty()

    @ndb.tasklet
    def some_method_1(self):
        self.prop_a = "The result of some computation"
        yield self.put_async()

    @ndb.tasklet
    def some_method_2(self):
        if some_condition:
            self.prop_b = "Some new value"
            yield self.put_async()

    @ndb.tasklet
    def some_method_3(self):
        if some_condition:
            self.prop_b = "Some new value"
            yield self.put_async()
        elif some_other_condition:
            self.prop_b = "Some new value"
            self.prop_c = "Some new value"
            yield self.put_async()

@ndb.tasklet
def manipulate_foo(f):
    yield f.some_method_1()
    yield f.some_method_2()
    yield f.some_method_3()

すべての呼び出しがエンティティput_async()の単一の呼び出しに結合されますか?はいの場合、このアプローチを使用することと、更新された戻り値を手動でチェックして呼び出しシーケンスの最後に1回呼び出すputことに固執することについて、注意点はありますか?put

4

2 に答える 2

6

さて、私は弾丸を噛み、AppStatを有効にしてテストGAEアプリケーションでこれらの3つのシナリオをテストし、RPC呼び出しが行われていることを確認しました。

class Foo(ndb.Model):
    prop_a = ndb.DateTimeProperty()
    prop_b = ndb.StringProperty()
    prop_c = ndb.IntegerProperty()

class ThreePutsHandler(webapp2.RequestHandler):
    def post(self):
        foo = Foo.get_or_insert('singleton')
        foo.prop_a = datetime.utcnow()
        foo.put()
        foo.prop_b = str(foo.prop_a)
        foo.put()
        foo.prop_c = foo.prop_a.microsecond
        foo.put()

class ThreePutsAsyncHandler(webapp2.RequestHandler):
    @ndb.toplevel
    def post(self):
        foo = Foo.get_or_insert('singleton')
        foo.prop_a = datetime.utcnow()
        foo.put_async()
        foo.prop_b = str(foo.prop_a)
        foo.put_async()
        foo.prop_c = foo.prop_a.microsecond
        foo.put_async()

class ThreePutsTaskletHandler(webapp2.RequestHandler):
    @ndb.tasklet
    def update_a(self, foo):
        foo.prop_a = datetime.utcnow()
        yield foo.put_async()

    @ndb.tasklet
    def update_b(self, foo):
        foo.prop_b = str(foo.prop_a)
        yield foo.put_async()

    @ndb.tasklet
    def update_c(self, foo):
        foo.prop_c = foo.prop_a.microsecond
        yield foo.put_async()

    @ndb.toplevel
    def post(self):
        foo = Foo.get_or_insert('singleton')
        self.update_a(foo)
        self.update_b(foo)
        self.update_c(foo)

app = webapp2.WSGIApplication([
    ('/ndb-batching/3-puts', ThreePutsHandler),
    ('/ndb-batching/3-puts-async', ThreePutsAsyncHandler),
    ('/ndb-batching/3-puts-tasklet', ThreePutsTaskletHandler),
], debug=True)

最初のものは、明らかに3回ThreePutsHandler呼び出すことになります。Put

ThreePutsHandlerAppStatトレース

ただし、呼び出している他の2つのテストは、次のput_async()1回の呼び出しで終了しますPut

ThreePutsAsyncHandlerAppStatトレース ThreePutsTaskletHandlerAppStatトレース

したがって、私の質問に対する答えは次のとおりです。はい、冗長なndb.Model.put_async()呼び出しは、NDBの自動バッチ機能によってバッチ処理され、最終的には単一のdatastore_v3.Put呼び出しになります。そして、それらのput_async()呼び出しがタスクレット内で行われるかどうかは関係ありません。

テスト結果で観察されたデータストア書き込み操作の数に関するメモ:Shayがコメントで指摘したように、変更されたインデックス付きプロパティ値ごとに4つの書き込みと、エンティティの1つの書き込みがあります。したがって、最初のテスト(3シーケンシャルput)では、(4 + 1)* 3=15書き込み操作を観察します。他の2つのテスト(非同期)では、(4 * 3)+ 1=13の書き込み操作が観察されます。

つまり、NDBput_asyncが同じエンティティに対して複数の呼び出しをバッチ処理することで、データストアへの呼び出しを1回行うことで待ち時間を大幅に節約でき、エンティティを1回だけ書き込むことで書き込み操作を数回節約できます。

于 2013-02-07T03:57:48.847 に答える
1

オブジェクト自体に注釈を付け、応答を返す前に確認してみてください。Zopeの_p_changed属性のように。別の代替手段は、戻る前に書き込む必要がある変更されたオブジェクトの要求/スレッドローカルレジストリである可能性があります。GAEのスレッドローカルの例については、google / appengine / runtime/request_environment.pyを確認してください

于 2013-02-06T21:59:24.597 に答える