Конфликт в хранилище данных GAE
Наше приложение GAE делает локальную копию реляционной базы данных другого сайта в NDB. Существует 4 типа сущностей: Пользователь, Таблица, Строка, Поле. У каждого пользователя есть куча таблиц, у каждой таблицы есть куча строк, у каждой строки есть куча полей.
SomeUser> SomeTable> ARow> AField
Таким образом, каждый пользователь становится одной группой лиц. Мне нужна функция, где я могу очистить все таблицы (и их строки) для определенного пользователя. Как правильно удалить все таблицы и все строки, избегая ограничения конкуренции ~5 операций в секунду.
Текущий код становится TransactionFailedError
из-за разногласий в Entity Group.
(деталь, которую я упустил из виду, это то, что мы хотим удалять только таблицы с атрибутом "service", установленным на определенное значение)
def delete_tables_for_service(user, service):
tables = Tables.query(Tables.service == service, ancestor=user.key).fetch(keys_only=True)
for table in tables:
keys = []
keys += Fields.query(ancestor=table).fetch(keys_only=True)
keys += TableRows.query(ancestor=table).fetch(keys_only=True)
keys.append(table)
ndb.delete_multi(keys)
2 ответа
Если все объекты, которые вы удаляете, находятся в одной группе объектов, попробуйте удалить их все в одной транзакции. Без явной транзакции каждое удаление происходит в своей собственной транзакции, и все транзакции должны быть выстроены в ряд (посредством конфликтов и повторных попыток), чтобы изменить группу объектов.
Вы уверены, что это основано на конкуренции, или, возможно, потому что приведенный выше код выполняется внутри транзакции? Быстрое исправление может заключаться в увеличении количества повторных попыток и включении транзакций между группами для этого метода:
@ndb.transactional(retries=5, xg=True)
Подробнее об этом вы можете прочитать здесь: https://developers.google.com/appengine/docs/python/ndb/transactions. Если это не является причиной, возможно, рассмотрите возможность отсрочки или запуска асинхронного удаления, чтобы они выполнялись со временем и небольшими партиями. Хитрость с NDB состоит в том, чтобы делать небольшие всплески работы регулярно, а нередко - большую часть работы. Вот один из способов превратить этот код в асинхронную единицу работы:
def delete_tables_for_service(user, service):
tables = Tables.query(Tables.service == service, ancestor=user.key).fetch(keys_only=True)
for table in tables:
# Delete fields
fields_keys = Fields.query(ancestor=table).fetch(keys_only=True)
ndb.delete_multi_async(fields_keys)
# Delete table rows
table_rows_keys = TableRows.query(ancestor=table).fetch(keys_only=True)
ndb.delete_multi_async(table_rows_keys)
# Finally delete table itself
ndb.delete_async(table.key)
Если вам нужен больший контроль над удалениями, повторными попытками, сбоями, вы можете использовать Очереди задач или просто использовать отложенную библиотеку ( https://developers.google.com/appengine/articles/deferred):
- Включите отложенный в вашем app.yaml
Измените вызовы на ndb.delete_multi на отложенные:
def delete_tables_for_service(user, service): tables = Tables.query(Tables.service == service, ancestor=user.key).fetch(keys_only=True) for table in tables: keys = [] keys += Fields.query(ancestor=table).fetch(keys_only=True) keys += TableRows.query(ancestor=table).fetch(keys_only=True) keys.append(table) deferred.defer(_deferred_delete_tables_for_keys, keys) def _deferred_delete_tables_for_keys(keys): ndb.delete_multi(keys)