我们在 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 个子实体ChildKindA
和ChildKindB
实体 - 可能是 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
实体,只删除与我们初始查询匹配且是其子级的实体。