Mongoose是否不能通过文档数组编辑和版本控制进行扩展?

Is Mongoose not scalable with document array editing and version control?

本文关键字:版本控制 扩展 编辑 数组 不能 是否 文档 Mongoose      更新时间:2023-09-26

我正在开发一个web应用程序与Node.js和MongoDB/Mongoose。我们最常用的模型Record有许多子文档数组。例如,其中包括"评论"、"预订"answers"订阅者"。

在客户端应用程序中,无论何时用户点击"delete"按钮,它都会触发一个AJAX请求,删除该特定评论的路由。我遇到的问题是,当许多这些AJAX调用同时进来时,Mongoose在一些(但不是全部)调用上失败并出现"Document not found"错误。

这种仅在快速且多次调用时发生。我认为这是由于Mongoose的版本导致文档冲突。我们当前的删除流程是:

  1. 使用Record.findById()
  2. 获取文档
  3. 从适当的数组中删除子文档(例如使用comment.remove())
  4. Call record.save()

我找到了一个解决方案,我可以使用Record.findByIdAndUpdate手动更新集合,然后使用$pull操作符。然而,这意味着我们不能使用mongoose的任何中间件而完全失去版本控制。我想得越多,我就越意识到这种情况会发生,我必须使用Mongoose的包装函数,比如findByIdAndUpdatefindAndRemove。我能想到的唯一其他解决方案是将删除尝试放入while循环并希望它有效,这似乎是一个非常糟糕的修复。

使用Mongoose包装器并不能真正解决我的问题,因为它不允许我使用任何中间件或钩子,这基本上是使用Mongoose的巨大好处之一。

这是否意味着猫鼬基本上是无用的任何快速编辑,我也可能只是使用本机MongoDB驱动程序?我是不是误解了猫鼬的局限性?我该如何解决这个问题?

Mongoose的版本化文档数组编辑是不可伸缩的,原因很简单,它不是一个原子操作。因此,您拥有的数组编辑活动越多,两个编辑发生冲突的可能性就越大,并且您将在代码中遭受重试/恢复的开销。

对于可伸缩的文档数组操作,必须使用update和原子数组更新操作符:$pull[All]$push[All]$pop$addToSet$。当然,如果您还需要原始文档或结果文档,也可以将这些操作符与基于findByIdAndUpdatefindOneAndUpdate的原子findAndModify方法一起使用。

正如您所提到的,使用update而不是findOne + save的最大缺点是在update期间没有执行任何Mongoose中间件和验证。但我看不出如果你想要一个可扩展的系统,你有什么选择。我宁愿为更新情况手动复制一些中间件和验证逻辑,也不愿忍受使用Mongoose的版本化文档数组编辑所带来的可伸缩性损失。嘿,至少你还能从Mongoose基于模式的类型转换更新中获益!

我认为根据我们自己的经验,对你的问题的答案是"是"。对于基于数组的快速更新,Mongoose是不可扩展的。

的背景

我们在HabitRPG也遇到了同样的问题。在最近用户增长激增(使我们的数据库达到6gb)之后,我们开始在许多基于数组的更新中遇到VersionError (VersionError的背景)。ensureIndex({_id:1,__v1:1})帮了一点忙,但随着越来越多的用户加入,这种作用逐渐减弱。在我看来,Mongoose确实不能扩展到基于数组的更新。你可以在这里看到我们整个调查过程。

解决方案

如果你有能力从数组转移到对象,那么就这样做。例如,comments: Schema.Types.Array =>comments: Schema.Types.Mixed,并按post.comments.{ID}.date排序,必要时甚至手动设置post.comments.{ID}.position

如果你坚持使用数组:

  1. db.collection.ensureIndex({_id:1,__v:1})
  2. 使用上面描述的方法。你不会从钩子和验证中受益,但还有更糟糕的事情。

我强烈建议将这些数组提取到新的集合中。例如,在Comments集合中,每个文档都有一个记录ID来指示它所属的位置。这是一个可扩展的解决方案。

你是正确的,Mongoose的数组操作不是原子的,因此不能很好地伸缩。

我想到了另一个主意,我不确定,但似乎值得提出:软删除。

Mongoose非常关注数组结构的变化,因为它们会使未来的变化变得模棱两可。但是,如果您只是用comment.deleted=true标记评论子文档,那么您可能能够做更多这样的操作而不会遇到冲突。然后你可以有一个cron任务来检查并删除这些注释。

哦,另一个想法是使用某种内存缓存,所以如果一个记录在最近几分钟内被访问/编辑,它是可用的,而不必从服务器中取出它,这意味着两个请求同时进入将修改同一个对象。

注意:我不确定这些中的一般来说是好主意,或者它们能解决你的问题,所以如果它们不好,请继续编辑/评论/downvote:)