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库来将你的数据压缩到$set
select器中。 - 如果您的数据出现意想不到的情况(例如保存后遗失的属性),首先要调查的是另一个正在执行的操作
- 您的问题最不可能的原因是MongoDB本身。 这通常是不遵循primefaces性规则的代码(这可能发生在很多人用来交易数据库:))。