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

?
Foi útil?

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()
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top