'개발관련/MongoDB'에 해당되는 글 15건
- 2015.11.20 :: mongodb mapreduce(2)
- 2015.11.20 :: mongodb mapreduce(1)
- 2015.11.20 :: mongodb aggregation(2)
- 2015.11.20 :: mongodb aggregation(1)
- 2015.11.20 :: mongodb sharding 작업 및 확인
- 2015.11.19 :: mongodb cluster(4)
- 2015.11.19 :: mongo cluster(3)
- 2015.11.19 :: mongo cluster(2)
- 2015.11.19 :: mongoDB cluster (1)
- 2015.11.17 :: mongdb sort, paging, plan
먼저 collections를 확인해보자.
1 2 3 4 5 6 7 | show collections //result testReduce users | cs |
show collections를 하면 결과가 나온다. 앞서 만든 mapreduce에 사용한 testReduce와 데이타를 저장한 users가 존재한다.
그럼 system.js를 만들어보자. 앞서 만들어놓은 함수들을 어딘가 복사해놓자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | db.system.js.insert( [ { _id: "map", value : function () { var key = {}; key.eyeColor = this.eyeColor; key.gender = this.gender; var value = {}; value.age = this.age; value.count = 1; emit(key, value); } }, { _id: "reduce", value : function (key, values){ var returnVal = {}; var totalAge =0; var totalCount = 0; for( index in values){ totalAge += values[index].age; totalCount += values[index].count; } return {"age":totalAge , "count" : totalCount}; } }, { _id : "finalize", value : function(key, values){ return { "age" : values.age, "count" : values.count, "avg" : values.age/values.count }; } } ] ); | cs |
1 2 3 4 5 6 | system.indexes system.js testReduce users | cs |
system.js 라는 collections과 indexes라는넘이 추가되었다.
이제 mapReduce를 하기전에 마지막으로 위에 값들을 load 를 해야한다.
1 2 | db.loadServerScripts(); | cs |
위에 구문을 쓰면 몽고 디비의 서버에 맵리듀스 함수가 로드 된것이다.
그럼 map,reduce, finalize 라는 _id의 값은 어떻게 되있는지 확인하자
map; reduce; finalize; 라는 명령으로 확인하면 다음과 같은 결과를 확인 할 수 있다.
아래는 map; 의 함수를 보여주고있다.
1 2 3 4 5 6 7 8 9 10 | function __cf__13__f__anonymous_function() { var key = {}; key.eyeColor = this.eyeColor; key.gender = this.gender; var value = {}; value.age = this.age; value.count = 1; emit(key, value); } | cs |
이제 다시한번 mapreduce를 하는데 이번엔 out의 옵션을 좀 추가해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | db.users.mapReduce( map,reduce, { out : { reduce : "testReduce" }, //map들어갈 collection 필터링 할때 query : { "index" : { $gte : 1000 } }, //가령 추천수가 가장 많은 사람을 쓸때 오름 차순으로 하면 제일 먼저 나오는 사람이 젤위이므로 이 사람을 빨리 가져올때 사용. //sort : {}, //reduce 를 한 결과를 가공을 할때 사용. finalize : finalize, //scope는 map, reduce, finalize 함수의 전역 변수로 사용된다. //사용시는 $변수명으로 사용하면된다. //scope: { "temp" : 1}, //몽고디비는 bson 타입으로 저장을한다. 이때 속도가 좀 걸리는데 json 형태로 할것인지를 선택하는 옵션이다. //그러나 json의 경우 속도는 빠르나 collection document의 제한이 걸린다. 그래서 안쓰는게 상책. //jsMode: <boolean>, //verbose: <boolean> } ); | cs |
out의 옵션에 reduce를 주고 "testReduce" collection명을 주었다. 실행하면 결과는 testReduce로 저장될 것이다. 이제
db.testReduce.find()로 값을 확인하자.
아까 값에 reduce 된 값이 보일것이다.
후 여기까지.....
mapreduce는 map 과 reduce의 합성어 이다.
map은 특정 collection의 특정 key 와 value를 map으로 하며, map의 key는 중복이 안되며, value는 list에 쌓이는 형태로 된다.
reduce는 map의 데이터를 감소 시키는 역활을 한다. reduce를 통해 특정 값을 뽑아낸다.이를 통계치 데이터로 사용하게 된다.
1 2 3 | use dbname db.users.find(); | cs |
1 2 3 4 5 6 7 8 9 10 11 | { "_id" : "564c3240e5aa2a5f7c7bd7f5", "index" : 0, "isActive" : false, "age" : 30, "eyeColor" : "green", "name" : "Rodgers Grant", "gender" : "male", "company" : "VERTON", "email" : "rodgersgrant@verton.com" } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 | //make map var map = function () { //맵은 단순 key value로 선언을 하며 , 이 값들은 emit을 통해 reduce로 전달을 한다. //this는 현재 collection에 document가 저장된다. var key = {}; key.eyeColor = this.eyeColor; key.gender = this.gender; var value = {}; value.age = this.age; value.count = 1; emit(key, value); }; | cs |
어라 js랑 똑같다. 걍 js 함수다 . map이라는 함수에 key에는 eyeColor와 gender를 저장하고, value에는 age와 count를 저장한다. 여기서 눈여겨 볼 껀 this라는 넘이다. 이 this는 현재 사용하고 있는 collection을 뜻하며, 이 collection의 filed명을 쓰면 그 값들이 전달된다. 자 위에 map을 선언 하면 다음과 같은 비슷한 결과 형태로 될것이다. (머 내부적으로 어떻게 저장하는지는 몰라서 ... 추측하건데...=_=;)
1 | green.male : [{ 30,1},{36,1}............] | cs |
1 2 3 4 5 6 7 8 9 10 11 12 | var reduce = function (key, values){ var returnVal = {}; var totalAge =0; var totalCount = 0; for( index in values){ totalAge += values[index].age; totalCount += values[index].count; } //return type은 map의 기준의 value로 결정되므로 reduce의 return type의 value랑 똑같은 형태로 맞춰줘야한다.!! return {"age":totalAge , "count" : totalCount}; } | cs |
1 2 3 4 5 6 7 8 9 | var finalize = function(key, values){ return { "age" : values.age, "count" : values.count, "avg" : values.age/values.count }; } | cs |
finalize는 reduce의 마지막 나온 결과를 건네주게 된다. 여기서 마지막이란 trycatch의 finaliy 정도 생각 하면 되겠다. finalize는 결과를 마음 대로 수정해도 된다.
자 이제 mapreduce 함수를 호출 해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | db.users.mapReduce( map,reduce, { //없으면 현재 db에 만든다. /** * out: { <action>: <collectionName> [, db: <dbName>] [, sharded: <boolean> ] [, nonAtomic: <boolean> ] } <action> replace - 기존의 있는 콜렉션을 없애고 mapreduce결과를 집어 넣는다. merge - 이미 있는 콜렉션을 넣는데 현재 키가 존재할경우 오버라이트 한다. reduce - 같이 키로 결과가 있으면 그 결과들 끼리 reduce를 한다. db - output collection 을 쓸 db이름을 정의 default db는 mapreduce collection과 같은 db이다. sharded - 맵리듀스 결과 collection을 바로 샤딩하고 싶을때 true로 변경. nonAtomic - mapreduce 사용시 true로 사용하면 lock이 걸리지 않는다. (상황에 따라 써야겠구만.) **/ out : "testReduce", //map들어갈 collection 필터링 할때 query : { "index" : { $gte : 1000 } }, //가령 추천수가 가장 많은 사람을 쓸때 오름 차순으로 하면 제일 먼저 나오는 사람이 젤위이므로 이 사람을 빨리 가져올때 사용. //sort : {}, //reduce 를 한 결과를 가공을 할때 사용. finalize : finalize, //scope는 map, reduce, finalize 함수의 전역 변수로 사용된다. //사용시는 $변수명으로 사용하면된다. //scope: { "temp" : 1}, //몽고디비는 bson 타입으로 저장을한다. 이때 속도가 좀 걸리는데 json 형태로 할것인지를 선택하는 옵션이다. //그러나 json의 경우 속도는 빠르나 collection document의 제한이 걸린다. 그래서 안쓰는게 상책. //jsMode: <boolean>, //verbose: <boolean> } ); | cs |
파라미터는 총 3개다. map, reduce, 3번째는 object이다. 중요한것은 이 3번째 파라미터...
먼저 out 이라는 넘이 나온다. 단순히 값을 쓰면, 지정한 값으로 collection을 만들고 그 collection에 mapreduce 값을 쓴다. 이때 위에 주석으로 써놓았듯이 옵션에는 총 3가지가 있다. replace, merge, reduce 이 3개가 있는데 default는 replace 로 쓴다.
query라는 넘은 mapreduce를 할 collection의 값을 특정 결과로 만들고 난 후 mapreduce로 전달한다. 위에 예제는 users collection에 index가 1000 이상인 사람들에 대해 mapreduce를 한다는 예시이다.
finalize 는 위에 선언해놓은 finalize 함수의 변수를 쓰면 된다. 나머지 옵션은 주석으로 써놓았으니 살펴 보면된다.
이 mapreduce는 cursor라는 곳에 저장을 한다. 이 cursor는 휘발성이므로 계속적으로 사용 할때는 위와 같이 사용 해서는 안된다. 다음에는 이를 막고자 할때 사용하는 system.js 에 대해 알아본다.
자 이번엔 이전 쿼리에서 평균을 내어 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | db.users.aggregate([ //stage 별로 나뉘며 앞에서 처리 된걸 가지고 다음 stage로 전달 //stage 1 { $match : { "index" :{ $gte: 10000 } } }, //stage2 { $group:{ "_id" : "$eyeColor", "sum" : { $sum : 1 } } }, //stage3 { $group:{ "_id" : "$_id", "avg" : { $avg : "$sum" } } } ]); | cs |
어라.. 먼가 바뀌었다. 위에 $group의 값을 보면 "$eyeColor"가 존재한다. 이 말은 users의 field에 eyeColor 라는넘을 사용하고 싶으면 위의 예시 처럼 사용하면 된다. 마지막 $group에 또 "$sum"이라는 넘이 나온다. 이 "$sum"이 앞서 있던 stage의 key였던 sum이 $sum으로 변경된 것이다.
결론은 collection의 field 명 -> "$필드명" 이전 statge key 값 -> 다음 stage의 "$key" 로 사용 할 수 있다.
근데 결과가 이전에 봤던거랑 같다. 이유는 $avg의 평균을 내면 그 값이 나오므로 ... 그럼 진짜 평균을 내보자. "각 eyeColor의 전체나이와 전체 나이의 평균" 을 내어보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | db.users.aggregate([ //stage 별로 나뉘며 앞에서 처리 된걸 가지고 다음 stage로 전달 //stage 1 { $group : { "_id" : "$eyeColor" , "ageSum" : { $sum : "$age" }, "ageCount" : { $sum : 1 } } }, //stage2 { $project : { //"_id" : 1, "ageAVG" : { $divide : ["$ageSum" ,"$ageCount"] } } } ]); | cs |
또 새로운게 등장 $project 이넘은 없는 필드를 만들거나 기존 필드를 안보이고 싶을때 사용 한다고 공식사이트에 명시 되있다. 위의 예제를 보면 각 stage별로 값들을 전달한 것들을 알 수있다.
이걸로 aggregation은 끗....
aggregation은 '집합' 이란 뜻이다. 고로, 특정 데이터의 통계를 뽑을 때 많이 쓰인다. mapreduce는 통계의 추이를 뽑고 싶을 때 사용되며 aggregation은 양이 적고, 휘발성인 통계를 뽑을 때 사용하면 좋다.
사용법은 공식 싸이트 https://docs.mongodb.org/manual/reference/operator/aggregation/ 이넘을 참고하자. 또는 http://hongtaey.tistory.com/57 이 분 역시 나름 정리를 잘하신듯.. 보인다.
그럼 한번 해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | use dbname db.users.find(); //result /* 1 */ { "_id" : "564c3240e5aa2a5f7c7bd7f5", "index" : 0, "isActive" : false, "age" : 30, "eyeColor" : "green", "name" : "Rodgers Grant", "gender" : "male", "company" : "VERTON", "email" : "rodgersgrant@verton.com" } ........................생략........................ | cs |
1 2 3 4 5 6 7 8 9 | //aggregation db.users.aggregate( //stage 별로 나뉘며 앞에서 처리 된걸 가지고 다음 stage로 전달 [{ $match : { "eyeColor" :"green" } }] ); | cs |
aggregation이라는 함수로 내부에 array를 가지며 이를 다시 object 형태를 가진다. 여기서 중요한 것은 각 object의 순서 마다 stage 라 칭하며 , pipeline 형태로 다음 state로 전달하게 된다.
위의 결과는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | { "result" : [ { "_id" : "564d1acb42b552b7a4e93ff4", "index" : 19347, "isActive" : true, "age" : 30, "eyeColor" : "green", "name" : "Spencer Brady", "gender" : "male", "company" : "KIDSTOCK", "email" : "spencerbrady@kidstock.com" }, { "_id" : "564c3240e5aa2a5f7c7bd7f5", "index" : 0, "isActive" : false, "age" : 30, "eyeColor" : "green", "name" : "Rodgers Grant", "gender" : "male", "company" : "VERTON", "email" : "rodgersgrant@verton.com" } ............................생략.............................................. | cs |
와 잘나온다. 그리고 쉽다!!!!! 흠.. 그럼 stage라고 하는넘을 해보자.
이제 위의 결과를 가지고 응용하자. "눈이 녹색이고, 나이가 30보다 많은 사람"를 찾아보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | db.users.aggregate([ //stage 별로 나뉘며 앞에서 처리 된걸 가지고 다음 stage로 전달 //stage 1 { $match : { "eyeColor" :"green" } }, //stage 2 { $match :{ "age" : { $gt : 30 } } } ]); | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | "result" : [ { "_id" : "564d1acb42b552b7a4e93ff4", "index" : 19347, "isActive" : true, "age" : 30, "eyeColor" : "green", "name" : "Spencer Brady", "gender" : "male", "company" : "KIDSTOCK", "email" : "spencerbrady@kidstock.com" }, { "_id" : "564c3240e5aa2a5f7c7bd7f5", "index" : 0, "isActive" : false, "age" : 30, "eyeColor" : "green", "name" : "Rodgers Grant", "gender" : "male", "company" : "VERTON", "email" : "rodgersgrant@verton.com" }, ..........................생략.................................. | cs |
잘나온다... 흠. 근데 먼가 예제로서는 부족하다... stage라는 넘을 잘 모르겠다.
쿼리를 좀 바꿔보자. $match 말고 $group도 사용해보자. "index가 10000 이상이며, eyeColor 별 사람은 몇명인지"를 찾아보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | db.users.aggregate([ //stage 별로 나뉘며 앞에서 처리 된걸 가지고 다음 stage로 전달 //stage 1 { $match : { "index" :{ $gte: 10000 } } }, { $group:{ "_id" : "$eyeColor", "sum" : { $sum : 1 } } } ]); | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* 1 */ { "result" : [ { "_id" : "brown", "sum" : 3296.0000000000000000 }, { "_id" : "blue", "sum" : 3362.0000000000000000 }, { "_id" : "green", "sum" : 3342.0000000000000000 } ], "ok" : 1.0000000000000000, "$gleStats" : { "lastOpTime" : Timestamp(1448006114, 21), "electionId" : ObjectId("564d825928cf905edab537fb") } } | cs |
저번 글까지는 단순히 cluster 환경 구성을 하는 것이었다.. 이제 데이터를 넣어보자.
먼저 필자는 다음과 같은 json 구조로 데이터를 10000건을 만들었다.
1 2 3 4 5 6 7 8 9 10 11 | { "_id" : "564c3240e5aa2a5f7c7bd7f5", "index" : 0, "isActive" : false, "age" : 30, "eyeColor" : "green", "name" : "Rodgers Grant", "gender" : "male", "company" : "VERTON", "email" : "rodgersgrant@verton.com" } | cs |
linux 커널에서 다음과 같은 명령으로 데이터를 넣는다.
1 2 | ./bin/mongoimport -h 192.168.0.105:45005 -d dbname -c collectionName --file ${path}/users.json | cs |
그런후... 샤드의 상태를 확인해보자.
1 2 | sh.status(); | cs |
아마도, 1개의 샤드에만 들어있을 것이다.
분명히 2개의 샤드에 분리 되야 하는데.... =_=;
그럼 chunk의 size를 확인한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | use config db.settings.find(); //result { "_id" : "chunksize", "value" : 64.0000000000000000 } /* 2 */ { "_id" : "balancer", "stopped" : false } | cs |
use config 이 넘은 cluster server의 내부 db 이다. cluster만 존재하며, 설정 정보들이 들어있다.
db.settings.fing()를 하면 위의 결과 처럼 나올 것이다. 기본 mongodb의 chunk size는 64M 인것이다. 10000건의 데이터는 3M도 안된다. =_=; 그러므로 샤딩은 당연히 안될것이다. 그럼 한번 줄여보자.
1 | db.settings.save( { _id:"chunksize", value: 1 } ); | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | --- Sharding Status --- sharding version: { "_id" : 1, "minCompatibleVersion" : 5, "currentVersion" : 6, "clusterId" : ObjectId("564aff51f77bed76dca6ba70") } shards: { "_id" : "elastic", "host" : "elastic/192.168.0.105:45001,192.168.0.105:45002,192.168.0.105:45003" } { "_id" : "shard0000", "host" : "192.168.0.105:55001" } databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "latis", "partitioned" : true, "primary" : "elastic" } latis.users shard key: { "index" : 1 } chunks: shard0000 4 elastic 5 { "index" : { $minKey : 1 } } -->> { "index" : 1 } on : shard0000 Timestamp(5000, 1) { "index" : 1 } -->> { "index" : 4369 } on : shard0000 Timestamp(3000, 0) { "index" : 4369 } -->> { "index" : 6554 } on : elastic Timestamp(4000, 1) { "index" : 6554 } -->> { "index" : 8739 } on : elastic Timestamp(1000, 4) { "index" : 8739 } -->> { "index" : 10923 } on : elastic Timestamp(3000, 2) { "index" : 10923 } -->> { "index" : 14000 } on : elastic Timestamp(3000, 3) { "index" : 14000 } -->> { "index" : 16184 } on : shard0000 Timestamp(4000, 2) { "index" : 16184 } -->> { "index" : 19000 } on : shard0000 Timestamp(4000, 3) { "index" : 19000 } -->> { "index" : { $maxKey : 1 } } on : elastic Timestamp(5000, 0) { "_id" : "users", "partitioned" : true, "primary" : "elastic" } { "_id" : "db", "partitioned" : false, "primary" : "elastic" } { "_id" : "test", "partitioned" : false, "primary" : "elastic" } | cs |
오오오오... 잘된다. 다음은 aggregation 및 mapreduce로....
mongodb의 cluster의 최종편.... 후 힘드네....
mongos 이름하여 mongo cluster server 라 한다. 거두 절미 하고 셋팅 파일 부터 보자..
머 기존꺼랑 비슷하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #yml 파일이 존재 하지 않으면 /var/local/mongdb/data 에 data를 저장. (경로도 마찬가지로 없으면 default 경로에 저장.) #log역시 설정 정보가 없으면 /var/local/mongdb/logs/log.log에 저장. #net - network 설정 #하위 구조로 bindIp와 port를 설정한다. #bindIP는 mongoDB 자신의 ip를 설정. #port는 mongDB 의 port를 설정. net: bindIp: "192.168.0.105" port: 45005 #processManagement - 실행 옵션. #fork 는 백그라운드 옵션. processManagement: fork: true #storage - data 저장 경로를 설정. #dbpath - data를 저장할 실제 경로. #journal - data를 쓰기 전 , data write query를 파일에 저장하는 옵션. (일종의 검증, 복구를 위한 작업인듯...) # enabled - journal 을 쓸지 말지의 설정. #storage: # dbPath: /home/solrslave/src/mongodb-linux-x86_64-rhel62-3.0.4/cluster # journal: # enabled: true #destination: file, syslog 2가지 옵션 사용 가능 #file로 선언하면 path 옵션을 필수로 넣어서 경로를 직접 설정 #syslog로 선언하면 경로 설정 없이 디폴트 로그파일에 저장 # #verbosity: 로그 기록 레벨 # default : 0 # 0 ~ 5 숫자 값으로 레벨 구분 # 0 : all # 1 : debug, # 2: information # 3: warning # 4: errorm # 5: fatal systemLog: verbosity: 1 destination: file logAppend: true path: /home/solrslave/src/mongodb-linux-x86_64-rhel62-3.0.4/cluster_logs/cluster_log.log sharding: configDB: "192.168.0.105:45004" chunkSize: "64" | cs |
이것도 마지막 부분만 틀리다 sharding 이라는 설정 값이 추가 됬다 . 일단 옵션은 보통 한개면 될듯 보인다. configDB라는 옵션만.....
configDB는 앞서 설명한 config server의 아이피와 포트가 온다. 필자는 한개만 썻지만 여러개 썻다면 "xxx.xxx.xxx.xxx:12345, xxx.xxx.xxx.xxx:23456" 요렇게 하면된다. 그리고 두번째 chunkSize라는 넘이 있다. default는 64이다. mongodb의 chunk 는 데이터의 크기의 묶음(?) 정도가 되겠다.
이 말은 위에 구성은 여러개의 shard는 대략 저 크기로 데이터를 나눈다. 64M 이하인 data는 특정 shard 한군데를 정해서 데이터를 쌓을 것이다.
실행은 mongd가 아닌 mongos로 실행한다. 실행후 process를 확인해보자.
1 2 3 4 | 500 17431 1 0 Nov19 ? 00:01:45 ./bin/mongos -f cluster.yml | cs |
자 이제 모든 환경 셋팅은 끝났다. 근데 이상한게 보일 것이다. replicaset과 연동은????
이제 mongos 인 cluster server에 접속하자. 접속은 기존과 동일하게 ip와 port만 cluster server로 하면된다.
1 2 3 4 5 6 7 | sh.enableSharding("testDB");//없으면 만든다. sh.addShard("192.168.0.105:45001"); sh.status(); | cs |
접속후 sharding과 관련된 명령어인 sh 로 먼저 어떤 db를 sharding을 할지 설정한다.
addShard 명령어로 셋팅을 한다. 재미난 점은 위 예시는 primary 로 잡혀있는 서버를 했지만 secondary로 해도 된다.
그럼 제대로 shard를 하기 위해서 독립서버 55001을 한개 띄우고
sh.addShard("192.168.0.105:55001");
추가한다. 마지막으로 sh.status()로 상태 정보 를 확인해보자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | --- Sharding Status --- sharding version: { "_id" : 1, "minCompatibleVersion" : 5, "currentVersion" : 6, "clusterId" : ObjectId("564aff51f77bed76dca6ba70") } shards: { "_id" : "elastic", "host" : "elastic/192.168.0.105:45001,192.168.0.105:45002,192.168.0.105:45003" } { "_id" : "shard0000", "host" : "192.168.0.105:55001" } databases: { "_id" : "admin", "partitioned" : false, "primary" : "config" } { "_id" : "latis", "partitioned" : true, "primary" : "elastic" } latis.users shard key: { "index" : 1 } chunks: shard0000 4 elastic 5 { "index" : { $minKey : 1 } } -->> { "index" : 1 } on : shard0000 Timestamp(5000, 1) { "index" : 1 } -->> { "index" : 4369 } on : shard0000 Timestamp(3000, 0) { "index" : 4369 } -->> { "index" : 6554 } on : elastic Timestamp(4000, 1) { "index" : 6554 } -->> { "index" : 8739 } on : elastic Timestamp(1000, 4) { "index" : 8739 } -->> { "index" : 10923 } on : elastic Timestamp(3000, 2) { "index" : 10923 } -->> { "index" : 14000 } on : elastic Timestamp(3000, 3) { "index" : 14000 } -->> { "index" : 16184 } on : shard0000 Timestamp(4000, 2) { "index" : 16184 } -->> { "index" : 19000 } on : shard0000 Timestamp(4000, 3) { "index" : 19000 } -->> { "index" : { $maxKey : 1 } } on : elastic Timestamp(5000, 0) { "_id" : "users", "partitioned" : true, "primary" : "elastic" } { "_id" : "db", "partitioned" : false, "primary" : "elastic" } { "_id" : "test", "partitioned" : false, "primary" : "elastic" } | cs |
필자는 현재 data를 넣어봐서 위와 같은 그림이 보인다.
중요한 것은 위에 shards이다. 추가한 아이피와 포트가 다 생성 되있다...
이상으로 cluster는 완성 되었다.
다음은 cloud의 꽃(?)이라 불리는 aggregation과 mapreduce를 ......
앞서 보여준 그림을 보시면 오른쪽 위가 config server 되있을 것이다. 이는 shard에 대한 모든 쿼리(?) 들을 저장하며, 이를 가지고 백업 등에 사용 하게 된다. 그림의 가운데 위 mongos 라 써있는 서버 보다 먼저 셋팅 해야하는 이유는 mongos 에 config server의 설정을 써야 하므로 config server 를 먼저 셋팅 해야한다.
설정 정보는 기존 yml 파일과 거의 비슷하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #yml 파일이 존재 하지 않으면 /var/local/mongdb/data 에 data를 저장. (경로도 마찬가지로 없으면 default 경로에 저장.) #log역시 설정 정보가 없으면 /var/local/mongdb/logs/log.log에 저장. #net - network 설정 #하위 구조로 bindIp와 port를 설정한다. #bindIP는 mongoDB 자신의 ip를 설정. #port는 mongDB 의 port를 설정. net: bindIp: "192.168.0.105" port: 45004 #processManagement - 실행 옵션. #fork 는 백그라운드 옵션. processManagement: fork: true #storage - data 저장 경로를 설정. #dbpath - data를 저장할 실제 경로. #journal - data를 쓰기 전 , data write query를 파일에 저장하는 옵션. (일종의 검증, 복구를 위한 작업인듯...) # enabled - journal 을 쓸지 말지의 설정. storage: dbPath: /home/solrslave/src/mongodb-linux-x86_64-rhel62-3.0.4/shard journal: enabled: true #destination: file, syslog 2가지 옵션 사용 가능 #file로 선언하면 path 옵션을 필수로 넣어서 경로를 직접 설정 #syslog로 선언하면 경로 설정 없이 디폴트 로그파일에 저장 # #verbosity: 로그 기록 레벨 # default : 0 # 0 ~ 5 숫자 값으로 레벨 구분 # 0 : all # 1 : debug, # 2: information # 3: warning # 4: errorm # 5: fatal systemLog: verbosity: 1 destination: file logAppend: true path: /home/solrslave/src/mongodb-linux-x86_64-rhel62-3.0.4/shard_logs/log.log #replication: # replSetName: elastic sharding: clusterRole: configsvr | cs |
기존 replica set 과 다른점은 replication을 안쓰고 sharding이라는 설정이 추가 됐다. 그리고 clusterRole이라는 항목에 configsvr 이라는 configserver의 약자를 써주면 된다.
공식 사이트에선 이 서버를 여러대 설치 하라고 권장한다. 이유는 다른 정책 서버가 장애가 났을 시 다른 서버에서 값을 가져올 수 있으므로 권장한다. 머 다른이유는 없는거 같다.
서버는 replicaset 과 동일하게 실행하면 된다.
실행 후 process는 다음과 같은 형태가 될 것이다.
1 2 3 4 5 | 500 12235 1 0 Nov17 ? 00:07:42 ./bin/mongod -f startup2.yml 500 12251 1 0 Nov17 ? 00:07:05 ./bin/mongod -f startup3.yml 500 12267 1 0 Nov17 ? 00:07:03 ./bin/mongod -f startup4.yml 500 12298 1 0 Nov17 ? 00:10:28 ./bin/mongod -f shard_config.yml | cs |
아래 shard_config.yml이 정책 서버가 된다. 그럼 다음에는 mongos에 대해 알아본다.
이번에는 replicaset에 대해 설정해보자. 먼저 단일 서버를 셋팅한 yml 파일을 복사후 replication name을 셋팅하면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #yml 파일이 존재 하지 않으면 /var/local/mongdb/data 에 data를 저장. (경로도 마찬가지로 없으면 default 경로에 저장.) #log역시 설정 정보가 없으면 /var/local/mongdb/logs/log.log에 저장. #net - network 설정 #하위 구조로 bindIp와 port를 설정한다. #bindIP는 mongoDB 자신의 ip를 설정. #port는 mongDB 의 port를 설정. net: bindIp: "192.168.0.105" port: 45001 #processManagement - 실행 옵션. #fork 는 백그라운드 옵션. processManagement: fork: true #storage - data 저장 경로를 설정. #dbpath - data를 저장할 실제 경로. #journal - data를 쓰기 전 , data write query를 파일에 저장하는 옵션. (일종의 검증, 복구를 위한 작업인듯...) # enabled - journal 을 쓸지 말지의 설정. storage: dbPath: /home/solrslave/src/mongodb-linux-x86_64-rhel62-3.0.4/data2 journal: enabled: true #destination: file, syslog 2가지 옵션 사용 가능 #file로 선언하면 path 옵션을 필수로 넣어서 경로를 직접 설정 #syslog로 선언하면 경로 설정 없이 디폴트 로그파일에 저장 # #verbosity: 로그 기록 레벨 # default : 0 # 0 ~ 5 숫자 값으로 레벨 구분 # 0 : all # 1 : debug, # 2: information # 3: warning # 4: errorm # 5: fatal systemLog: verbosity: 1 destination: file logAppend: true path: /home/solrslave/src/mongodb-linux-x86_64-rhel62-3.0.4/logs2/repl_log1.log replication: replSetName: elastic | cs |
1 2 3 | 500 12235 1 0 Nov17 ? 00:07:42 ./bin/mongod -f startup2.yml 500 12251 1 0 Nov17 ? 00:07:05 ./bin/mongod -f startup3.yml 500 12267 1 0 Nov17 ? 00:07:03 ./bin/mongod -f startup4.yml | cs |
그럼 위에 서버들이 replicaset으로 제대로 셋팅이 되었는지 확인해보자.
아무 몽고 서버에 먼저 접속을 한다. (robomngo tool 추천 또는 ./bin/mongo 아이피:포트 로 접속 하면된다.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | use dbaname //사용할 db로 이동. show dbs //모든 db의 이름 확인 show collections //모든 collection 이름 정보 확인 rs.status(); /* 1 */ { "set" : "elastic", "date" : ISODate("2015-11-20T09:44:41.517Z"), "myState" : 1, "members" : [ { "_id" : 1, "name" : "192.168.0.105:45001", "health" : 1.0000000000000000, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 258290, "optime" : Timestamp(1448006114, 22), "optimeDate" : ISODate("2015-11-20T07:55:14.000Z"), "electionTime" : Timestamp(1447920217, 1), "electionDate" : ISODate("2015-11-19T08:03:37.000Z"), "configVersion" : 1, "self" : true }, { "_id" : 2, "name" : "192.168.0.105:45002", "health" : 1.0000000000000000, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 92465, "optime" : Timestamp(1448006114, 22), "optimeDate" : ISODate("2015-11-20T07:55:14.000Z"), "lastHeartbeat" : ISODate("2015-11-20T09:44:40.779Z"), "lastHeartbeatRecv" : ISODate("2015-11-20T09:44:40.805Z"), "pingMs" : 0, "syncingTo" : "192.168.0.105:45001", "configVersion" : 1 }, { "_id" : 3, "name" : "192.168.0.105:45003", "health" : 1.0000000000000000, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 92465, "optime" : Timestamp(1448006114, 22), "optimeDate" : ISODate("2015-11-20T07:55:14.000Z"), "lastHeartbeat" : ISODate("2015-11-20T09:44:40.808Z"), "lastHeartbeatRecv" : ISODate("2015-11-20T09:44:40.783Z"), "pingMs" : 0, "syncingTo" : "192.168.0.105:45001", "configVersion" : 1 } ], "ok" : 1.0000000000000000, "$gleStats" : { "lastOpTime" : Timestamp(0, 0), "electionId" : ObjectId("564d825928cf905edab537fb") } } | cs |
mongodb의 replication 정보는 rs 명령어로 한다. rs.status() 함수를 하면 위 정보와 같이 나온다. 여기서 누가 primary이고 secondary인지 알 수있다. 기본적으로 모든 cloud는 대장(?)을 선출하는데 mongodb는 primary라 칭한다. 설정 정보를 보고싶으면, rs.config() 를 하면된다.
다음은 shard config server에대해 알아보겠다.
몽고 db는 아래 그림과 같은 모양으로 구성된다.
(출처는 http://mobicon.tistory.com/307 이분 블로그는 볼게 많아요.)
설명을 하자면 아마도 클라우드 환경을 지원하는 대부분의 것들이 mongodb와 유사할 것이다. (solr와 es를 좀 틀리다... =-=;;;)
먼저 cloud는 두가지 말들이 많이 나온다. shard(파편)와 replica(복제) 이 두말이 가장 먼저 나온다. 몽고 db의 shard는 위에 그림과 같이 몽고db의 물리적 서버를 여러개 합쳐놓은 논리적 단위이다. 그럼 전체 데이터를 봤을때 샤드라고 하는 논리적 단위는 일부 만을 가지게 되고 이들이 합쳐져서 전체 데이타를 이룬다.
물리적 서버는 다시 노드 또는 데이터 서버 라고 칭하며, 저 노드들간의 여러개를 replicaset 이라는 이름으로 다시 집합을 이룬다. 결론은 replicaset은 흔히 말하는 replica가 된다. replica는 들어온 데이타들을 상호간에 복제를 하게 된다.
그럼 mongos라는 넘을 무얼까? 이넘은 클러스터 서버라는 넘이다. 유저가 쿼리를 전송을 하면 클러스터 서버는 데이터를 저장하지 않고 일종의 queue역활을 해준다. 어느 데이터를 누구한테 전할지 이러한 역활을 한다. 오른쪽 위에 보면 config servers 라는 넘이 있는데 이넘은 모든 쿼리를 저장하며 백업등을 관리한다. 이 역시 장애가 발생할 수 있으므로 여러개를 권장한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //sort //asc db.crudtest.find( { } ).sort( { "name" : 1 } ); //desc db.crudtest.find( { } ).sort( { "name" : -1 } ); | cs |
sort는 sort라는 내부함수로 쓰며 1, -1로 오름 차순과 내림차순을 정한다.
다음은 다량의 데이터를 넣는 bulk 또는 fetch 또는 batch 다. (용어는 =_=; 쓰기나름. )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | db.users.insert( [ { "_id": "56497019adbf20c07138d982", "index": 0, "isActive": false, "age": 24, "eyeColor": "blue", "name": "Moon Robles", "gender": "male", "company": "MANTRIX", "email": "moonrobles@mantrix.com" }, { "_id": "56497019be3cc168feb7b889", "index": 1, "isActive": true, "age": 32, "eyeColor": "blue", "name": "Annabelle Bass", "gender": "female", "company": "FOSSIEL", "email": "annabellebass@fossiel.com" }, ////////////////////생략///////////////////////////////// { "_id": "564970199f18e80dce7f2fed", "index": 99, "isActive": false, "age": 32, "eyeColor": "brown", "name": "Richardson Hansen", "gender": "male", "company": "AQUASURE", "email": "richardsonhansen@aquasure.com" } ] ); | cs |
다음은 paging 기법.
1 2 3 | /////////////////////paging////////////////////////////// db.users.find().limit(10); db.users.find().skip(10).limit(10); | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //////////////////////index////////////////////////////////// db.users.ensureIndex( { "index" : 1 } ); db.users.ensureIndex( { "email" : 1 } ); //compound db.users.ensureIndex( { "email" : 1, "index" : 1 } ); | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | ///////////////////plan//////////////////////// db.users.find( { "index":{ $lte : 50 } } ).explain(); db.users.find( { $and :[ { "index" : { $lte :50 } }, { "name": "Joseph Ferguson" }, { "email" : "josephferguson@exiand.com" } ] } ).explain(); | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | /* 1 */ { "queryPlanner" : { "plannerVersion" : 1, "namespace" : "test2.users", //어느 db의 어느 collection 인지. "indexFilterSet" : false, "parsedQuery" : { //실행된 쿼리 "index" : { "$lte" : 50.0000000000000000 } }, "winningPlan" : { "stage" : "FETCH", "inputStage" : { //index를 탔을때 쿼리가 실행되는 단계를 나타냄. "stage" : "IXSCAN", //인덱스를 통해서 검색. /** * COLLSCAN for a collection scan IXSCAN for scanning index keys FETCH for retrieving documents SHARD_MERGE for merging results from shards */ "keyPattern" : { "index" : 1.0000000000000000 }, "indexName" : "index_1", "isMultiKey" : false, //compound 키 사용 여부. "direction" : "forward", "indexBounds" : { "index" : [ "[-inf.0, 50.0]" ] } } }, "rejectedPlans" : []//인텍스가 여러개일 경우 색인하지 않을 인덱스 값의 이름이 들어있다. }, "executionStats" : { "executionSuccess" : true, "nReturned" : 51,//document 개수 "executionTimeMillis" : 0,//실행 시간 "totalKeysExamined" : 51, //검색에 사용한 index를 통해서 개수. "totalDocsExamined" : 51, //collection에서 검색한 개수. "executionStages" : { "stage" : "FETCH", "nReturned" : 51, // return 된 document 수. "executionTimeMillisEstimate" : 0, "works" : 52, "advanced" : 51, "needTime" : 0, "needFetch" : 0, "saveState" : 0, "restoreState" : 0, "isEOF" : 1, "invalidates" : 0, "docsExamined" : 51, "alreadyHasObj" : 0, "inputStage" : { "stage" : "IXSCAN", "nReturned" : 51, "executionTimeMillisEstimate" : 0, "works" : 52, "advanced" : 51, "needTime" : 0, "needFetch" : 0, "saveState" : 0, "restoreState" : 0, "isEOF" : 1, "invalidates" : 0, "keyPattern" : { "index" : 1.0000000000000000 }, "indexName" : "index_1", "isMultiKey" : false, "direction" : "forward", "indexBounds" : { "index" : [ "[-inf.0, 50.0]" ] }, "keysExamined" : 51, "dupsTested" : 0, "dupsDropped" : 0, "seenInvalidated" : 0, "matchTested" : 0 } }, "allPlansExecution" : [] }, "serverInfo" : { "host" : "localhost", "port" : 35001, "version" : "3.0.4", "gitVersion" : "0481c958daeb2969800511e7475dc66986fa9ed5" } } | cs |
위 결과는 첫번째 예제를 실행 할때 나오는 결과 인데.. 중요한 key들이 몇개 보인다.
state 경우 값이 COLLSCAN 인경우는 index를 타지 않았을 경우 값이 찍히고, 탔을 경우 IXSCAN 요런 값이 찍히게 된다.
또 위 두번째 예제를 실행하면 rejectedPlans 에 key에 단일 색인 키들이 들어 갈것이다. 위 주석을 보고 참고 하면 된다.