Question

Je l'ai touché un petit dilemme! J'ai un gestionnaire appelé vote; lorsqu'il est appelé, il définit le vote d'un utilisateur à tout ce qu'ils ont pris. Pour ne pas oublier les options qu'ils ont choisi précédemment, je stocke un choix de VoteRecord qui décrit en détail ce que leur vote actuel est réglé sur.

Bien sûr, la première fois qu'ils votent, je dois créer l'objet et le stocker. Mais votes successifs devraient simplement changer la valeur de la VoteRecord existante. Mais il vient le problème: dans certaines circonstances, deux VoteRecords peuvent être créés. Il est rare (seulement arrivé une fois dans tous les 500 votes, nous avons vu jusqu'à présent), mais encore mal quand il le fait.

Le problème se produit parce que deux gestionnaires distincts pour les deux font essentiellement ceci:

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

Ma question est: quelle est la plus efficace et la plus rapide pour traiter ces demandes, tout en assurant qu'aucune demande est perdue et aucune demande crée deux objets VoteRecord

Était-ce utile?

La solution

La question est cette partie:

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

Sans une transaction, votre code pourrait exécuter dans cet ordre dans deux instances d'interpréteur:

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

Ou une combinaison étrange de ceux-ci. Le problème est le test de comptage fonctionne à nouveau avant la vente a eu lieu, de sorte que le second fil passe par la première partie du conditionnel au lieu du second.

Vous pouvez résoudre ce problème en plaçant le code dans une fonction, puis en utilisant

db.run_in_transaction()

pour exécuter la fonction.

Le problème est que vous semblez compter sur le nombre d'objets retournés par une requête pour votre logique de décision qui doit être mis dans la transaction. Si vous lisez les pourparlers d'E / S de Google ou de regarder le groupe, vous verrez que cela ne soit pas recommandé. C'est parce que vous ne pouvez pas transactionalize une requête. Au lieu de cela, vous devez stocker le compte en tant que valeur d'entité quelque part, requête pour elle à l'extérieur de la fonction de transaction, et ensuite passer la clé de cette entité à votre fonction de transaction.

Voici un exemple d'une fonction de transaction qui vérifie une propriété d'entité. Il est passé la clé comme paramètre:

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

Un seul utilisateur à la fois peut verrouiller cette entité, et il n'y aura jamais de verrous en double.

Autres conseils

La meilleure façon de le faire est d'utiliser des noms clés pour vos objets de vote, et utiliser Model.get_or_insert. Tout d'abord, venir avec un schéma de nommage pour vos noms clés - nommant après le scrutin est une bonne idée - et puis faire un get_or_insert chercher ou créer l'entité concernée:

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()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top