您将如何为Twitter等社交网站设计AppEngine数据存储?

时间:2009-10-27 11:05:00

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

我想知道设计社交应用程序的最佳方式是什么,其中成员使用Google AppEngine进行活动并关注其他成员的活动。

更具体地说,假设我们有这些实体:

  • 有朋友的用户
  • 活动,代表用户所做的操作(假设每个用户都有一个字符串消息和一个ReferenceProperty给它的所有者用户,或者它可以通过appengine的密钥使用父关联)

困难的部分是关注朋友的活动,这意味着汇总所有朋友的最新活动。 通常情况下,这将是活动表和你的朋友列表之间的连接,但这不是一个可行的appengine设计,因为没有连接模拟它将需要激活N个查询(其中N是朋友的数量),然后在内存中合并 - 非常昂贵,可能会超过要求的截止日期......)

我目前正在考虑使用收件箱队列实现此功能,其中新活动的创建将触发后台进程,该进程将新活动的密钥放在每个后续用户的“收件箱”中:

  • 获取“跟随X的所有用户”是可能的appengine查询
  • 对于基本上存储(用户,活动密钥)元组的新“收件箱”实体而言,不是非常昂贵的批量输入。

我很高兴听到有关此设计或其他建议等的想法。

4 个答案:

答案 0 :(得分:25)

看看Building Scalable, Complex Apps on App Enginepdf),这是Brett Slatkin在Google I / O上发表的精彩演讲。他解决了构建像Twitter这样的可扩展消息传递服务的问题。

以下是使用list属性的解决方案

class Message(db.Model):
    sender = db.StringProperty()
    body = db.TextProperty()

class MessageIndex(db.Model):
    #parent = a message
    receivers = db.StringListProperty()

indexes = MessageIndex.all(keys_only = True).filter('receivers = ', user_id)
keys = [k.parent() for k in indexes)
messages = db.get(keys)

此仅密钥查询查找接收方等于您指定的接收方的消息索引,而不反序列化和序列化接收方列表。然后使用这些索引仅获取所需的消息。

这是错误的方式

class Message(db.Model):
    sender = db.StringProperty()
    receivers = db.StringListProperty()
    body = db.TextProperty()

messages = Message.all().filter('receivers =', user_id)

这是低效的,因为查询必须解包查询返回的所有结果。因此,如果您在每个接收者列表中返回100条消息,其中包含1,000个用户,则必须反序列化100,000(100 x 1000)个列表属性值。数据存储延迟和CPU的方式太贵了。

起初我对所有这些感到非常困惑,所以我写了short tutorial about using the list property。享受:)

答案 1 :(得分:7)

我不知道它是否是社交应用程序的最佳设计,但当公司被Google收购时,jaikuported to App Engine的原始创建者,{}所以这应该是合理的。

请参阅design_funument.txt中的 Actors and Tigers and Bears,Oh My!部分。实体在common/models.py中定义,查询在common/api.py

答案 2 :(得分:1)

罗伯特,关于你提出的解决方案:

messages = Message.query(Message.receivers == user_id).fetch(projection=[Message.body])

我认为ndb.TextProperty" body"不能与投影一起使用,因为没有编入索引。预测仅支持索引属性。 有效的解决方案是维护2个表:Message和MessageIndex。

答案 3 :(得分:0)

我认为现在可以通过NDB中的新投影查询来解决这个问题。

class Message(ndb.Model):
    sender = ndb.StringProperty()
    receivers = ndb.StringProperty(repeated=True)
    body = ndb.TextProperty()

messages = Message.query(Message.receivers == user_id).fetch(projection=[Message.body])

现在您不必处理反序列化列表属性的昂贵成本。