【问题标题】:Is Mongoose not scalable with document array editing and version control?Mongoose 不能通过文档数组编辑和版本控制进行扩展吗?
【发布时间】:2013-03-12 18:03:53
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

    标签: javascript node.js mongodb express mongoose


    【解决方案1】:

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

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

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

    注意:我实际上并不确定 其中任何一个 总的来说是好主意还是它们会解决您的问题,所以如果它们不好,请继续编辑/评论/否决:)

    【讨论】:

    • 我相信这会有类似的问题。我认为对文档的任何更改都会导致版本增加(将字段设置为true 或删除数组元素),然后在尝试调用update() 时将被拒绝,但如果这是错误的,请纠正我。
    【解决方案2】:

    认为,根据我们自己的经验,您的问题的答案是“是”。 Mongoose 可扩展以进行基于数组的快速更新。

    背景

    我们在HabitRPG 遇到了同样的问题。在最近用户增长激增(使我们的数据库达到 6gb)之后,我们开始体验VersionError 进行许多基于数组的更新(background on VersionError)。 ensureIndex({_id:1,__v1:1}) 有点帮助,但随着更多用户的加入而逐渐减少。在我看来,Mongoose 对于基于数组的更新确实不可扩展。你可以看到我们的整个investigation process here

    解决方案

    如果您负担得起从数组到对象的移动,那就这样做吧。例如,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. 使用上述方法。你不会从钩子和验证中受益,但还有更糟糕的事情。

    【讨论】:

    • 副本集在这里有什么帮助?这是为了高可用性。 OP(和您)需要更快的处理或写入(或更多并行写入),这只能通过分片来实现。
    • 你是对的。我没有意识到所有的写入都转到主服务器,而副本仅用于读取。已更新。
    • 正如顶级评论所建议的那样,在可能的情况下尝试使用原子操作对我来说比使用对象更有意义。感谢您链接 Github 问题讨论,这是一个非常有趣的阅读!
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

      • 不是我希望的答案,而是最有意义的答案。感谢您提供适当的可扩展解决方法!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-25
      • 2012-05-07
      • 2015-06-29
      • 1970-01-01
      • 2015-07-25
      • 1970-01-01
      相关资源
      最近更新 更多