使用猫鼬中间件删除依赖文档时的并发问题 [英] Concurrency issues when removing dependent documents with mongoose middlewares

查看:124
本文介绍了使用猫鼬中间件删除依赖文档时的并发问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Suppose we have a simple application where users can create products and comment them. The schema for products and comments could be:

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

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

我们要确保每个评论都指向现有产品.这可以通过猫鼬预保存钩子轻松实现:

We want to make sure that every comment refers to an existing product. This can be easily accomplished with 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();
    }
  });
});

此外,如果用户删除了某个产品,我们也希望删除该产品上的所有评论.这可以通过使用预移除钩子轻松实现:

Also if a user removes a product, we want to remove all the comments on that product. This can be easily accomplished using a pre remove hook:

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

但是,如果用户A删除了一个产品,同时用户B对该产品发表了评论,该怎么办?

But what if user A removes a product and at the same time user B comments on that product?

可能会发生以下情况:

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

最终结果是,我们有一条注释提到了不存在的产品.

The end result is that we have a comment that refers to a non-existent product.

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

This is just one of the many cases that would cause this to occur. How can this corner case be prevented?

推荐答案

似乎使用猫鼬post hook s代替pre hook s可以解决问题:

Seems that using mongoose post hooks instead of pre hooks solves the problem:

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();
});

让我们通过考虑四种可能的情况(我能想到)来解决这个问题的原因:

Let's see why this solves the problem by considering the four possible cases (that I can think of):

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之前发生了不好的情况该怎么办?说断电或类似的事情.在这种情况下,我们最终将得到涉及不存在的产品的注释.要解决此问题,我们可以在产品上保留pre remove hook.这保证只有在删除所有从属注释的情况下,才删除产品.但是,这不能解决并发问题,正如OP所指出的那样,这就是我们的post remove hook可以解决的地方!所以我们都需要:

However there's still a little problem: what if something bad happens after the product is removed but before the post remove hook is executed? Say electricity goes off or something like that. In that case we will end up with comments that refer to a product that doesn't exist. To fix this we can keep the pre remove hook on products. This guarantess that a product is removed only and only if all the dependent comments were removed. However this does not handle concurrency problems, as pointed out by the OP, that's where our post remove hook comes to the rescue! So we need both:

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执行之前执行但又保存了注释,该怎么办?评论),灯灭了!我们最后给出的评论指的是不存在的产品!发生这种情况的可能性极低,但仍然如此.

I wish this was it, but I can still think of a very remote case: what if a comment gets saved after the product is removed and post remove hook executed BUT just before the comment post save hook executes (which would remove the comment) the lights go off! We end up with a comment that refers to a product that does not exist! The odds for this to happen are incredibly low, but still..

如果有人想出一种更好的处理并发的方法,请改善我的答案或写自己的答案!

If any one can think of a better way to handle concurrency, please improve my answer or write your own!

这篇关于使用猫鼬中间件删除依赖文档时的并发问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆