node.js - MongoDB查询注释以及用户信息

标签 node.js mongodb

我正在用nodejs和mongod(不是mongoose)创建一个应用程序。我有一个问题,让我头痛了几天,任何人请提出一个办法!!
我有一个这样的MongoDB设计

post{
  _id:ObjectId(...),
  picture: 'some_url',
  comments:[
    {_id:ObjectId(...),
     user_id:Object('123456'),
     body:"some content"
    },
    {_id:ObjectId(...),
     user_id:Object('...'),
     body:"other content"
    } 
  ]
}

user{
 _id:ObjectId('123456'),
 name: 'some name', --> changable at any times
 username: 'some_name', --> changable at any times
 picture: 'url_link' --> changable at any times
}

我想查询帖子以及所有用户信息,这样查询将如下所示:
[{
  _id:ObjectId(...),
  picture: 'some_url',
  comments:[
    {_id:ObjectId(...),
     user_id:Object('123456'),
     user_data:{
         _id:ObjectId('123456'),
         name: 'some name',
         username: 'some_name',
         picture: 'url_link'
     }
     body:"some content"
    },
    {_id:ObjectId(...),
     user_id:Object('...'),
     body:"other content"
    } 
  ]
}]

我试图使用loop手动获取用户数据并添加到注释中,但事实证明这很困难,而且我的编码技能无法实现:(
请大家提出任何建议,我将不胜感激。
p/s我正在尝试另一种方法,我将所有的用户数据嵌入到评论中,每当用户更新他们的用户名、姓名或图片时。他们也会在所有评论中更新它

最佳答案

问题
由于written before,在过度嵌入时存在几个问题:
问题1:bson大小限制
截至本文撰写之时,BSON documents are limited to 16MB。如果达到了这个限制,mongodb将抛出一个异常,并且您不能添加更多的注释,在最坏的情况下,如果更改会增加文档的大小,甚至不能更改(用户名)或图片。
问题2:查询限制和性能
在某些情况下,查询或排序comments数组是不容易的。有些事情需要一个相当昂贵的聚合,另一些则需要相当复杂的语句。
虽然有人可能会说,一旦查询到位,这并不是什么大问题,但我不同意。首先,查询越复杂,对开发人员和随后的mongodbs查询优化器来说,优化就越困难。在简化数据模型和查询方面,我取得了最好的结果,在一个实例中,响应速度提高了100倍。
在扩展时,与更简单的数据模型和相应的查询相比,复杂和/或代价高昂的查询所需的资源甚至可能总计为整台机器。
问题3:可维护性
最后但并非最不重要的是,您可能会遇到维护代码的问题。作为一个简单的经验法则
代码越复杂,就越难维护。代码越难维护,维护代码的时间就越长。维护代码的时间越长,成本就越高。
结论:复杂的代码是昂贵的。
在这种情况下,“昂贵”既指金钱(专业项目)也指时间(业余项目)。
(我的!)解决方案
这很简单:简化数据模型。因此,您的查询将变得不那么复杂(希望)更快。
步骤1:识别用例
这对我来说是个疯狂的猜测,但这里最重要的是向你展示一般的方法。我将您的用例定义如下:
对于给定的帖子,用户应该能够评论
对于给定的帖子,显示作者和评论,以及评论人和作者的用户名和图片
对于给定的用户,应该可以很容易地更改名称、用户名和图片
步骤2:相应地对数据建模
用户
首先,我们有一个简单的用户模型

{
  _id: new ObjectId(),
  name: "Joe Average",
  username: "HotGrrrl96",
  picture: "some_link"
}

这里没有什么新的,只是为了完整起见。
帖子
{
  _id: new ObjectId()
  title: "A post",
  content: " Interesting stuff",
  picture: "some_link",
  created: new ISODate(),
  author: {
    username: "HotGrrrl96",
    picture: "some_link"
  }
}

就为了一个职位。这里有两件事需要注意:首先,我们存储显示post时立即需要的author数据,因为这为我们保存了一个非常常见(如果不是普遍存在的话)用例的查询。为什么我们不把评论和评论数据保存在一起呢?由于16 MB size limit,我们试图防止在单个文档中存储引用。相反,我们将引用存储在注释文档中:
评论
{
  _id: new ObjectId(),
  post: someObjectId,
  created: new ISODate(),
  commenter: {
    username: "FooBar",
    picture: "some_link"
  },
  comment: "Awesome!"
}

与posts一样,我们拥有显示post所需的所有数据。
询问
我们现在所取得的成果是,我们绕过了bson的大小限制,我们不需要引用用户数据就可以显示帖子和评论,这将为我们节省很多查询。但是让我们回到用例和更多的查询
添加注释
现在这完全是直截了当了。
获取给定帖子的全部或部分评论
所有评论
db.comments.find({post:objectIdOfPost})

最后3条评论
db.comments.find({post:objectIdOfPost}).sort({created:-1}).limit(3)

因此,为了显示一篇文章及其所有(或部分)评论,包括用户名和图片,我们有两个查询。比你以前需要的更多,但是我们绕过了大小限制,基本上你可以有一个无限数量的评论每一篇文章。但是让我们去做一些真实的事情
获取最新的5篇文章及其最新的3条评论
这是一个两步的过程。但是,如果有适当的索引(稍后将返回到该索引),这仍然应该很快(从而节省资源):
var posts = db.posts.find().sort({created:-1}).limit(5)
posts.forEach(
  function(post) {
    doSomethingWith(post);
    var comments = db.comments.find({"post":post._id}).sort("created":-1).limit(3);
    doSomethingElseWith(comments);
  }
)

获取给定用户从最新到最旧的所有帖子及其评论
var posts = db.posts.find({"author.username": "HotGrrrl96"},{_id:1}).sort({"created":-1});
var postIds = [];
posts.forEach(
  function(post){
    postIds.push(post._id);
  }
)
var comments = db.comments.find({post: {$in: postIds}}).sort({post:1, created:-1});

注意,我们这里只有两个查询。尽管您需要“手动”在帖子和它们各自的评论之间建立连接,但这应该非常简单。
更改用户名
这大概是一个很少执行的用例。然而,所述数据模型并不十分复杂
首先,我们更改用户文档
db.users.update(
  { username: "HotGrrrl96"},
  {
    $set: { username: "Joe Cool"},
    $push: {oldUsernames: "HotGrrrl96" }
  },
  {
    writeConcern: {w: "majority"}
  }
);

我们将旧用户名推送到相应的数组中。这是一种安全措施,以防以下操作出现问题。此外,为了确保数据的持久性,我们将write concern设置为相当高的级别。
db.posts.update(
  { "author.username": "HotGrrrl96"},
  { $set:{ "author.username": "Joe Cool"} },
  {
    multi:true,
    writeConcern: {w:"majority"}
  }
)

这里没什么特别的。注释的update语句看起来几乎相同。虽然这些查询需要一些时间,但很少执行。
指数
根据经验,可以说mongodb每个查询只能使用一个索引。虽然这并不完全正确,因为存在索引交叉点,但很容易处理。另一件事是复合索引中的各个字段可以独立使用。因此,一种简单的索引优化方法是找到在使用索引的操作中使用最多字段的查询,并创建它们的复合索引。请注意,查询中的发生顺序很重要。所以,我们继续吧。
帖子
db.posts.createIndex({"author.username":1,"created":-1})

评论
db.comments.createIndex({"post":1, "created":-1})

结论
诚然,每篇文章都有一个完全嵌入的文档是加载它和它的评论的最快方式。但是,它不能很好地扩展,而且由于处理它所必需的复杂查询的性质,这种性能优势可能被利用,甚至被消除。
有了以上的解决方案,您就可以以一定的速度(如果!)与基本上无限制的可伸缩性和更直接的数据处理方式相比。
Hth.

关于node.js - MongoDB查询注释以及用户信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32409635/

相关文章:

node.js - Mongoose 使用不同的外键加入两个不同的集合

javascript - NodeJS 函数返回数据的正确方法

javascript - 我如何在另一个 EC2 实例上运行部分代码

node.js - 从文件加载 Joi 模式

sql - RDBMS 或 NoSQL 用于具有循环和有序操作的复杂计划数据?

json - 将 Json 文件导入 Mongoose

mongodb - 从MongoDB到ElasticSearch的地理位置

javascript - 如何调用作为对象成员的函数?

javascript - 是否可以克隆 ES6 promise ?

mongodb - 如何在 Mongoose 文档中允许自由格式的 JSON 数据?