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 本身。它通常是不遵循原子性规则的代码(这可能发生在很多习惯事务数据库的人身上:))。
我们正在为我们的系统构建大小计算机制。
为了计算大小,我们从第一个原子操作开始 - 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 本身。它通常是不遵循原子性规则的代码(这可能发生在很多习惯事务数据库的人身上:))。