MongoDB 集合更新:使用默认值初始化文档

MongoDB Collection update: initialize a document with default values

我正在尝试使用 MongoDB 处理 时间序列 。社区采用的常见解决方案是使用子文档来存储不同粒度级别的信息(参见 MongoDB).

中时间序列数据的模式设计

例如,查看以下文档:

{
  timestamp_minute: ISODate("2013-10-10T23:06:00.000Z"),
  type: “memory_used”,
  values: [
    999999,   // 1 second
    …
    1000000,  // nth second
    1500000,  // n+1th second
    … 
    2000000   // 60th
  ]
}

该文档按分钟信息索引,并包含一个子文档,该子文档每秒存储更详细的信息。

到目前为止一切顺利。这种方法需要优化才能正常工作:

Another optimization [..] is preallocating all documents for the upcoming time period; This never causes an existing document to grow or be moved on disk.

要实现上述优化,可以在 update 方法上使用 $setOnInsert 属性。

db.getCollection('aCollection').update(
    {
      timestamp_minute: ISODate("2013-10-10T23:06:00.000Z"),
      type: “memory_used”
    },
    {
      $setOnInsert: { values: {'0': 0, '1': 0, '2': 0}},
      $inc: {"values.30": 1}
    },
    { upsert: true }
)

问题是不可能在两个不同的操作中的同一个更新中使用同一个字段。以上更新指令生成以下错误:

Cannot update 'values' and 'values.30' at the same time

此问题已在 issue 上跟踪。

我的问题是:有什么解决方法吗? 我不能使用任何预分配空文档的批处理,因为我不知道索引的值fields a priori(在上面的例子中,字段的值type.

提前致谢。

我和我的同事找到了解决方法。我们可以称之为三步初始化

请记住,MongoDB 保证了对单个文档的操作的原子性。考虑到这一点,我们可以按以下方式操作:

  1. 尝试更新文档,在指定的时间块正确增加计数器。不要做任何更新插入,只是一个 old-fashioned 更新操作。记住执行一条更新语句returns 写入的文件数。如果写入的文档数大于零,你就完成了。
  2. 如果更新写入的文档数为零,则意味着要更新的相关文档还不存在于集合中。尝试为指定的标签插入整个文档。将所有计数器(字段值)置零。另外执行一条insert语句returns写入的文件数。如果它 returns 为零或抛出异常,没关系:这意味着其他一些进程已经为相同的标签插入了文档。
  3. 再次执行上述更新。

代码应该类似于以下代码片段。

// Firt of all, try the update
var result = db.test.update(
  {timestamp_minute: ISODate("2013-10-10T23:06:00.000Z"), type: “memory_used”},
  {$inc: {"values.39": 1}},
  {upsert: false}
);
// If the update do not succeed, then try to insert the document
if (result.nModified === 0) {
  try {
    db.test.insert(/* Put here the whole document */);
  } catch (err) {
    console.log(err);
  }
  // Here we are sure that the document exists.
  // Retry to execute the update statement
  db.test.update(/* Same update as above */);
}

如果前提条件成立,则上述过程有效:_id 值应从文档中的其他字段派生。在我们的示例中,_id 值将是 '2013-10-10T23:06:00.000Z-memory_used。仅使用此技术,点 2 处的插入将正常失败。