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

我们正在为我们的系统build立规模计算机制。 为了计算大小,我们从第一个primefaces操作findAndModify开始寻找对象并为其添加locking属性(为了防止另一个计算对象与它进行交互并等待结束,因为我们可能有许多并行计算 – 在这种情况下,其他人应该被推迟),然后我们计算特定属性的大小,并在此操作之后 – 我们将元数据添加到对象并删除locking。 但是,有时候,当我们有很多单个对象的计算(特别是当我们并行计算大量对象的时候),有些更新不会被执行。

计算过程中的_size元数据如下所示:

 { _lockedAt: SomeDate, _transactionId: 'abc' } 

计算后应该是这样的:

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

这就是我们的更新stream程:

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

第二次更新之前的示例性实际对象JUST(为简明起见,截断):

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

出于某种原因,当我们有多个彼此相邻的计算时,有时(对于新的对象, _size不具有_size属性),尽pipe事实日志告诉我们一切都很顺利,对象保持与_size对象完全一样,计算完成,计算新的大小对象并调用第二个数据库更新)。

我们使用MongoDB 3.0,两个副本集。 有什么想法发生了什么?

然后把第二个更新,所以它会等待,直到承诺解决:

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

还要确保你有error handling你的承诺使用catch。

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

总而言之,我仔细地检查了代码,发现现实中发生的事情是,完全不同的代码部分是从DB查询对象,然后在其他一些操作(包括我的)之后,写了对象到数据库(因此,覆盖我的更改)。

所以,对每个MongoDB用户来说都是重要的注意事项 – 请记住,MongoDB不是事务性的,但仍然是primefaces性的 ,这意味着它可以保证你的操作将被持久化,但不能保证操作之间的数据将被持久化。

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

  • 永远不要用数据库中的数据来更新数据库中的整个对象(例如查询,更改某些属性并再次保存)
  • 使用 $set$inc$unset和其他特殊的操作符。 如果你有很多参数,使用例如mongo-dot-notation npm库来将你的数据压缩到$setselect器中。
  • 如果您的数据出现意想不到的情况(例如保存后遗失的属性),首先要调查的是另一个正在执行的操作
  • 您的问题最不可能的原因是MongoDB本身。 这通常是不遵循primefaces性规则的代码(这可能发生在很多人用来交易数据库:))。