Mongo:存储时间序列测量数据

时间:2016-08-15 05:34:01

标签: mongodb

我们每隔一段时间存储温度测量数据,我们只想保留90个以前的数据。我们的数据结构如下:

{ "_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.

是否有一个查询或一个聚合可以执行这些步骤?或者我们应该通过传统的方法,比如查询,评估,然后推或拉/推。

// // ***************第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++;
            }
        }       
    }  
}

1 个答案:

答案 0 :(得分:2)

在我看来,目前很难做到(目前mongodb的最新版本是v3.2。)我不知道如何实现目标。

解释

予。概述

此处有5个操作,您希望将它们合并为1个更新操作

  1. deviceId
  2. 查询
  3. 评估计数(计数<90或计数&gt; = 90?)
  4. 将新数据推送到数组
  5. 的尾部
  6. 数组中弹出最旧的数据(可选,基于步骤2)
  7. 计数 + 1(可选,基于第2步)
  8. 我们将它们分为两部分:查询部分&amp; 更新部分

    II。查询部分:合并查询(第1个)和评估(第2个)?

    由于我不知道如何将评估纳入更新声明的更新操作部分:

    db.test.update(
        {/* Query criteria */},
        {/* Update action */}
    )
    

    我采用了另一种方法 - 将评估纳入查询条件部分,如下所示:

    // 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 */}
    );
    

    如果计数&lt; 90 ,您的样本文档将匹配&amp;执行更新操作(此时不要关心更新操作是成功还是失败)。 然后mongoDB将把执行结果返回给你:

    // Matched and updated
    WriteResult({ 
        "nMatched" : 1,
        "nUpserted" : 0, 
        "nModified" : 1 
    });
    

    结果告诉您一个文档已匹配并更新(nMatched = 1&amp; nModified = 1)

    另一方面,如果 count&gt; = 90 ,则不会匹配和更新任何内容,执行结果将为:

    // Not Matched
    WriteResult({ 
        "nMatched" : 0, 
        "nUpserted" : 0, 
        "nModified" : 0 
    })
    

    所以你会知道计数达到90,你应该执行另一个更新声明:

    // 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*/}
    );
    

    总之 ,您可以先执行 UPDATE_STAT_1 ,然后检查执行结果以确定是否需要像这样执行 UPDATE_STAT_2 (伪代码):

    exe_result = run(UPDATE_STAT_1); // Run UPDATE_STAT_1
    if(exe_result.nMatched == 0 && exe_result.nModified == 0) {
        run(UPDATE_STAT_2); // Check the count and Run UPDATE_STAT_2
    } 
    

    这是我合并查询(第1个)和评估(第2个)的方法。我不知道如何将上述所有代码合并为一个语句。

    III。更新部分:合并推(第3)/流行(第4)/增加(第5)?

    我曾在当地尝试过,你可以把推送&amp; 增加到一个语句中:

    db.test.update(
        {device: 'deviceId1', count: {$lt: 90}},
        {
            $push: {values: { "ts" : "1471077454988", "measureData" : 39 }},
            $inc : {count: 1}
        }
    );
    

    做Push(3rd)&amp ;;同时增加(第5名)。但是,如果你想推动&amp;弹出数组:

    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"
        }
    })
    

    这意味着你无法推进和推动同时弹出值。为此,我找到了这个解释:

      

    问题是MongoDB不允许对其进行多项操作   同一更新调用中的相同属性。这意味着两者   操作必须在两个单独的原子操作中进行。

    <强> 因此。总之 ,它无法将Push(3rd)/ Pop(4th)/ Increase(5th)合并为一个语句。

    IV。摘要

    根据上面的示例,无法合并Query(1st)和Evaluation(2nd),Push(3rd)/ Pop(4th)/ Increase(5th)也不能合并。所以你可以用传统方式做到这一点。感谢。