简化上下文:
我有这个模式来存储用户、他们的消息以及他们有多少未读消息。
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()
以下是我考虑过的尝试:
为用户的消息提供相同的祖先,这样我就可以进行祖先查询。这将是最后的手段,因为祖先查询的性能限制,而且我不想替换每个实体。
将事务性标志与任务队列一起使用。不过,为了使我的put_multi是事务性的,我需要使用祖先查询。
以各种方式重新构造模式,但一旦我知道put完成,它总是能够运行代码。
更新所有将被读取的消息并更新计数器会让人觉得成本高昂。为什么不使用具有未读消息键的实体来快速读取(按键)并仅更新单个用户实体呢?该实体具有所有未读消息的索引和计数。
class User(ndb.Model):
unread_messages_count = ndb.IntegerProperty(default=0)
unread_messages_index = ndb.KeyProperty(repeated=True)
此未读邮件索引只有在邮件按到达时的相同顺序读取时才会起作用。请参阅下面的评论。