【问题标题】:AppEngine: Maintaining DataStore Consistency When Creating RecordsAppEngine:在创建记录时保持 DataStore 一致性
【发布时间】:2010-10-06 01:46:36
【问题描述】:

我遇到了一个小难题!我有一个叫 vote 的处理程序;当它被调用时,它将用户的投票设置为他们选择的任何内容。为了记住他们之前选择的选项,我存储了一个 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 对象的同时,处理这些请求的最有效和最快的方法是什么?

【问题讨论】:

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


【解决方案1】:

最简单的方法是为您的投票对象使用键名,并使用 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()

【讨论】:

    【解决方案2】:

    问题是这部分:

    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
    

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

    【讨论】:

    • 问题是,我不能在事务中放入查询。那我应该怎么形成呢?
    • 感谢您的更新。这是一个很好的逻辑 - 问题解决了! :)
    • 这是一个不必要的高开销解决方案 - 仍然允许竞争条件。
    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 2014-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多