如何使用App Engine保持实体计数的一致性



简化上下文:

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

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.在put之后运行计数查询

据我所知,在保证写入在查询中可见之后,无法执行代码。所以对于这个方法,我只在任务上设置了几秒钟的延迟。这在大多数情况下都有效,但很容易看出,比我的延迟时间更长的写入会导致错误的值。

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. 将事务性标志与任务队列一起使用。不过,为了使我的put_multi是事务性的,我需要使用祖先查询。

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

更新所有将被读取的消息并更新计数器会让人觉得成本高昂。为什么不使用具有未读消息键的实体来快速读取(按键)并仅更新单个用户实体呢?该实体具有所有未读消息的索引和计数。

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

此未读邮件索引只有在邮件按到达时的相同顺序读取时才会起作用。请参阅下面的评论。

最新更新