AppEngine:创建记录时维护DataStore一致性

时间:2009-02-06 22:55:04

标签: python google-app-engine transactions google-cloud-datastore consistency

我遇到了一个小困境!我有一个名为投票的处理程序;当它被调用时,它将用户的投票设置为他们所选择的任何内容。为了记住他们之前选择的选项,我存储了一个VoteRecord选项,详细说明了他们当前的投票设置。

当然,他们第一次投票时,我必须创建对象并存储它。但连续投票只应改变现有VoteRecord的价值。但他提出了这个问题:在某些情况下可以创建两个VoteRecords。这种情况很罕见(到目前为止我们所看到的所有500张选票中只发生过一次),但它确实很糟糕。

问题出现是因为两个独立的处理程序基本上都是这样做的:

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

我的问题是:在确保没有请求丢失且没有请求创建两个VoteRecord对象的情况下,处理这些请求的最有效和最快捷的方法是什么?

2 个答案:

答案 0 :(得分:3)

最简单的方法是使用投票对象的键名,并使用Model.get_or_insert。首先,为您的密钥名称提出一个命名方案 - 在民意调查之后将其命名为一个好主意 - 然后执行get_or_insert以获取或创建相关实体:

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

答案 1 :(得分:1)

问题在于这一部分:

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

如果没有事务,您的代码可以在两个解释器实例中按此顺序运行:

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

或任何奇怪的组合。问题是计数测试在put发生之前再次运行,所以第二个线程遍历条件的第一部分而不是第二部分。

您可以通过将代码放入函数然后使用

来解决此问题
db.run_in_transaction()

运行该功能。

问题是您似乎依赖于查询返回的对象计数,这些对象需要放入事务中。如果您阅读Google I / O会谈或查看该论坛,您会发现不建议这样做。那是因为您无法对查询进行事务处理。相反,您应该将计数存储在某个地方的实体值,在事务函数的外部中查询,然后将该实体的密钥传递给您的事务函数。

以下是检查实体属性的事务函数示例。它将密钥作为参数传递:

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

一次只有一个用户可以锁定此实体,并且永远不会有任何重复的锁。