Делать массовые db.delete на App Engine, не съедая процессор - PullRequest
7 голосов
/ 15 декабря 2010

У нас в 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 запроса.

Ответы [ 2 ]

2 голосов
/ 16 декабря 2010

Недавно мы добавили обработчик массового удаления, задокументированный здесь .Для этого требуется наиболее эффективный подход к массовому удалению, хотя он все еще потребляет квоту ЦП.

1 голос
/ 16 декабря 2010

Если вы хотите разогнать процессор, вы можете создать карту сокращения .Он по-прежнему будет перебирать каждую сущность (это текущее ограничение API картографа).Однако вы можете проверить, соответствует ли каждая сущность условию, и удалить или нет в это время.

Чтобы замедлить использование ЦП, назначьте преобразователь в очередь задач, настроенную для медленной работы.Вы можете распределить время выполнения на несколько дней и не съесть всю свою квоту на процессор.

...