Конфликт в хранилище данных 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):

  1. Включите отложенный в вашем app.yaml
  2. Измените вызовы на 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)
    
Другие вопросы по тегам