如何使用App Engine维护一致的实体数量?

时间:2014-03-06 00:22:08

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

简化上下文:

我有这个模式来存储用户,他们的消息,以及他们有多少条未读消息的计数。

from google.appengine.ext.ndb import *

class User(Model):
  unread_messages = IntegerProperty()

class Message(Model):
  read = BooleanProperty()
  user_id = IntegerProperty()

我的目标是让messages在用户阅读完一些消息后包含正确的值。创建消息时,很容易使用事务将unread_messages属性增加一个并继续。但阅读信息似乎更加困难。

这是我尝试过的:

1。仅使用相对更改来更新实体。

问题是delta来自一个查询,它可能在写完成之前两次返回相同的结果。

#User reads messages

query = Message.query()
query = query.filter(Message.read = False)
query = query.filter(Message.user_id = user.key.id())
unread_messages = query.fetch(10)

for message in unread_messages:
  message.read = True

put_multi(unread_messages)

txn(user.key.id(), -len(unread_messages))

@run_in_transaction
def txn(id, delta):
  user = Key(User, id).get()
  user.unread_messages += delta
  user.put()

2。在放置后运行计数查询。

据我所知,在保证在查询中显示写入后,无法执行代码。所以对于这种方法,我只是在任务上设置了几秒的延迟。这种方法大部分时间都有效,但很容易看出写入时间超过延迟时间会导致值不正确。

query = Message.query()
query = query.filter(Message.read = False)
query = query.filter(Message.user_id = user.key.id())
unread_messages = query.fetch(10)

for message in unread_messages:
  message.read = True

put_multi(unread_messages)

taskqueue.add(url = '/tasks/update-unread-messages', params = {'user_id': user.key.id()}, countdown = 10)

相关任务:

query = Message.query()
query = query.filter(Message.read = False)
query = query.filter(Message.user_id = user.key.id())
count = query.count()
user.unread_messages = count
user.put()

@run_in_transaction
def txn(id, delta):
  user = Key(User, id).get()
  user.unread_messages += delta
  user.put()

这是我考虑过的:

  1. 为用户的消息提供相同的祖先,以便我可以进行祖先查询。由于祖先查询的性能限制以及我不想替换每个实体这一事实,这将是最后的手段。

  2. 将事务标志与taskqueue一起使用。虽然我的put_multi是事务性的,但我需要使用祖先查询。

  3. 以各种方式重构架构,但一旦我知道put已经完成,它总是能够运行代码。

1 个答案:

答案 0 :(得分:1)

更新将要读取的所有消息并更新计数器会感觉很昂贵。为什么不使用具有未读消息的密钥的实体快速读取(按键)并仅更新单个用户实体,该实体具有所有未读消息的索引和计数。

class User(ndb.Model):
    unread_messages_count = ndb.IntegerProperty(default=0) 
    unread_messages_index = ndb.KeyProperty(repeated=True)

此未读消息索引仅在消息按到达时的顺序读取时才有效。见下面的评论。