AppEngine: поддержание согласованности хранилища данных при создании записей - PullRequest
3 голосов
/ 07 февраля 2009

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

Конечно, когда они голосуют в первый раз, я должен создать объект и сохранить его. Но последовательные голоса должны просто изменить значение существующего VoteRecord. Но у него возникает проблема: при некоторых обстоятельствах могут быть созданы две VoteRecords. Это редко (случается только один раз из всех 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?

Ответы [ 2 ]

3 голосов
/ 31 мая 2009

Самый простой способ сделать это - использовать имена ключей для объектов голосования и использовать 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()
1 голос
/ 07 февраля 2009

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

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()

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

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

db.run_in_transaction()

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

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

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

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

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

...