AppEngine: Manter DataStore consistência ao criar registros
-
22-08-2019 - |
Pergunta
Eu acertei um pequeno dilema! Eu tenho um manipulador chamado voto; quando é invocada ele define o voto de um usuário para o que quer que você escolheu. Para lembrar o que opções que anteriormente escolhido, eu armazenar uma opções VoteRecord que detalhes o que o seu voto atual é definida como.
Claro, a primeira vez que votar, eu tenho que criar o objeto e armazená-lo. Mas votações sucessivas deve apenas alterar o valor do VoteRecord existente. Mas ele vem o problema: em algumas circunstâncias dois VoteRecords pode ser criado. É raro (só aconteceu uma vez em todos os 500 votos que vimos até agora), mas ainda ruim quando se faz.
O problema acontece porque dois manipuladores separados ambos fazem basicamente o seguinte:
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')
A minha pergunta é: qual é a maneira mais eficaz e mais rápida de lidar com essas solicitações ao mesmo tempo garantir que nenhum pedido está perdido e nenhum pedido cria dois VoteRecord objetos
?Solução
A questão é esta parte:
if vote.count(1) == 0:
obj = VoteRecord()
obj.user = user
obj.option = option
obj.put()
Sem uma transação, seu código poderia executar nesta ordem, em duas instâncias intérprete:
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 qualquer combinação estranha mesmo. O problema é o teste de contagem é executado novamente antes da put ocorreu, de modo que o segundo segmento atravessa a primeira parte da condicional em vez do segundo.
Você pode corrigir isso colocando o código em uma função e, em seguida, usando
db.run_in_transaction()
para executar a função.
O problema é que você parece estar contando com a contagem de objetos retornados por uma consulta para o seu lógica de decisão que precisa ser colocado na transação. Se você ler as conversas Google I / O ou olhar para o grupo que você vai ver que isso não é recomendado. Isso é porque você não pode transactionalize uma consulta. Em vez disso, você deve armazenar a contagem como um algum lugar valor entidade, consulta para ele fora da função de transação e, em seguida, passar a chave para essa entidade para a sua função transação.
Aqui está um exemplo de uma função de transação que verifica uma propriedade entidade. É aprovada a chave como um parâmetro:
def checkAndLockPage(pageKey):
page = db.get(pageKey)
if page.locked:
return False
else:
page.locked = True
page.put()
return True
Somente um usuário de cada vez pode bloquear esta entidade, e nunca haverá qualquer bloqueio duplicados.
Outras dicas
A maneira mais fácil de fazer isso é usar nomes-chave para seus objetos voto, e usar Model.get_or_insert. Em primeiro lugar, vem com um esquema de nomenclatura para seus nomes-chave - nomeando-o após a pesquisa é uma boa idéia - e depois fazer um get_or_insert para buscar ou criar a entidade relevante:
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()