AppEngine: Mantenere DataStore Coerenza Quando Creazione di record
-
22-08-2019 - |
Domanda
Ho colpito un piccolo dilemma! Ho un gestore chiamato voto; quando viene richiamata imposta voto di un utente a quello che hanno raccolto. Per ricordare quali opzioni hanno precedentemente raccolti, ho memorizzare una scelta VoteRecord che dettaglia che cosa il loro voto corrente è impostato.
Naturalmente, la prima volta votano, devo creare l'oggetto e memorizzarlo. Ma successive voti dovrebbero solo cambiare il valore della VoteRecord esistente. Ma arriva il problema: in alcune circostanze possono essere creati due VoteRecords. E 'raro (è successo solo una volta in tutti i 500 voti che abbiamo visto finora), ma ancora male quando lo fa.
Il problema si verifica perché due gestori separati entrambi fanno essenzialmente questo:
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')
La mia domanda è: qual è il più efficace e veloce per gestire queste richieste, garantendo nel contempo che nessuna richiesta è perso e nessuna richiesta crea due oggetti VoteRecord
Soluzione
La questione è questa parte:
if vote.count(1) == 0:
obj = VoteRecord()
obj.user = user
obj.option = option
obj.put()
Senza una transazione, il codice potrebbe funzionare in questo ordine in due casi interprete:
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()
o una combinazione strana della stessa. Il problema è il test conteggio viene eseguito nuovamente prima put è verificato, in modo che il secondo filo passa attraverso la prima parte del condizionale posto del secondo.
È possibile risolvere questo problema inserendo il codice in una funzione e quindi utilizzando
db.run_in_transaction()
per eseguire la funzione.
Il problema è che ti sembra di essere basandosi sul conteggio degli oggetti restituiti da una query per la logica decisione che deve essere messo nella transazione. Se leggete i colloqui di Google I / O o guardare il gruppo vedrete che questo non è raccomandato. Ecco perché non si può transactionalize una query. Invece, è necessario memorizzare il conteggio come valore dell'entità da qualche parte, query per fuori della funzione di transazione e quindi passare la chiave per tale entità alla funzione transazione.
Ecco un esempio di una funzione transazione che controlla una proprietà entità. E 'passata la chiave come parametro:
def checkAndLockPage(pageKey):
page = db.get(pageKey)
if page.locked:
return False
else:
page.locked = True
page.put()
return True
Solo un utente alla volta può bloccare questa entità, e non ci sarà mai alcun blocco duplicato.
Altri suggerimenti
Il modo più semplice per farlo è quello di utilizzare i nomi delle chiavi per gli oggetti voto, e utilizzare Model.get_or_insert. In primo luogo, venire con uno schema di denominazione per i vostri nomi chiave - denominazione dopo il sondaggio è una buona idea - e poi fare un get_or_insert per andare a prendere o creare l'entità rilevante:
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()