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

Posted

技术标签:

【中文标题】您将如何为 Twitter 等社交网站设计 AppEngine 数据存储?【英文标题】:How would you design an AppEngine datastore for a social site like Twitter? 【发布时间】:2010-12-10 10:44:12 【问题描述】:

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

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

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

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

我目前正在考虑使用收件箱队列来实现这一点,其中创建新 Activity 将触发一个后台进程,该进程会将新 Activity 的密钥放入每个以下用户的“收件箱”中:

获取“所有关注 X 的用户”是一个可能的 appengine 查询 对于基本上存储(用户、活动键)元组的新“收件箱”实体的批量输入并不是非常昂贵。

我很高兴听到对此设计的想法或替代建议等。

【问题讨论】:

我正在研究同样的问题,并从 AppEngine 中发现了这个出色的(!)演示文稿,他们在 Google I/O 上提供了该演示文稿:scribd.com/doc/16952419/… 我希望你也会发现它也很有用。 【参考方案1】:

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

这是他使用列表属性的解决方案

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。享受:)

【讨论】:

正是我最初的设计。但我从那次谈话和 AppEngine 文档中了解到,在 IN 查询中,列表是毫无用处的。您提到的查询将在 google 系统中触发多个查询,每个查询都通过列表属性中的一个值进行过滤,然后合并结果。 Google 将此类查询限制为 30 个同时查询,这意味着它只能用于包含相对较少数量的键 ( 顺便说一句,我问过你关于你发布的另一个 *** 问题中的列表的相同问题 :) 我认为这不对。 Brett 说,当他谈到列表属性性能时,每个实体的索引属性限制为 5000 个(参见视频中的 14:15)。我认为您应该能够在接收器 StringListProperty 中拥有数千个用户,同时仍然能够执行有效的查询。我不确定“包含 != 或 IN 运算符的单个查询限制为 30 个子查询”这一行是什么意思,但我肯定它不会影响您在此处执行的操作。 GQL IN 运算符(根据文档限制为 30 个项目)和此处使用的过滤器(根据演示文稿将适用于大型列表)有什么区别? 'IN' 是“查找包含这些值之一的任何实体”,它被拆分为 n 个“查找包含此确切值的任何实体”查询。然而,列表上的相等过滤器是后者 - “查找具有此精确值的任何实体(可能在列表中)”,因此不会像 IN 查询那样低效。【参考方案2】:

我不知道它是否是社交应用程序的最佳设计,但jaiku 在公司被谷歌收购时由它的原始创建者ported to App Engine,所以它应该是合理。

请参阅design_funument.txt 中的演员、老虎和熊,哦,我的!部分。实体在common/models.py 中定义,查询在common/api.py 中。

【讨论】:

【参考方案3】:

我认为现在可以通过 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])

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

【讨论】:

【参考方案4】:

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

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

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

【讨论】:

以上是关于您将如何为 Twitter 等社交网站设计 AppEngine 数据存储?的主要内容,如果未能解决你的问题,请参考以下文章

您将如何为 MegaProtoUser 实现自我关系?

您将如何为地图上各点之间的路线提供路线?缺失的部分是啥?

如何为用户创建无痛的安全春季社交注册/登录

[iPhone]你将如何为下面给出的结构设计核心数据对象模型

Facebook、Twitter 等。登录网站未获取电子邮件

如何为用户实现关注者,如在 twitter 中