GAE: согласованность хранилища данных без конфликтов (т. Е. Без групп объектов)

Я создаю сайт для голосования в GAE, который будет работать так:

  • Голосование объявляется открытым (но остается открытым только минуту или две).
  • Люди отдают свои голоса.
  • Голосование закрывается. Больше нельзя отдавать голоса.
  • Отображаются результаты.

Фаза голосования длится всего пару минут, поэтому за короткий период будет подано много голосов.

Я хочу избежать конфликтов в хранилище данных, поэтому я не могу хранить голоса в группе сущностей (вероятно, будет превышен предел ~ 1 запись в секунду).

Однако я должен убедиться, что подсчитываю ВСЕ голоса после завершения голосования.

Мой вопрос: как я могу обеспечить согласованность голосов в хранилище данных (без группы сущностей) после закрытия голосования? Другими словами, в какой момент я могу быть уверен, что каждый голос записан в хранилище данных (и доступен для чтения)?

Только когда я узнаю, что каждый голос доступен для чтения, я могу безопасно рассчитать результаты.

PS: Должен заметить, что это не «простая» схема голосования; избиратели выбирают свой 1-й, 2-й и 3-й варианты, и победитель определяется в ходе довольно сложного итеративного процесса, то есть недостаточно подсчитать, сколько голосов получает каждый кандидат.

Заранее спасибо!


person Justin    schedule 26.02.2014    source источник


Ответы (2)


My 2c.

Предположим, вы пользуетесь пользовательским сервисом. Я буду использовать обработчик голосования и choice в качестве ввода от пользователя. Я не буду использовать предков, поэтому все голоса будут корневыми объектами. Мы можем использовать user_id для пользователя, который уникален как ключ для голосования.

Теперь в зависимости от производительности у нас есть 3 варианта. Почему? Посмотрим.

Первые 2.

Подход 1 - Слепая запись (без транзакций)

class VoteHandler(webapp2.RequestHandler):

  def get(self, choice):

    user = users.get_current_user()

    # Blind write it! or just check before if exists 
    Vote(id=user.user_id, selection=choice).put()

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

Подход 2 - Get_or_inserts (небольшие транзакции)

class VoteHandler(webapp2.RequestHandler):

  def get(self, choice):

    user = users.get_current_user()

    # Construct a vote with the user_id as a key using get_or_insert
    vote = Vote.get_or_insert(user.user_id())

    # Check if he has voted (general check with default entity prop to None)
    if vote.selection is not None:
      # vote is cast return or do other logic
      return

    vote.selection = choice
    vote.put()

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

Что касается get_or_insert, он использует транзакцию и делает следующее:

def txn(key_name, **kwds):
  entity = Story.get_by_key_name(key_name, parent=kwds.get('parent'))
  if entity is None:
    entity = Story(key_name=key_name, **kwds)
    entity.put()
  return entity

def get_or_insert(key_name, **kwargs):
  return db.run_in_transaction(txn, key_name, **kwargs)

get_or_insert('some key', title="The Three Little Pigs")

Во втором подходе я использовал get_or_insert в начале, а позже я просто проверял свойство, если оно «установлено». В зависимости от этого условия экономим или нет. Остерегаться!!! Параллельный запрос мог изменить свойство vote_selection и уже установил его.

Некоторые мысли по этому поводу:

Используя user_id, я знаю, что только одновременные запросы одного и того же пользователя будут вызывать такое поведение.

Обычно, если пользователь инициирует 2 одновременных запроса vote_selection, то происходит изменение, в котором запросы будут:

  • Оба проверяют, существует ли голосование объекта.
  • Один вставит сущность.
  • Другой получит сущность.

Но, возможно, они оба увидят свойство selection как None и оба попытаются написать. Последний будет действительным. И у вас будет 2 или более записей (если будет куда больше запросов).

Подход 3 - транзакционный

class VoteHandler(webapp2.RequestHandler):

  def get(self, choice):

    user = users.get_current_user()

    self.vote(user.user_id, choice)

  @ndb.transactional()
  def vote(key, choice):
    vote = ndb.Key(Vote, key).get()
      if vote:
         # user has voted 
         return
      # return the key 
      return Vote(id=key, selection=choise).put()

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

Выбирайте с умом, и хотелось бы больше ответов / мнений / подходов.

person Jimmy Kane    schedule 26.02.2014
comment
Спасибо, Джимми! Итак, вы говорите, что если я знаю ключ объекта голосования (то есть user_id в данном случае), то при чтении хранилища данных гарантированно будет получено последнее (непротиворечивое) значение? [Если это не так, как я могу быть уверен, что прочитал каждый голос при подсчете результатов, т.е. как я могу быть уверен, что в моем экземпляре хранилища данных нет голосов, ожидающих появления?] - person Justin; 26.02.2014
comment
@Justin посмотрите здесь developers.google.com/appengine/docs/python/datastore/ здесь stackoverflow.com/questions / 19884076 / здесь - person Jimmy Kane; 26.02.2014
comment
@Justin также здесь developers.google.com/appengine/docs/python/datastore и здесь для аналогичной проблемы stackoverflow.com/questions/8744761/ - person Jimmy Kane; 26.02.2014
comment
@Justin За ответ: Да, получение сущности по ее ключу гарантирует сильную согласованность. - person Jimmy Kane; 26.02.2014
comment
Джимми, отличные ответы, очень ценю детали. Я думаю, что для меня подходит второй вариант, поскольку ни один избиратель не должен голосовать более одного раза одновременно, и если они проголосуют, то это будет приемлемо. Но действительно полезно иметь три сценария для демонстрации различных подходов. - person Justin; 18.03.2014
comment
@Justin Также взгляните на ответ Умайра Кхана для обмена, если вам нужно увеличить счетчики и т. Д. Рад, что это помогает. - person Jimmy Kane; 18.03.2014

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

person Umair Khan    schedule 01.03.2014
comment
Я вижу, что сегментированный список ключей голосования может работать (счетчиков недостаточно для моего случая использования). Пища для размышлений - спасибо! - person Justin; 18.03.2014