7

我们在 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)

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

这在当时似乎是合理的,但我们运行了一批作为测试,结果查询需要 9 秒才能运行——并且在可计费的 CPU 时间上花费了 1933秒来访问数据存储。

这看起来相当苛刻——每个实体 0.5 秒计费!- 但我们并不完全确定我们做错了什么。仅仅是批次的大小吗?祖先查询特别慢吗?或者,删除(实际上是所有数据存储访问)是否像糖蜜一样慢?

更新

我们将查询更改为keys_only,虽然这将运行一批的时间减少到了 4.5 秒,但它仍然花费了大约 1900 秒的 CPU 时间。

接下来,我们将 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)

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

Delete通话是手术中最昂贵的部分!

有没有解决的办法?Nick Johnson 提到,使用批量删除处理程序是目前最快的删除方式,但理想情况下,我们不想删除一种类型的所有bar = foo实体,只删除与我们初始查询匹配且是其子级的实体。

4

2 回答 2

2

我们最近添加了一个批量删除处理程序,记录在这里。它采用最有效的方法进行批量删除,但仍会消耗 CPU 配额。

于 2010-12-15T22:09:56.897 回答
1

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

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

于 2010-12-16T19:27:53.360 回答