【问题标题】:MongoDB Schema Design - Voting on PostsMongoDB Schema 设计 - 对帖子进行投票
【发布时间】:2012-07-03 13:50:52
【问题描述】:

假设我有一个网站,上面有很多文章,人们可以对他们喜欢的文章进行投票。

我希望能够查询到在某个时间(最后一小时、最后一天、上周)内按票数排序的得票最多的文章。

与 MongoDB 一样,有几种不同的方法可以实现这一点,但我不确定哪一种是正确的。

  • 一个帖子文档,其中包含一个投票数组 - 投票本身就是包含用户 ID、用户名和投票日期的文档:
    {
    "_id": "ObjectId(xxxx)",
    "title": "Post Title",
    "postdate": "21/02/2012+1345",
    "summary": "Summary of Article",

    "Votes": [
        {
            "userid":ObjectId(xxxx),
            "username": "Joe Smith",
            "votedate": "03/03/2012+1436"
        },
            ]
     }
  • 单独的投票集合,包含个人投票的详细信息和对投票帖子的引用:
{
    "_id": "ObjectId(xxxx)",
    "postId": ObjectId(xxxx),
    "userId": ObjectId(xxxx),
    "votedate": "03/03/2012+1436"
}

第一个是 more Documentey,但我不知道如何查询投票数组以获取过去 24 小时内投票最多的文档。

我倾向于第二个,因为我认为查询按投票分组的投票计数会更容易,但我不确定它的表现如何。这就是你在关系数据库中的做法,但它似乎不是很记录 - 但我不确定这是否有问题,是吗?

或者我是否将两者结合使用?我还会在每个页面加载时实时执行这种类型的聚合查询。还是我只需要每分钟运行一次查询并将结果存储在查询结果集合中?

您将如何实现此架构?

【问题讨论】:

标签: mongodb


【解决方案1】:

跟踪总体投票计数的常用方法是保留 post 文档中的投票数量,并在将新值推送到投票数组时自动更新它。

由于是单次更新,因此可以保证计数与数组中的元素数匹配。

如果聚合的数量是固定的,并且网站非常繁忙,您可以扩展此范例并增加额外的计数器,例如月、日和小时的计数器,但这可能很快就会失控。因此,您可以使用新的Aggregation Framework(在 2.1.2 开发版中可用,将在 2.2 版中投入生产。它比 Map/Reduce 更易于使用,它可以让您非常简单地进行您想要的计算,尤其是如果您注意将投票日期存储为 ISODate() 类型。

本月最高投票者的聚合查询的典型管道可能如下所示:

today = new Date();
thisMonth = new Date(today.getFullYear(),today.getMonth());
thisMonthEnd = new Date(today.getFullYear(),today.getMonth()+1);

db.posts.aggregate( [
    {$match: { "Votes.votedate": {$gte:thisMonth, $lt:thisMonthEnd} } },
    {$unwind: "$Votes" },
    {$match: { "Votes.votedate": {$gte:thisMonth, $lt:thisMonthEnd} } },
    {$group: { _id: "$title", votes: {$sum:1} } },
    {$sort: {"votes": -1} },
    {$limit: 10}
] );

这会将管道的输入限制为通过将投票日期与您计算的月份相匹配来获得投票的帖子,“展开”数组以获得每个投票的一个文档,然后对所有投票进行等效的“分组”对于每个标题(我假设标题是唯一的)。然后它按投票数降序排序并将输出限制为前十个。

您还可以按日期(例如)汇总该月的投票,以查看投票最活跃的日期:

db.posts.aggregate( [
    {$match: { "Votes.votedate": {$gte:thisMonth, $lt:thisMonthEnd} } },
    {$unwind: "$Votes" },
    {$match: { "Votes.votedate": {$gte:thisMonth, $lt:thisMonthEnd} } },
    {$project: { "day" : { "$dayOfMonth" : "$Votes.votedate" }  } },
    {$group: { _id: "$day", votes: {$sum:1} } },
    {$sort: {"votes": -1} },
    {$limit: 10}
] );

【讨论】:

  • 如果您最终将投票存储在他们自己的集合中而不是嵌入帖子中,那么您就不需要“$unwind”步骤,其余的聚合将基本保持不变。跨度>
  • 请注意,我两次匹配所需的月份并非偶然。第一个 $match 删除了在所需月份没有任何投票的帖子,但第二个 $match(在 $unwind 之后)确保我们只保留该月发生的投票,然后再计算它们。第一个 $match 用于减少我们输入管道的总文档数量,除了性能之外,它不是绝对必要的。
  • 我正在尝试将解决方案用于参考文档,但它不起作用。在我的测试中,我有 6 个帖子,其中只有一个拥有一票。所有其他人在posts_votes 集合中都没有记录。如果我运行{$group: { _id: "$votes.post_id", votes: {$sum: 1 }}},我会得到一条_id 为null 的记录。如果我将$votes.post_id 更改为$title,它将返回所有 6 个帖子,每个帖子都有一个投票(应该只有一个投票的帖子,所有其他帖子都为零)。还尝试添加$project 数组,该数组使用$votes.post_id 创建一个变量
  • 同意。我发布了一个与此类似的问题,但没有太大的吸引力。 stackoverflow.com/q/38936693/239375
  • 是的,所以在现实生活中,您不希望数组无限增长,因此您可能实现的是混合。由于即时准确的计数对于投票数较少的帖子很重要,而对于大量投票的帖子则不那么重要,因此您可能只保留数组中最近的 X 票,并异步验证您的计数,或者其他一些变体。这个例子更多是为了展示原子操作机制,可能不是如何在“现实世界”中做事的最佳例子,除非选民的数量实际上受到其他现实世界因素的限制。
【解决方案2】:

您选择的架构很大程度上取决于您的用例。如果您期望有很多投票/cmets 并希望独立于它们所属的帖子来处理它们,您可以将它们保存在一个单独的集合中,其中 postID 为“外键”..但是,如果您想在加载特定帖子时加载所有选票,并且如果没有包含它们的帖子,选票本身就没有任何意义,那么请进行嵌入(在您的情况下, 第一种) 方法。

【讨论】:

  • 你可以试试 mapreduce 用 more documentey 的方法来查询 votes 数组,得到最近 24 小时内得票最多的文档...由于 mapreduce 恰好是一个繁重的操作,最好只偶尔运行它并使用缓存的结果。
猜你喜欢
  • 1970-01-01
  • 2014-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多