在 MongoDB mapreduce 中,如何展平值对象?

Posted

技术标签:

【中文标题】在 MongoDB mapreduce 中,如何展平值对象?【英文标题】:In MongoDB mapreduce, how can I flatten the values object? 【发布时间】:2011-11-07 15:35:52 【问题描述】:

我正在尝试使用 MongoDB 来分析 Apache 日志文件。我从 Apache 访问日志中创建了一个 receipts 集合。以下是我的模型外观的简短摘要:

db.receipts.findOne()

    "_id" : ObjectId("4e57908c7a044a30dc03a888"),
    "path" : "/videos/1/show_invisibles.m4v",
    "issued_at" : ISODate("2011-04-08T00:00:00Z"),
    "status" : "200"

我编写了一个MapReduce function,它按issued_at 日期字段对所有数据进行分组。它总结了请求的总数,并提供了每个唯一路径的请求数的细分。以下是输出的示例:

db.daily_hits_by_path.findOne()

    "_id" : ISODate("2011-04-08T00:00:00Z"),
    "value" : 
        "count" : 6,
        "paths" : 
            "/videos/1/show_invisibles.m4v" : 
                "count" : 2
            ,
            "/videos/1/show_invisibles.ogv" : 
                "count" : 3
            ,
            "/videos/6/buffers_listed_and_hidden.ogv" : 
                "count" : 1
            
        
    

我怎样才能使输出看起来像这样:


    "_id" : ISODate("2011-04-08T00:00:00Z"),
    "count" : 6,
    "paths" : 
        "/videos/1/show_invisibles.m4v" : 
            "count" : 2
        ,
        "/videos/1/show_invisibles.ogv" : 
            "count" : 3
        ,
        "/videos/6/buffers_listed_and_hidden.ogv" : 
            "count" : 1
        
    

【问题讨论】:

【参考方案1】:

目前不可能,但我建议对此案投票:https://jira.mongodb.org/browse/SERVER-2517。

【讨论】:

这是正确的答案,所以投票给这个案例,然后使用***.com/a/18124090/1402121作为你的解决方案【参考方案2】:

充分利用以前的答案和 cmets:

db.items.find().hint(_id: 1).forEach(function(item) 
    db.items.update(_id: item._id, item.value);
);

来自http://docs.mongodb.org/manual/core/update/#replace-existing-document-with-new-document “如果update 参数只包含字段和值对,则update() 方法将现有文档替换为update 参数中的文档,_id 字段除外。”

因此您既不需要$unset value,也不需要列出每个字段。

来自https://docs.mongodb.com/manual/core/read-isolation-consistency-recency/#cursor-snapshot “在某些情况下,MongoDB 游标可以多次返回同一文档。...在此字段或这些字段上使用唯一索引,以便查询将不超过一次返回每个文档。使用提示()查询以显式强制查询以使用该索引。”

【讨论】:

这会导致严重的竞争条件。 @DerekBrown,请解释或提供证明或指出此代码中的错误 db.items.update() 是异步的,因此外部调用(即db.items.find().hint()....foreach())的执行可以在所有更新完成之前终止。 @DerekBrown,上面的代码使用标准的同步 JS API。如果您使用异步驱动程序,那么您有责任等待所有异步操作完成。如果你想说更新是最终一致的(这与异步不同),并且默认写关注点不足以满足你的情况,那么请使用更严格的写关注点配置 - docs.mongodb.com/manual/reference/write-concern 1) 官方不支持同步驱动。所以至少你应该提到你的代码需要你使用不同的 mongo 驱动程序。 2)写关注在这里不相关。问题不在于您创建的更新没有传播到其他分片,而是未来的查询(需要完成更新)尚未在 NodeJS 与之交互的单个实例上发出。【参考方案3】:

AFAIK,按照设计,Mongo 的 map reduce 将在“值元组”中输出结果,我还没有看到任何可以配置“输出格式”的东西。也许可以使用 finalize() 方法。

您可以尝试运行一个后期处理,该处理将使用

重塑数据
results.find().forEach( function(result) 
  results.update(_id: result._id, count: result.value.count, paths: result.value.paths)
);

是的,看起来很难看。我知道。

【讨论】:

有没有办法直接修改result对象/文档?【参考方案4】:

您可以使用集合引用来编写 Dan 的代码:

    function clean(collection)  
      collection.find().forEach( function(result) 
      var value = result.value;
      delete value._id;     
      collection.update(_id: result._id, value);     
      collection.update(_id: result.id, $unset: value: 1 )  );

【讨论】:

【参考方案5】:

类似于@ljonas 的方法,但不需要硬编码文档字段:

db.results.find().forEach( function(result) 
    var value = result.value;
    delete value._id;
    db.results.update(_id: result._id, value);
    db.results.update(_id: result.id, $unset: value: 1 )
 );

【讨论】:

【参考方案6】:

所有提出的解决方案都远非最优。到目前为止,您可以做到的最快速度是:

var flattenMRCollection=function(dbName,collectionName) 
    var collection=db.getSiblingDB(dbName)[collectionName];

    var i=0;
    var bulk=collection.initializeUnorderedBulkOp();
    collection.find( value:  $exists: true  ).addOption(16).forEach(function(result) 
        print((++i));
        //collection.update(_id: result._id,result.value);

        bulk.find(_id: result._id).replaceOne(result.value);

        if(i%1000==0)
        
            print("Executing bulk...");
            bulk.execute();
            bulk=collection.initializeUnorderedBulkOp();
        
    );
    bulk.execute();
;

然后调用它: flattenMRCollection("MyDB","MyMRCollection")

这比顺序更新要快得多。

【讨论】:

注意:这是 MongoDB 2.6 中的新功能【参考方案7】:

在尝试文森特的答案时,我发现了几个问题。基本上,如果您在 foreach 循环中执行更新,这会将文档移动到集合的末尾,并且光标将再次到达该文档(example)。如果使用$snapshot,则可以避免这种情况。因此,我在下面提供了一个 Java 示例。

final List<WriteModel<Document>> bulkUpdate = new ArrayList<>();

// You should enable $snapshot if performing updates within foreach
collection.find(new Document().append("$query", new Document()).append("$snapshot", true)).forEach(new Block<Document>() 
    @Override
    public void apply(final Document document) 
        // Note that I used incrementing long values for '_id'. Change to String if
        // you used string '_id's
        long docId = document.getLong("_id");
        Document subDoc = (Document)document.get("value");
        WriteModel<Document> m = new ReplaceOneModel<>(new Document().append("_id", docId), subDoc);
        bulkUpdate.add(m);

        // If you used non-incrementing '_id's, then you need to use a final object with a counter.
        if(docId % 1000 == 0 && !bulkUpdate.isEmpty()) 
            collection.bulkWrite(bulkUpdate);
            bulkUpdate.removeAll(bulkUpdate);
        
    
);
// Fixing bug related to Vincent's answer.
if(!bulkUpdate.isEmpty()) 
    collection.bulkWrite(bulkUpdate);
    bulkUpdate.removeAll(bulkUpdate);

注意:这个 sn-p 在我的机器上执行平均需要 7.4 秒,有 10 万条记录和 14 个属性(IMDB 数据集)。如果没有批处理,平均需要 25.2 秒。

【讨论】:

以上是关于在 MongoDB mapreduce 中,如何展平值对象?的主要内容,如果未能解决你的问题,请参考以下文章

在 MongoDB mapreduce 中,如何展平值对象?

在MongoDB mapreduce中,如何展平值对象?

用mongodb的mapreduce可以加两个条件吗

基于MongoDB分布式存储进行MapReduce并行查询

如何查询特定的 mongodb mapreduce 字段?

使用hadoop mapreduce分析mongodb数据: