У нас в 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 секунд в оплачиваемое время ЦП на доступ к хранилищу данных.
Это кажется довольно резким - 0,5 оплачиваемых секунд на единицу! - но мы не совсем уверены, что делаем неправильно. Это просто размер партии? Запросы предков особенно медленные? Или удаления (и действительно, все обращения к хранилищам данных) просто медленны, как патока?
Обновление
Мы изменили наши запросы на keys_only
, и хотя это сократило время выполнения одного пакета до 4,5 реальных секунд, это все равно стоило ~ 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
- самая дорогая часть операции!
Есть ли способ обойти это? Ник Джонсон отметил, что использование обработчика массового удаления является самым быстрым способом удаления в настоящее время, но в идеале мы не хотим удалять всех объектов вида, только те, которые соответствуют и являются потомками нашего начального bar = foo
запроса.