【问题标题】:Concurrency issues when removing dependent documents with mongoose middlewares使用 mongoose 中间件删除依赖文档时的并发问题
【发布时间】:2017-07-20 04:18:33
【问题描述】:

假设我们有一个简单的应用程序,用户可以在其中创建产品并对其进行评论。 products 和 cmets 的架构可以是:

var productSchema = new mongoose.Schema({
  author_id: ObjectId,
  description: String
});

var commentSchema = new mongoose.Schema({
  product_id: ObjectId,
  author_id: ObjectId,
  message: String
});

我们希望确保每条评论都指向现有产品。这可以通过 mongoose pre save hook 轻松完成:

commentSchema.pre("save", function(next) {
  Product.count({ _id: this.product_id }, function(err, count) {
    if (err || !count) {
      next(new Error("Could not find product"));
    } else {
      next();
    }
  });
});

此外,如果用户删除了产品,我们希望删除该产品上的所有 cmets。这可以使用 pre remove hook 轻松完成:

productSchema.pre("remove", function(next) {
  Comment.remove({ product_id: this._id }, next);
});

但是如果用户 A 删除了一个产品,同时用户 B 开始使用该产品呢?

可能会发生以下情况:

Call pre save hook for new comment, and check if product exists
Call pre remove hook for product, and remove all comments
In pre save hook, done checking: product actually exists, call next
Comment saved
In pre remove hook, done removing comments: call next
Product removed

最终结果是我们有一个评论指向一个不存在的产品。

这只是导致这种情况发生的众多情况之一。如何防止这种极端情况?

【问题讨论】:

  • 您可能会有延迟,用户无法发表评论。我认为这就是堆栈的作用

标签: javascript node.js mongodb mongoose concurrency


【解决方案1】:

国旗怎么样。

var deletingPostOfId = null;

function deletePost(id) {
  deletingPostOfId = id;
  
  Post.remove({_id: id}, function() {
      deletingPostOfId = null;
  })
}

function createComment(comment) {
  if(comment.post_id !== deletingPostOfId) Comment.save(...)
}

【讨论】:

    【解决方案2】:

    似乎使用猫鼬post hooks 而不是pre hooks 可以解决问题:

    commentSchema.post("save", function(comment) {
      Product.count({ _id: comment.product_id }, function(err, count) {
        if (err || !count) comment.remove();
      });
    });
    
    productSchema.post("remove", function(product) {
      Comment.remove({ product_id: product._id }).exec();
    });
    

    让我们通过考虑四种可能的情况(我能想到的)来看看为什么这可以解决问题:

    1) Comment gets saved before product is removed
    2) Comment gets saved after product is removed but before post remove hook
    3) Comment gets saved after product is removed and while post remove hook is 
       executing
    4) Comment gets saved after product is removed and post remove hook executed
    ------------------------------------------------------------------------
    In case 1, after the product is removed, the comment will be removed in the post 
    remove hook.
    In case 2, same, post remove hook will remove the comment.
    In case 3, the comment post save hook will successfully remove the comment.
    In case 4, same as case 3, post save hook removes the comment.
    

    但是还有一个小问题:如果在删除产品之后但在执行post remove hook 之前发生了不好的事情怎么办?说停电或类似的事情。在这种情况下,我们最终会得到引用不存在的产品的 cmets。为了解决这个问题,我们可以在产品上保留pre remove hook。这保证了仅在删除所有相关 cmets 时才删除产品。然而,正如 OP 所指出的,这并不能处理并发问题,这就是我们的 post remove hook 来救援的地方!所以我们两者都需要:

    productSchema.pre("remove", function(next) {
      var product = this;
      Comment.remove({ product_id: product._id }, next);
    });
    
    productSchema.post("remove", function(product) {
      Comment.remove({ product_id: product._id }).exec();
    });
    

    我希望是这样,但我仍然可以想到一个非常遥远的案例:如果在删除产品后保存评论并且 post remove hook 执行但就在评论 post save hook 执行之前(这将删除评论)灯熄灭了!我们最终得到的评论是指不存在的产品!这种情况发生的几率非常低,但仍然......

    如果有人能想到更好的并发处理方法,请改进我的答案或自己编写!

    【讨论】:

      猜你喜欢
      • 2020-07-29
      • 2014-02-09
      • 2016-05-06
      • 2015-11-08
      • 2014-05-01
      • 2015-04-04
      • 2020-01-31
      • 1970-01-01
      • 2013-09-04
      相关资源
      最近更新 更多