在App Engine上进行大批量db.delete,不耗用CPU
我们在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)
我们决定一次删除100个ParentKind
实体;每个ParentKind
大约有40个子实体ChildKindA
和ChildKindB
,总共可能有4000个实体。
当时觉得这样做还不错,但我们测试了一下,结果发现一次查询花了9秒钟,而在访问数据库时,消耗了1933 秒的可计费CPU时间。
这听起来有点夸张——每个实体竟然要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)
来自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
查询条件的实体及其子实体。
2 个回答
我们最近添加了一个批量删除的处理功能,具体说明可以在这里找到。这个功能是以最有效的方式进行批量删除的,不过它还是会消耗一些CPU的配额。
如果你想让CPU的使用更均匀,可以创建一个叫做map reduce的任务。虽然这个任务还是会检查每一个实体(这是目前这个工具的一个限制),但你可以在检查每个实体时判断它是否符合条件,然后决定是否删除它。
为了降低CPU的使用率,可以把这个任务放到一个你设置过的任务队列里,让它的运行速度比平常慢。这样,你可以把运行时间分散到好几天里,就不会一下子消耗掉所有的CPU配额。