4

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

在客户端应用程序中,每当用户点击“删除”按钮时,它都会触发 AJAX 请求以删除该特定评论的路由。我遇到的问题是,当这些 AJAX 调用中的许多同时进入时,Mongoose 在某些(但不是全部)调用上失败并出现“找不到文档”错误。

这只发生在一次快速且多次拨打电话时。我认为这是由于 Mongoose 中的版本导致文档冲突。我们当前的删除流程是:

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

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

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

这是否意味着 Mongoose 对于快速编辑的任何东西基本上都没有用,我还不如只使用本机 MongoDB 驱动程序?我是否误解了猫鼬的局限性?我该如何解决这个问题?

4

4 回答 4

7

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

对于可扩展的文档数组操作,您必须使用update原子数组更新运算符:$pull[All]、、、、$push[All]和。当然,如果您还需要原始或生成的文档,您也可以将这些运算符与基于 atomic的方法一起使用。$pop$addToSet$findAndModifyfindByIdAndUpdatefindOneAndUpdate

update正如您所提到的,使用而不是findOne+的最大缺点save是您的 Mongoose 中间件和验证都不会在update. 但是,如果您想要一个可扩展的系统,我认为您别无选择。我宁愿为更新案例手动复制一些中间件和验证逻辑,也不愿遭受使用 Mongoose 的版本化文档数组编辑的可伸缩性损失。嘿,至少您仍然可以从 Mongoose 的基于模式的更新类型转换中受益!

于 2014-02-07T03:48:26.057 回答
3

认为,根据我们自己的经验,您的问题的答案是“是”。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. 使用上述方法。你不会从钩子和验证中受益,但还有更糟糕的事情。
于 2014-02-07T02:43:35.840 回答
0

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

你是对的,Mongoose 的数组操作不是原子的,因此不能很好地扩展。

于 2014-02-08T05:24:13.783 回答
0

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

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

哦,另一个想法是使用某种内存缓存,因此如果在过去几分钟内访问/编辑了一条记录,则无需从服务器拉取它即可使用,这意味着两个请求同时进入时间将修改同一个对象。

注意:我实际上不确定这些都是好主意还是它们会解决您的问题,所以如果它们不好,请继续编辑/评论/downvote :)

于 2017-11-19T15:02:56.340 回答