多余的ndb.Model.put_async()调用最终只发送一次到数据存储区?

Pas*_*que 3 google-app-engine app-engine-ndb

我有一个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()
Run Code Online (Sandbox Code Playgroud)

基本上,每个可能更新实体的方法都会返回一个bool来指示实体是否已更新,因此需要保存.按顺序调用这些方法时,put()如果返回任何方法,我确保调用True.

但是,在涉及其他子例程的情况下,这种模式可能很复杂.在这种情况下,我需要使从子程序返回的更新的布尔值冒泡到顶级方法.

我现在正在优化我的许多请求处理程序,尝试尽可能多地限制AppStat报告的瀑布,使用尽可能多的异步API并将大量方法转换为tasklet.

这项工作使我开始阅读NDB异步文档,该文档提到NDB实现了一个autobatcher,它在单个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()
Run Code Online (Sandbox Code Playgroud)

是否所有呼叫put_async()都会合并为一个put实体呼叫?如果是,是否有任何警告使用这种方法与坚持手动检查更新的返回值并put在调用序列结束时调用一次?

Pas*_*que 6

好吧,我咬了一下子弹并在一个测试GAE应用程序中测试了这3个场景,启用了AppStat来查看正在进行的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)
Run Code Online (Sandbox Code Playgroud)

第一个,ThreePutsHandler显然最终会召唤Put3次.

ThreePutsHandler AppStat跟踪

但是,另外两个正在调用的测试put_async()最终只能调用Put:

ThreePutsAsyncHandler AppStat跟踪 ThreePutsTaskletHandler AppStat跟踪

所以我的问题的答案是:是的,冗余的ndb.Model.put_async()调用由NDB的自动调度功能进行批处理,最终作为单个datastore_v3.Put调用结束.这些put_async()调用是否在一个tasklet中进行并不重要.

关于在测试结果中观察到的数据存储区写操作数的说明:正如Shay在注释中指出的那样,每个修改的索引属性值有4个写入加上实体的1个写入.所以在第一次测试(3次连续put)中,我们观察到(4 + 1)*3 = 15次写操作.在另外两个测试(异步)中,我们观察到(4*3)+ 1 = 13个写操作.

因此,最重要的是,put_async对同一实体进行NDB批量多次调用可以通过单次调用数据存储来为我们节省大量延迟,并通过仅编写一次实体来节省一些写操作.