mongoDB 에서 map/reduce 기능을 사용할 일이 있어 정리를 해 놓는다.
참고로 간단한 modify 는 forEach() 를 사용하자.
MapReduce()
MapReduce() 가 하는 일을 정리하면, map 을 통해 특정 id(id 를 여러개 지정할 수도 있다.) 로 data 를 묶고(grouping), reduce 를 통해 이 묶인 data 를 어떻게 처리할 지를 결정하는 일을 하게 된다.
- map : grouping 을 하고,
- reduce : grouping 한 값에 어떤 작업을 해서,
- 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)
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},
...]
이 예제를 말로 설명하면,
- _id 가 같은 것 끼리 묶고(map)
- 이 묶은 값중의 첫번째 값을 출력(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 를 반드시 넣어줘야 한다.
기타 참고 자료
- MongoDB: The Definitive Guide, map/reduce 예제
- MongoDB remove mapreduce collection
- Drop tmp collections in Mongodb 06 Jan 2011
- Mongo Db:The Next Big Thing?, Geeks Share Space, 2010년
Reference
- MongoDB: Combine data from multiple collections in to one..how?, StackOverflow
- http://docs.mongodb.org/manual/core/map-reduce/#MapReduce-Outputoptions
- QAing New Code with MMS: Map/Reduce vs. Aggregation Framework, OCTOBER 2, 2013, MongoDB blog
- Using MongoDB's map/reduce to “group by” two fields, StackOverflow
- Counting Unique Items with Map-Reduce, MongoDB Cookbook
- Reference > Database Commands > Aggregation Commands > mapReduce, MongoDB homepage
댓글 없음:
댓글 쓰기