MongoDB - 两个更新顺序相互重叠

MongoDB - two updates in sequence overlap each other

我们正在为我们的系统构建大小计算机制。 为了计算大小,我们从第一个原子操作开始 - findAndModify - 找到对象并为其添加锁定属性(以防止对该对象进行另一次计算以与其交互并等到结束,因为我们可能有很多并行计算——在这种情况下,其他计算应该推迟),然后我们计算特定属性的大小,并在此操作之后——我们将元数据添加到对象并删除锁。 但是,似乎有时,当我们对单个对象进行大量多次计算时(尤其是当我们并行计算大量对象时),某些更新不会执行。

_size 计算期间的元数据如下所示:

{
  _lockedAt: SomeDate,
  _transactionId: 'abc'
}

经过计算应该是这样的:

{
  somePropertySize: 123,
  anotherPropertySize: 1245,
  (...)
  _total: 131431523 // Some number
  // Notice that both _lockedAt and _transactionId should be missing
}

这就是我们的更新流程:

return Promise.coroutine(function * () {

    yield object.findOneAndUpdate({
        '_id': gemId,
        '_size._lockedAt': {
          $exists: false
        }
      }, {
        $set: {
          '_size._lockedAt': moment.utc().toDate(),
          '_size._transactionId': transactionId
        }
      }).then(results => results.value);

      // Calculations are performed here, new _size object is built

    yield object.findOneAndUpdate({
      _id: gemId,
      _lockedAt: {
        $exists: true // We tried both with and without this property, does not change anything
      }
    }, {
      $set: {
        _size: newSizeObject
      }
    });

})()

第二次更新之前的示例性真实对象(为简洁起见被截断):

{ 
  title: 11, 
  description: 2, 
  detailedSection: 0, 
  tags: 2
  file: 5625898,
  _total: 5625913 
}

出于某种原因,当我们有多个计算彼此相邻时,有时(对于新对象,根本没有 _size 属性),对象与 _size 对象保持一致看起来与锁定后完全一样,尽管事实日志告诉我们一切顺利(计算已完成,计算了新的大小对象并调用了第二次数据库更新)。

我们使用 MongoDB 3.0,两个副本集。对正在发生的事情有什么想法吗?

将第二次更新放在 then 之后,这样它将等到 promise 解决:

object.findOneAndUpdate({
    '_id': gemId,
    '_size._lockedAt': {
      $exists: false
    }
  }, {
    $set: {
      '_size._lockedAt': moment.utc().toDate(),
      '_size._transactionId': transactionId
    }
  }).then(results => {

  // Calculations are performed here, new _size object is built

  object.findOneAndUpdate({
    _id: gemId,
    _lockedAt: {
      $exists: true // We tried both with and without this property, does not change anything
    }
  }, {
    $set: {
      _size: newSizeObject
    }
  });
}).catch(err => console.error);

还要确保使用 catch 对 promise 进行了错误处理。

如果您真的不需要锁或交易字段,那么我会删除那些东西。如果你确实需要它们,像 RethinkDB 这样的东西可能会更好一些,或者 PostgresSQL 可以提供真实的交易。

总而言之,我非常仔细地检查了代码,实际上发生了什么,代码的完全不同的部分是从数据库中查询对象,然后在进行了一些其他操作(包括我的操作)之后),它将对象写入数据库(因此,覆盖了我的更改)。

所以,对于每个 MongoDB 用户的重要提示 - 请记住 MongoDB 不是事务性的,但仍然是 atomic,这意味着它保证您的操作将被持久化,但不保证操作之间的数据将被持久化。

总结一下,我通过这个例子学到的东西:

  • 从不用之前从数据库中获得的数据更新整个数据库中的对象(例如通过查询、更改某些属性并再次保存)
  • USE$set$inc$unset等特殊运算符。如果您有很多参数,请使用例如mongo-dot-notation npm 库将您的数据扁平化为 $set 选择器。
  • 如果您的数据出现意外情况(例如,保存后缺少属性),首先要调查的是对这些特定实体的另一个未决操作
  • 问题的最不可能 原因是 MongoDB 本身。它通常是不遵循原子性规则的代码(这可能发生在很多习惯事务数据库的人身上:))。