使用 Meteor 建模和发布基于关注者的提要
Modeling and publishing a follower-based feed with Meteor
我正在开发一个简单的应用程序,用户可以在其中关注 其他用户。用户可以星posts。用户的供稿由他们关注的用户加星标的 post 组成。其实很简单。然而,这一切在 Mongo 和 Meteor 中变得复杂...
我能想到的基本上有两种建模方式:
一个用户有一个属性,following
,是用户关注的userId数组。此外,post 有一个 属性、starrers
,这是一个为这个 post 加注星标的用户 ID 数组。这种方法的好处是发布比较简单:
Meteor.publish 'feed', (limit) ->
Posts.find({starrers: {$in: Meteor.users.findOne(@userId).following}}, {sort: {date: -1}, limit:limit})
我们不会被动地倾听用户关注的人,但目前还算不错。这种方法的主要问题是 (1) 如果 1000000 人 star a post,单个文档将变得庞大且效率低下。另一个问题是 (2) 跟踪信息会很痛苦,例如用户何时开始关注另一个用户或用户何时为 post.
加注星标
另一种方法是再创建两个集合,Stars
和 Follows
。如果用户为 post 加注星标,那么我们将创建一个具有属性 userId
和 postId
的文档。如果一个用户关注另一个用户,那么我们将创建一个具有属性 userId
和 followId
的文档。这为我们提供了 Users
和 Posts
的较小文档大小的优势,但在查询时却很复杂,特别是因为 Mongo 不处理连接!
现在,我做了一些研究,人们似乎同意第二种选择是正确的选择。现在我遇到的问题是高效查询和发布。基于 Discover Meteor chapter about Advanced Publications,我创建了一个出版物,用于发布由用户的关注者加注星标的 post——已排序且有限。
# a helper to handle stopping observeChanges
observer = (sub, func) ->
handle = null
sub.onStop ->
handle?.stop?()
() ->
handle?.stop?()
handle = func()
Meteor.publish 'feed', (limit) ->
sub = this
userId = @userId
followIds = null
eventIds = null
publishFollows = observer sub, () ->
followIds = {}
Follows.find({userId:userId}).observeChanges
added: (id, doc) ->
followIds[id] = doc.followId
sub.added('follows', id, doc)
publishStars()
removed: (id) ->
delete followIds[id]
sub.removed('follows', id)
publishStars()
publishStars = observer sub, () ->
eventIds = {}
Stars.find({userId: {$in: _.keys(followIds)}).observeChanges
added: (id, doc) ->
eventIds[id] = null
sub.added('stars', id, doc)
publishEvents()
removed: (id) ->
delete eventIds[id]
sub.removed('stars', id)
publishEvents()
publishEvents = observer sub, () ->
Events.find({_id: {$in: _.keys(eventIds)}}, {sort: {name:1, date:-1}, limit:limit}).observeChanges
added: (id, doc) ->
sub.added('events', id, doc)
changed: (id, fields) ->
sub.changed('events', id, fields)
removed: (id) ->
sub.removed('events', id)
虽然这可行,但规模似乎非常有限。特别是,我们必须编制一份由每个关注者加注星标 post 的列表。此列表的大小将增长得非常快。然后我们对所有 post 进行一个巨大的 $in
查询。
另一个烦恼是在我们订阅后在客户端查询提要:
Meteor.subscribe("feed", 20)
posts = null
Tracker.autorun ->
followers = _.pluck(Follows.find({userId: Meteor.userId()}).fetch(), "followId")
starredPostIds = _.pluck(Stars.find({userId: {$in: followers}}).fetch(), "postId")
posts = Posts.find({_id: {$in: starredPostIds}}, {sort: {date: -1}, limit: 20}).fetch()
这就像我们把所有这些工作都做了两次。首先,我们在服务器上完成所有工作以发布提要。然后我们需要在客户端再次通过完全相同的逻辑来获取那些 posts...
我这里的问题是关于一切的设计问题。 如何根据关注者注视 post 来高效设计此 Feed? 我应该使用什么收集/收集模式?我应该如何创建适当的出版物?我如何在客户端查询提要?
所以事实证明 Mongo 和 "non-relational" 数据库根本不是为关系数据设计的。因此,这里没有 Mongo 的解决方案。我最终使用了 Neo4j,但 SQL 也可以正常工作。
meteor add reywood:publish-composite
Meteor.publishComposite('tweets', function(username) {
return {
find: function() {
// Find the current user's following users
return Relationships.find({ follower: username });
},
children: [{
find: function(relationship) {
// Find tweets from followed users
return Tweets.find({user: relationship.following});
}
}]
}
});
Meteor.publish('ownTweets', function(username) {
return Tweets.find({user: username});
});
我正在开发一个简单的应用程序,用户可以在其中关注 其他用户。用户可以星posts。用户的供稿由他们关注的用户加星标的 post 组成。其实很简单。然而,这一切在 Mongo 和 Meteor 中变得复杂...
我能想到的基本上有两种建模方式:
一个用户有一个属性,
following
,是用户关注的userId数组。此外,post 有一个 属性、starrers
,这是一个为这个 post 加注星标的用户 ID 数组。这种方法的好处是发布比较简单:Meteor.publish 'feed', (limit) -> Posts.find({starrers: {$in: Meteor.users.findOne(@userId).following}}, {sort: {date: -1}, limit:limit})
我们不会被动地倾听用户关注的人,但目前还算不错。这种方法的主要问题是 (1) 如果 1000000 人 star a post,单个文档将变得庞大且效率低下。另一个问题是 (2) 跟踪信息会很痛苦,例如用户何时开始关注另一个用户或用户何时为 post.
加注星标
另一种方法是再创建两个集合,
Stars
和Follows
。如果用户为 post 加注星标,那么我们将创建一个具有属性userId
和postId
的文档。如果一个用户关注另一个用户,那么我们将创建一个具有属性userId
和followId
的文档。这为我们提供了Users
和Posts
的较小文档大小的优势,但在查询时却很复杂,特别是因为 Mongo 不处理连接!
现在,我做了一些研究,人们似乎同意第二种选择是正确的选择。现在我遇到的问题是高效查询和发布。基于 Discover Meteor chapter about Advanced Publications,我创建了一个出版物,用于发布由用户的关注者加注星标的 post——已排序且有限。
# a helper to handle stopping observeChanges
observer = (sub, func) ->
handle = null
sub.onStop ->
handle?.stop?()
() ->
handle?.stop?()
handle = func()
Meteor.publish 'feed', (limit) ->
sub = this
userId = @userId
followIds = null
eventIds = null
publishFollows = observer sub, () ->
followIds = {}
Follows.find({userId:userId}).observeChanges
added: (id, doc) ->
followIds[id] = doc.followId
sub.added('follows', id, doc)
publishStars()
removed: (id) ->
delete followIds[id]
sub.removed('follows', id)
publishStars()
publishStars = observer sub, () ->
eventIds = {}
Stars.find({userId: {$in: _.keys(followIds)}).observeChanges
added: (id, doc) ->
eventIds[id] = null
sub.added('stars', id, doc)
publishEvents()
removed: (id) ->
delete eventIds[id]
sub.removed('stars', id)
publishEvents()
publishEvents = observer sub, () ->
Events.find({_id: {$in: _.keys(eventIds)}}, {sort: {name:1, date:-1}, limit:limit}).observeChanges
added: (id, doc) ->
sub.added('events', id, doc)
changed: (id, fields) ->
sub.changed('events', id, fields)
removed: (id) ->
sub.removed('events', id)
虽然这可行,但规模似乎非常有限。特别是,我们必须编制一份由每个关注者加注星标 post 的列表。此列表的大小将增长得非常快。然后我们对所有 post 进行一个巨大的 $in
查询。
另一个烦恼是在我们订阅后在客户端查询提要:
Meteor.subscribe("feed", 20)
posts = null
Tracker.autorun ->
followers = _.pluck(Follows.find({userId: Meteor.userId()}).fetch(), "followId")
starredPostIds = _.pluck(Stars.find({userId: {$in: followers}}).fetch(), "postId")
posts = Posts.find({_id: {$in: starredPostIds}}, {sort: {date: -1}, limit: 20}).fetch()
这就像我们把所有这些工作都做了两次。首先,我们在服务器上完成所有工作以发布提要。然后我们需要在客户端再次通过完全相同的逻辑来获取那些 posts...
我这里的问题是关于一切的设计问题。 如何根据关注者注视 post 来高效设计此 Feed? 我应该使用什么收集/收集模式?我应该如何创建适当的出版物?我如何在客户端查询提要?
所以事实证明 Mongo 和 "non-relational" 数据库根本不是为关系数据设计的。因此,这里没有 Mongo 的解决方案。我最终使用了 Neo4j,但 SQL 也可以正常工作。
meteor add reywood:publish-composite
Meteor.publishComposite('tweets', function(username) {
return {
find: function() {
// Find the current user's following users
return Relationships.find({ follower: username });
},
children: [{
find: function(relationship) {
// Find tweets from followed users
return Tweets.find({user: relationship.following});
}
}]
}
});
Meteor.publish('ownTweets', function(username) {
return Tweets.find({user: username});
});