28

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

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

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

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

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

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

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

4

4 回答 4

25

Take a look at Building Scalable, Complex Apps on App Engine (pdf), a fascinating talk given at Google I/O by Brett Slatkin. He addresses the problem of building a scalable messaging service like Twitter.

Here's his solution using a list property:

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)

This key only query finds the message indices with a receiver equal to the one you specified without deserializing and serializing the list of receivers. Then you use these indices to only grab the messages that you want.

Here's the wrong way to do it:

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

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

This is inefficient because queries have to unpackage all of the results returned by your query. So if you returned 100 messages with 1,000 users in each receivers list you'd have to deserialize 100,000 (100 x 1000) list property values. Way too expensive in datastore latency and cpu.

I was pretty confused by all of this at first, so I wrote up a short tutorial about using the list property. Enjoy :)

于 2009-10-27T17:41:08.740 回答
7

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

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

于 2009-10-27T19:58:05.813 回答
1

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

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

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

于 2014-04-23T18:49:55.110 回答
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])

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

于 2014-02-17T18:49:18.580 回答