Mongodb Mongo:存储时间序列测量数据
我们每隔一段时间存储温度测量数据,我们只想保留90个以前的数据。我们的数据结构如下:Mongodb Mongo:存储时间序列测量数据,mongodb,Mongodb,我们每隔一段时间存储温度测量数据,我们只想保留90个以前的数据。我们的数据结构如下: { "_id" : ObjectId("xxx"), "device" : "deviceId1", "count": 2, "values" : [ { "ts" : NumberLong("1471077454902"), "measureData" : 37.3 }, { "ts" : NumberLong("1471077454911"), "measureData" : 37.
{ "_id" : ObjectId("xxx"), "device" : "deviceId1", "count": 2, "values" :
[
{ "ts" : NumberLong("1471077454902"), "measureData" : 37.3 },
{ "ts" : NumberLong("1471077454911"), "measureData" : 37.4 }
]
}
count是值的大小,例如,当数组有2个元素时,则大小为2。
我们对Java API的设计如下:
When the new measurement data comes:
Get corresponding device id count:
if count < 90:
Push the new mesaure data into values and increase count by 1
if count >90:
Pull the first element of the array and push the latest data into array.
Store the first element pulled into history collection.
当新的测量数据出现时:
获取相应的设备id计数:
如果计数小于90:
将新的mesaure数据推入值并将计数增加1
如果计数>90:
拉取数组的第一个元素并将最新数据推入数组。
存储拉入历史记录集合的第一个元素。
是否有一个查询或一个聚合可以执行这些步骤?或者我们应该用传统的方法,比如查询、求值,然后推或者拉/推
//
//***************第2部分传统方法***************//
public class TSDesignMain {
public static void main(String[] args) {
MongoClient mClient = new MongoClient();
MongoDatabase db = mClient.getDatabase(MongoTSConstants.dbname);
long tWarmingStart = System.nanoTime();
InsertingWarmingDocument(db);
long tWarmingEnd = System.nanoTime();
double tDurationWarming = (double) (tWarmingEnd-tWarmingStart) / 1000/1000;
System.out.println("warming db, insert 10000 document per event duration is "+tDurationWarming+"ms");
long tInsert1Dev1DocStart = System.nanoTime();
for(int j=1;j<10000;j++){
storeMeasureDataIntoDB(db);
}
long tInsert1Dev1DocEnd = System.nanoTime();
double tInsert1Dev1DocDuration = (double) (tInsert1Dev1DocEnd-tInsert1Dev1DocStart) / 1000/1000;
System.out.println("insert 10000 document in 90*24*30 elements array duration is "+tInsert1Dev1DocDuration+"ms");
long tQuery1Dev1DocStart = System.nanoTime();
for(int j=1;j<10000;j++){
handleQueryDocument(db);
}
long tQuery1Dev1DocEnd = System.nanoTime();
double tQuery1Dev1DocDuration = (double) (tQuery1Dev1DocEnd-tQuery1Dev1DocStart) / 1000/1000;
System.out.println("query 10000 times in 90*24*30 elements array duration is "+tQuery1Dev1DocDuration+"ms");
mClient.close();
}
private static void InsertingWarmingDocument(MongoDatabase db) {
long ts = Calendar.getInstance().getTimeInMillis();
for(long i=1;i<100;i++){
db.getCollection(MongoTSConstants.tsDataPdCollection).insertOne(
new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+i+"test")
.append(MongoTSConstants.tsFn,ts+i)
);
}
}
private static void handleQueryDocument(MongoDatabase db) {
Document where = new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+1);
FindIterable<Document> it = db.getCollection(MongoTSConstants.tsDataInOneCollection).find(where);
MongoCursor<Document> cur = it.iterator();
int i=0;
if(!cur.hasNext()){
Document doc = cur.next();
ArrayList<Document> obj = (ArrayList<Document>) doc.get("values");
}
}
private static void storeMeasureDataIntoDB(MongoDatabase db) {
Document where = new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+1);
FindIterable<Document> it = db.getCollection(MongoTSConstants.tsDataInOneCollection).find(where);
MongoCursor<Document> cur = it.iterator();
int i=0;
/**
* There is no device document in DB, insert new one. and use update to store 1st measure data
*
*/
if(!cur.hasNext()){
long tsInsert = System.nanoTime();
/**
* insert device id
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).insertOne(
new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+1)
.append(MongoTSConstants.countFn,0));
/**
* using update to insert values array (store the first measure data)
*
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(
new Document(MongoTSConstants.deviceFn,MongoTSConstants.deviceIdPrefix+1),
new Document("$push", new Document("values",new Document(MongoTSConstants.tsFn,tsInsert).
append(MongoTSConstants.measureDataFn,37.1)))
.append("$inc", new Document(MongoTSConstants.countFn,1)));
}else{
while(cur.hasNext()){
/**
* if i > 1, it means there are two doc with same device id, error!!
*/
if(i>=1){
//log error find two docs with same devicedID.
break;
}
Document doc = cur.next();
Integer count = doc.getInteger("count");
/**
* measure data has over 3 month.
*/
if( count >= MongoTSConstants.queryDataLength ){
/**
* get the first one in the array
*/
ArrayList<Document> obj = (ArrayList<Document>) doc.get("values");
Document doc1Elem = (Document) obj.get(0);
/**
* pull the first one in the array
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(where,
new Document("$pop", -1));
long ts = Calendar.getInstance().getTimeInMillis();
/**
* push the new one
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(where,
new Document("$push", new Document("values",new Document(MongoTSConstants.tsFn,ts).
append(MongoTSConstants.measureDataFn,37.8))));
//Store doc1Elem in another history collection;
db.getCollection(MongoTSConstants.tsHistoryCollection).insertOne(doc1Elem);
}else{
/**
* Measure data has not reach 3 month data
*/
long ts = Calendar.getInstance().getTimeInMillis();
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(where,
new Document("$push", new Document("values",new Document(MongoTSConstants.tsFn,ts).
append(MongoTSConstants.measureDataFn,37.9))).append("$inc", new Document(MongoTSConstants.countFn,1)));
}
i++;
}
}
}
}
公共类TSDesignMain{
公共静态void main(字符串[]args){
MongoClient mClient=新的MongoClient();
MongoDatabase db=mClient.getDatabase(MongoTSConstants.dbname);
long-tWarmingStart=System.nanoTime();
插入预热文件(db);
long-tWarmingEnd=System.nanoTime();
双时间升温=(双时间)(tWarmingEnd tWarmingStart)/1000/1000;
System.out.println(“升温数据库,每个事件持续时间插入10000个文档”+tDurationWarming+“ms”);
long tInsert1Dev1DocStart=System.nanoTime();
for(int j=1;j=MongoTSConstants.queryDataLength){
/**
*获取数组中的第一个
*/
ArrayList obj=(ArrayList)doc.get(“值”);
documentdoc1elem=(Document)对象get(0);
/**
*拉数组中的第一个
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(其中,
新文件(“$pop”,-1));
long ts=Calendar.getInstance().getTimeInMillis();
/**
*推新的
*/
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(其中,
新文档($push),新文档(“值”,新文档(MongoTSConstants.tsFn,ts)。
追加(MongoTSConstants.measureDataFn,37.8));
//将文档存储在另一个历史记录集合中;
db.getCollection(MongoTSConstants.tsHistoryCollection).insertOne(doc1Elem);
}否则{
/**
*测量数据未达到3个月数据
*/
long ts=Calendar.getInstance().getTimeInMillis();
db.getCollection(MongoTSConstants.tsDataInOneCollection).updateOne(其中,
新文档($push),新文档(“值”,新文档(MongoTSConstants.tsFn,ts)。
追加(MongoTSConstants.measureDataFn,37.9))。追加($inc),新文件(MongoTSConstants.countFn,1));
}
i++;
}
}
}
}
在我看来,目前很难做到这一点(目前mongodb的最新版本是v3.2),我不知道如何实现这一目标
解释
一、概述
此处有5个操作,您希望将它们合并为1个更新操作:
db.test.update(
{/* Query criteria */},
{/* Update action */}
)
// We call this statement as UPDATE_STAT_2
db.test.update(
{device: 'deviceId1', count: {$gte: 90}},
{/* Update action: push new data to values and pop the oldest*/}
);
db.test.update(
{device: 'deviceId1', count: {$lt: 90}},
{
$push: {values: { "ts" : "1471077454988", "measureData" : 39 }},
$inc : {count: 1}
}
);
我采用了另一种方法-将评估放入查询条件部分,如下所示:
// We call this statement as UPDATE_STAT_1
db.test.update(
{device: 'deviceId1', count: {$lt: 90}},
{/* Update action: push new data to values and increase count */}
);
如果计数<90,您的示例文档将被匹配并执行更新操作(此时不关心更新操作是否成功)。
然后mongoDB会将执行结果返回给您:
// Matched and updated
WriteResult({
"nMatched" : 1,
"nUpserted" : 0,
"nModified" : 1
});
结果告诉您一个文档已匹配并更新(nMatched=1&nModified=1)
另一方面,如果计数>=90,则不会匹配和更新任何内容,执行结果将为:
// Not Matched
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0
})
因此,您将知道计数达到90,您应该执行另一个update语句:
db.test.update(
{/* Query criteria */},
{/* Update action */}
)
// We call this statement as UPDATE_STAT_2
db.test.update(
{device: 'deviceId1', count: {$gte: 90}},
{/* Update action: push new data to values and pop the oldest*/}
);
db.test.update(
{device: 'deviceId1', count: {$lt: 90}},
{
$push: {values: { "ts" : "1471077454988", "measureData" : 39 }},
$inc : {count: 1}
}
);
总之,您可以先执行更新统计1,然后检查执行结果,以决定是否需要像这样执行更新统计2(伪代码):
这是我合并查询(第一)和评估(第二)的方法。我不知道如何将上述所有代码合并到一个语句中
三、 更新部分:合并推送(第三次)/弹出(第四次)/增加(第五次)?
我已经在本地尝试过,您可以将推和增加放在一个语句中:
db.test.update(
{/* Query criteria */},
{/* Update action */}
)
// We call this statement as UPDATE_STAT_2
db.test.update(
{device: 'deviceId1', count: {$gte: 90}},
{/* Update action: push new data to values and pop the oldest*/}
);
db.test.update(
{device: 'deviceId1', count: {$lt: 90}},
{
$push: {values: { "ts" : "1471077454988", "measureData" : 39 }},
$inc : {count: 1}
}
);
同时推(第三)和增加(第五)是合法的。但如果要推送和弹出阵列:
db.test.update(
{device: 'deviceId1', count: {$gte: 90}},
{
$pop: {values: -1},
$push: {values: { "ts" : "1471077454911", "measureData" : 40 }}
}
);
您将得到以下错误:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 16837,
"errmsg" : "Cannot update 'values' and 'values' at the same time"
}
})
这意味着您不能同时对值执行push&pop操作。为此,我找到了这样的解释:
问题是MongoDB不允许在服务器上执行多个操作
同一更新调用中的相同属性。这意味着