我正在尝试使用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 ] }
该文档由分钟信息编制索引,并包含一个子文档,该子文档存储每秒的更详细信息.
到现在为止还挺好.这种方法需要优化才能正常工作:
另一个优化[..]正在为即将到来的时间段预分配所有文件; 这永远不会导致现有文档在磁盘上增长或移动.
要实现上述优化,可以使用$setOnInsert
该update
方法的属性.
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 } )
问题是在两个不同的操作中不可能在同一更新中使用相同的字段.以上更新istruction会生成以下错误:
Cannot update 'values' and 'values.30' at the same time
在此问题上跟踪此问题.
我的问题是:有没有解决方法?我的前缀是我不能使用任何预先分配空文档的批处理,因为我无法先验地知道索引字段的值(在上面的例子中,字段的值type
.
提前致谢.
我和我的同事找到了解决方法.我们可以称之为三步初始化.
请记住,MongoDB保证了单个文档的操作原子性.考虑到这一点,我们可以通过以下方式运作:
尝试更新文档,在指定的时间块正确递增计数器.不要做任何upsert,只是一个老式的更新操作.请记住,update语句的执行会返回写入的文档数.如果写入的文档数大于零,那么就完成了.
如果更新写入的文档数为零,则表示要更新的相关文档尚未出现在集合中.尝试为指定的标记插入整个文档.将所有计数器(字段值)置于零.插入语句的执行也返回写入的文档数.如果它返回零或抛出异常,请不要介意:这意味着其他一些进程已经为相同的标记插入了文档.
再次执行相同的上述更新.
代码看起来应该类似于以下代码片段.
// 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点处的插入将正确失败.