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

时间:2016-09-06 16:27:44

标签: node.js mongodb bluebird coroutine

我们正在为我们的系统建立规模计算机制。 为了计算大小,我们从第一个原子操作开始 - 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对象保持完全一样,锁定后,尽管事实日志告诉我们一切顺利(计算完成,计算新的大小对象并调用第二个DB更新)。

我们使用MongoDB 3.0,两个replicaSet。关于发生了什么的任何想法?

2 个答案:

答案 0 :(得分:2)

在第二次更新之后放置,然后它将等到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对您的承诺进行错误处理。

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

答案 1 :(得分:0)

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

因此,对每个MongoDB用户来说都很重要 - 请记住MongoDB不是事务性的,但仍然是 atomic ,这意味着它可以保证您的操作将被持久化,但不保证数据操作之间将持续存在。

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

  • 从未使用从中获取的数据更新数据库中的整个对象(例如,通过查询,更改某些属性并再次保存)
  • USE $set$inc$unset和其他特殊运营商。如果你有很多参数,请使用例如mongo-dot-notation npm库将您的数据展平为$set选择器。
  • 如果您的数据发生意外情况(例如保存后缺少属性),首先要调查的是对这些特定实体的其他待处理操作
  • 问题的最不可能原因是MongoDB本身。它通常是不遵循原子性规则的代码(很可能是很多人用来处理事务DB)。)。