[컴][DB] MongoDB 의 map reduce 사용법

map reduce / 맵 리듀스 / 몽고 db / mongodb / 몽고db / map reduce 사용법 /



mongoDB 에서 map/reduce 기능을 사용할 일이 있어 정리를 해 놓는다.
참고로 간단한 modify 는 forEach() 를 사용하자.

MapReduce()


MapReduce() 가 하는 일을 정리하면, map 을 통해 특정 id(id 를 여러개 지정할 수도 있다.) 로 data 를 묶고(grouping), reduce 를 통해 이 묶인 data 를 어떻게 처리할 지를 결정하는 일을 하게 된다.
  1. map : grouping 을 하고,
  2. reduce : grouping 한 값에 어떤 작업을 해서,
  3. out : 결과로 보여준다.

Map/Reduce command


// mapReduce()
db.agent_5m.mapReduce(map, reduce, {"out": {"replace": "test_coll"}});

// runCommand()
db.runCommand({"mapreduce" : "foo", "map" : map, "reduce" : reduce, "out" : "test_coll"});


위의 2가지 방법으로 가능하다. 참고로, 위의 map 과 reduce 는 function 이다.




map

map 은 {key:value, key:value ...} 같이 key/value 의 짝으로 이뤄진 자료구조(data structure) 이다. 이런 map 을 만드는 과정이라고 보면 된다.
  • emit(key, value)
emit() 이라는 함수를 통해 key 를 정하고 value 를 정할 수 있다. 여기서 key 나 value 는 그냥 primitive type 이 될 수도 있고, dictionary 가 될 수도 있다.



map 의 item

item 한개는 아래와 같은 형식을 띠게 된다.
{key : [{ k : v, k2 : v2, ...}, { j : v, j2 : v2, ...}, ...]}
{key : [1,2,3,...]}


reduce

map 을 통해 만들어진 data 는 하나의 key 에 한개 이상의 값이 들어가 있게 된다. 그렇기 때문에 이 부분을 처리하게 된다.(?)

reduce 는 이 map 에서 만들어진 item 한개, 그러니까 (key, value) 값을 한개 받아서 value 에 대한 처리를 하는 부분이다.

만약 key 에 해당하는 값이 하나뿐이라면, reduce() 를 실행하지 않는다.(이건 map 할 때도 마찬가지이다. grouping 을 할 때 대상이 한 개 뿐이라면, grouping 을 하지 않는 듯 하다.)


map08 = function() {
    var value = {
        bytes : this.bytes,
        product_id : this.product_id
    };
    var key = { 
        product_id : this.product_id,
        cdn_svc_id : this.cdn_svc_id,
        domain_seq : this.domain_seq,
        path_id : this.path_id,
        datetime : this.datetime,
    }
    emit(key, value);
};
reduce = function(k, values) {
    var total = 0;
    var productId = 0;
    for(i in values){
        total += values[i].bytes;
        var pid = values[i].product_id;
        if(pid != 0 ){
            productId = pid;
        }
    }
    return {total : total, product_id : productId};
};


db.agent_5m.mapReduce(map08, reduce, {"out": {"replace": "namh_test_coll"}});


/* 24 */
{
    "_id" : {
        "product_id" : 3,
        "cdn_svc_id" : 44,
        "domain_seq" : 838,
        "path_id" : 927,
        "datetime" : "201402111355"
    },
    "value" : {
        "bytes" : NumberLong(1589896),
        "product_id" : 3
    }
}

/* 25 */
{
    "_id" : {
        "product_id" : 3,
        "cdn_svc_id" : 44,
        "domain_seq" : 838,
        "path_id" : 927,
        "datetime" : "201402111400"
    },
    "value" : {
        "total" : 32195384,
        "product_id" : 3
    }
}



out options 

1.7.x 버전 이후부터는 필수 parameter 가 된 듯 하다. 밑에 temporary collection 에 대한 설명을 참고하자.

mapReduce 를 만약에 주기적으로 실행하게 된다면, 또는 여러 collection 에 같은 mapReduce 를 적용해야 한다면 결과값을 같은 collection 에 결과를 넣어야 할지도 모르고, 아니면, mapReduce 를 실행할 때 마다 새로운 Collection 을 만들어야 될지도 모른다. 이것에 대한 것은 out 의 option 부분을 통해 정해 줄 수 있다. 이 option 을 action 이라고 부르고 있다.

out option 의 조절에 따라 결과로 나오는 collection 이 어떻게 될 지 결정된다.

  • replace : 기존의 결과를 버리고 새롭게 collection 을 만들어준다.
  • merge : 새롭게 map-reduce 해서 나온 결과를 collection 이 이미 존재하고 있는 녀석과 합쳐준다.(merge) 이 때 key 가 중복되는 경우에는 새롭게 나온 값으로 overwrite 한다.
  • reduce : 이것도 merge 처럼 합쳐준다. 그런데 key 가 충돌하는 경우에 overwrite 를 하지 않고, 양쪽 값(document)에 대해서 reduce function 을 수행하고, 그 결과를 overwrite 한다.

    만약 2개 이상의 collection 을 join(?) 하려고 한다면 이 option 을 사용해야 할 것이다.

    이것은 아마도

    reduce(key, [new_value, old_value]) 

    이런식으로 인자가 넘어갈 것이다.(?)


query

그런데 언제나 collection 전체를 map-reduce 하는 것은 시간이 많이 걸리는 작업이다. 그래서 collection 의 원하는 부분만 할 수 있도록 query 를 제공한다. query 를 통해 collection 내에서 일정 부분만 가져오고 그 녀석을 map-reduce 할 수 있는 것이다.



Double type

참고로 map-reduce 를 통해 나온 녀석들의 숫자들이 double 로 표현되는데, 그 이유는 javascript 로 작업이 이뤄지기 때문이다. 아마 javascript 에서 type 을 지정해 줄 수 있다면, 원하는 type 으로 만들어 낼 수도 있을 듯 하다. 아래에 관련한 이야기가 있다.





Example


map08 = function() {
    var value = {
        region_id: this.region_id,
        zone_id: this.zone_id,
        usvc_id: this.usvc_id
    };
    emit(this._id, value);
};
reduce = function(k, values) {
   
    return values[0];
};


db.t_vm_net_mon_mi_20131108.mapReduce(map08, reduce, {"out": {"reduce": "namh_test_coll"}});
db.t_vm_net_mon_mi_20131109.mapReduce(map08, reduce, {"out": {"reduce": "namh_test_coll"}});
db.namh_test_coll.find()

reduce 에 인자로 오는 values 의 모양은 아래와 같은 모습이 될 것이다.

values = [{region_id: 200,  zone_id: 300,   usvc_id: 400},
               {region_id: 201,  zone_id: 301,   usvc_id: 401},
               ...]


이 예제를 말로 설명하면,

  1. _id 가 같은 것 끼리 묶고(map)
  2. 이 묶은 값중의 첫번째 값을 출력(reduce)

하는 것이 될 것이다.



Map 에서 2 개 이상의 key 를 이용하는 방법

ref. 4 에서 ref. 5 를 알려줬다. 이 곳에 가면 예제를 확인할 수 있다. 2개 이상의 key 를 사용하기 위해 key 부분에 map({...}) 을 사용하고 있다.



Map Reduce vs Aggregation

ref. 2 의 Note 와 ref. 3 을 참고하면, 대체로 Aggregation 이 낫다고 하는 듯 하다.



Map Reduce 와 thread-safe

thread safe 한지는 모르지만, 일단 임시로 이름을 만들고, 실제 out 에 설정된 이름으로 rename 하는 구조라고 한다. 그렇게 temporary collection 이 만들어졌지만 누군가 이 collection 의 이름을 바꿀 수 있다. 자세한 이야기는 아래 글을 참고하자.




Map/Reduce Temporary Collection, tmp.mr

이 temporary collection 이 여전히 지원되는 줄 알고 조사를 해봤는데, 이제는 지원하지 않는다고 한다.

이전버전에서 제공했었는데, 원래 Map/Reduce 에서 만든 temporary collection 은 connection 이 close 되면 자동으로 지워진다고 한다. 그런데, shard 된 mongoDB 에서 자동으로 지우는데에 문제가 있었다고 한다. 여하튼 MongoDB 1.7.x 부터는 이 temporary collection 이 없어졌다고 한다. 그래서 map/reduce 를 할 때 out parameter 를 반드시 넣어줘야 한다.

기타 참고 자료





Reference

  1. MongoDB: Combine data from multiple collections in to one..how?, StackOverflow
  2. http://docs.mongodb.org/manual/core/map-reduce/#MapReduce-Outputoptions
  3. QAing New Code with MMS: Map/Reduce vs. Aggregation Framework,  OCTOBER 2, 2013, MongoDB blog
  4. Using MongoDB's map/reduce to “group by” two fields, StackOverflow
  5. Counting Unique Items with Map-Reduce, MongoDB Cookbook
  6. Reference > Database Commands > Aggregation Commands > mapReduce, MongoDB homepage


댓글 없음:

댓글 쓰기