AppEngine:Поддержание согласованности хранилища данных при создании записей

StackOverflow https://stackoverflow.com/questions/522586

Вопрос

Я столкнулся с небольшой дилеммой!У меня есть обработчик voice;когда он вызывается, он устанавливает голос пользователя на то, что он выбрал.Чтобы запомнить, какие варианты они выбрали ранее, я сохраняю параметры VoteRecord, в которых подробно указано, на что установлен их текущий голос.

Конечно, при первом голосовании мне придется создать объект и сохранить его.Но последующие голоса должны просто изменить значение существующей записи VoteRecord.Но у него возникает проблема:при некоторых обстоятельствах могут быть созданы две записи голосования.Это редко (только один раз из всех 500 голосов, которые мы видели до сих пор), но все равно плохо, когда это происходит.

Проблема возникает потому, что два отдельных обработчика по сути делают это:

query = VoteRecord.all().filter('user =', session.user).filter('poll =', poll)

if query.count(1) > 0:
 vote = query[0]

 poll.votes[vote.option] -= 1
 poll.votes[option] += 1
 poll.put()

 vote.option = option
 vote.updated = datetime.now()
 vote.put()
else:
 vote = VoteRecord()
 vote.user = session.user
 vote.poll = poll
 vote.option = option
 vote.put()

 poll.votes[option] += 1
 poll.put()

 session.user.votes += 1
 session.user.xp += 3
 session.user.put()

 incr('votes')

Мой вопрос:Каков наиболее эффективный и быстрый способ обработки этих запросов, гарантируя при этом, что ни один запрос не будет потерян и ни один запрос не создаст два объекта VoteRecord?

Это было полезно?

Решение

Проблема в этой части:

if vote.count(1) == 0:
    obj = VoteRecord()
    obj.user = user
    obj.option = option
    obj.put()

Без транзакции ваш код мог бы выполняться в следующем порядке в двух экземплярах интерпретатора:

if vote.count(1) == 0:
    obj = VoteRecord()
    obj.user = user


if vote.count(1) == 0:
    obj = VoteRecord()
    obj.user = user
    obj.option = option
    obj.put()


    obj.option = option
    obj.put()

Или любая странная комбинация этого.Проблема в том, что проверка счетчика запускается снова до того, как произошла операция put, поэтому второй поток проходит через первую часть условия вместо второй.

Вы можете исправить это, поместив код в функцию, а затем используя

db.run_in_transaction()

для запуска функции.

Проблема в том, что вы, похоже, полагаетесь на количество объектов, возвращаемых запросом, для вашей логики принятия решений, которую необходимо включить в транзакцию.Если вы прочитаете доклады Google I/O или посмотрите группу, вы увидите, что это не рекомендуется.Это потому, что вы не можете транзакционизировать запрос.Вместо этого вам следует где-то сохранить счетчик как значение сущности и запросить его. снаружи функции транзакции, а затем передайте ключ этой сущности в вашу функцию транзакции.

Вот пример функции транзакции, которая проверяет свойство сущности.Он передал ключ в качестве параметра:

def checkAndLockPage(pageKey):
  page = db.get(pageKey)
  if page.locked:
    return False
  else:
    page.locked = True
    page.put()
    return True

Только один пользователь одновременно может заблокировать этот объект, и дублирующих блокировок никогда не будет.

Другие советы

Самый простой способ сделать это — использовать имена ключей для объектов голосования и использовать Model.get_or_insert.Сначала придумайте схему именования для имен ваших ключей — хорошей идеей будет назвать ее после опроса — а затем выполните get_or_insert, чтобы получить или создать соответствующую сущность:

vote = VoteRecord.get_or_insert(pollname, parent=session.user, user=session.user, poll=poll, option=option)
if vote.option != option:
  # Record already existed; we need to update it
  vote.option = option
  vote.put()
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top