在App Engine上做一个大规模的db.delete,不用吃CPU

Bla*_*way 7 python google-app-engine bigtable cpu-usage

我们在Google App Engine上有一个相当大的数据库 - 只有超过50,000个实体 - 我们希望从中清除过时的数据.计划是编写一个延迟任务来迭代我们不再需要的实体,并批量删除它们.

一个复杂因素是我们的实体也有我们想要清除的子实体 - 没问题,我们想; 我们只是查询这些实体的数据存储区,并将其与父项同时删除:

query = ParentKind.all()
query.count(100)
query.filter('bar =', 'foo')
to_delete = []
for entity in enumerate(query):
    to_delete.append(entity)
    to_delete.extend(ChildKindA.all().ancestor(entity).fetch(100))
    to_delete.extend(ChildKindB.all().ancestor(entity).fetch(100))
db.delete(to_delete)
Run Code Online (Sandbox Code Playgroud)

我们限制自己一次删除100个ParentKind实体; 每个人ParentKind有大约40个孩子ChildKindAChildKindB实体 - 可能有4000个实体.

这在当时似乎是合理的,但是我们运行了一个批处理作为测试,结果查询运行了9秒 - 并且在可计算的CPU时间花费了1933 来访问数据存储区.

这似乎相当苛刻 - 每个实体0.5个可计费秒! - 但我们并不完全确定我们做错了什么.它只是批量的大小吗?祖先的查询特别慢吗?或者,删除(实际上,所有数据存储访问)只是作为糖蜜缓慢?

更新

我们将查询更改为keys_only,虽然这样可以将运行一个批处理的时间缩短到4.5个实际秒,但CPU时间仍然需要大约1900秒.

接下来,我们将Appstats安装到我们的应用程序(感谢,kevpie)并运行一个较小的批次 - 10个父实体,总计约450个实体.这是更新的代码:

query = ParentKind.all(keys_only=True)
query.count(10)
query.filter('bar =', 'foo')
to_delete = []
for entity in enumerate(query):
    to_delete.append(entity)
    to_delete.extend(ChildKindA.all(keys_only=True).ancestor(entity).fetch(100))
    to_delete.extend(ChildKindB.all(keys_only=True).ancestor(entity).fetch(100))
db.delete(to_delete)
Run Code Online (Sandbox Code Playgroud)

Appstats的结果:

service.call           #RPCs  real time  api time
datastore_v3.RunQuery  22     352ms      555ms
datastore_v3.Delete    1      366ms      132825ms
taskqueue.BulkAdd      1      7ms        0ms
Run Code Online (Sandbox Code Playgroud)

这个Delete电话是操作中最昂贵的一部分!

有没有解决的办法?尼克约翰逊提到使用批量删除处理程序是目前最快的删除方式,但理想情况下我们不希望删除所有类型的实体,只是那些匹配的实体,并且是我们初始bar = foo查询的子代.

Luk*_*ncl 1

如果你想分散 CPU 消耗,你可以创建一个MapReduce作业。它仍然会迭代每个实体(这是映射器 API 的当前限制)。但是,您可以检查每个实体是否满足条件并在当时删除。

要降低 CPU 使用率,请将映射器分配到已配置为比正常运行速度慢的任务队列。您可以将运行时间分散在几天内,而不会耗尽所有 CPU 配额。