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