MongoDB ReplicaSet 세팅

ReplicaSet을 간략히 설명하자면, 

디비 서버의 장애 발생을 대비해 여러 대의 서버에 원본을 복사해 두고,

장애가 발생하면, 자동으로 사본 디비 중 하나를 선택하여 원본 디비처럼 사용할 수 있도록 한다.

즉, Auto failover를 위한 구성이다.


최소한의 디비 구성

1개의 Primary DB + 1개의 Secondary DB + 1개의 Arbiter DB(Secondary DB)로 구성한다.

Primary DB

원본 데이터가 저장되는 디비이며 Read & Write 요청을 처리한다.

Secondary DB

원본 데이터의 사본을 저장하는 디비이며, Read 요청을 처리한다.

장애가 발생하면, 투표를 하여 Secondary DB 중 하나를 선택하여 Primary DB로 승격시킨다.

Arbiter DB

장애 발생시 투표에만 참여한다.

리소스를 많이 먹지 않기 때문에, 전용 서버를 따로 둘 필요는 없다.


구성해보기

Primary DB와 Secondary DB 구성

둘 다 동일하다.

테스트 용으로 서버 1대에서 실행할 때에는 logpatth, dbpath, port, pidfilepath를 다르게 지정해야 한다.

ReplicaSet의 이름은 first로 지정해 보자.

Arbiter DB 구성

리소스를 최소한으로 설정하기 위해 smallfiles=true, nojournal=true, noprealloc=true, oplogSize=1로 설정한다. 

설정했을 때, 30메가 정도의 하드디스크를 먹었고, 

안했을 때, 400메가 정도의 하드디스크를 차지하였다.

실행해보기

## 실행 편의를 위해 머신 1대에서 실행하기 위해 ## port와 dbpath, logpath등을 변경하였다. ## dbpath, logpath에 지정한 폴더가 존재해야 한다. 없으면 만들어야 한다. 파일은 자동 생성되니 만들필요 없다.. ## Primary DB 실행 port:10001 $ mongod --config /etc/mongod_repl1.conf ## Sencondary DB 실행 port:10002 $ mongod --config /etc/mongod_repl2.conf ## Arbiter DB 실행 port:10003 $ mongod --config /etc/mongod_arb.conf ## Primary DB 접속해서 ReplicaSet 설정하기 $ mongo localhost:10001/admin >db.runCommand({"replSetInitiate" : {"_id" : "first", "members" : [{"_id" : 1, "host" : "localhost:10001"}, {"_id" : 2, "host" : "localhost:10002"} ]}}) first:PRIMARY> rs.add('localhost:10003', true) ## 설정 확인 first:PRIMARY> rs.status() { "set" : "first", "date" : ISODate("2014-06-23T08:10:24Z"), "myState" : 1, "members" : [ { "_id" : 1, "name" : "localhost:10001", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 5086, "optime" : Timestamp(1403505938, 1), "optimeDate" : ISODate("2014-06-23T06:45:38Z"), "lastHeartbeat" : ISODate("2014-06-23T08:10:23Z"), "lastHeartbeatRecv" : ISODate("2014-06-23T08:10:23Z"), "pingMs" : 0, "syncingTo" : "localhost:10002" }, { "_id" : 2, "name" : "localhost:10002", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 6041, "optime" : Timestamp(1403505938, 1), "optimeDate" : ISODate("2014-06-23T06:45:38Z"), "electionTime" : Timestamp(1403505913, 1), "electionDate" : ISODate("2014-06-23T06:45:13Z"), "self" : true }, { "_id" : 3, "name" : "localhost:10003", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 22, "lastHeartbeat" : ISODate("2014-06-23T08:10:22Z"), "lastHeartbeatRecv" : ISODate("2014-06-23T08:10:23Z"), "pingMs" : 0 } ], "ok" : 1 }

테스트 해보기

Secondary DB가 원본 데이터를 잘 복사하는지 보자

## Primary DB 접속해 테스트 데이터 넣기 $ mongo localhost:10002/ first:PRIMARY> use test switched to db test first:PRIMARY> for(var i = 0; i < 100; ++i) db.test_col.save({name:i}) WriteResult({ "nInserted" : 1 }) ## Secondary DB 접속해 원본 데이터를 복사했는지 확인 $ mongo localhost:10001/ first:SECONDARY> use test switched to db test first:SECONDARY> db.test_col.find({}) error: { "$err" : "not master and slaveOk=false", "code" : 13435 } ## slaveOk함수를 호출해야 읽기가 가능함 first:SECONDARY> rs.slaveOk() first:SECONDARY> db.test_col.find({}) { "_id" : ObjectId("53a7bd6c08c322644599914a"), "name" : 0 } { "_id" : ObjectId("53a7bd6c08c322644599914b"), "name" : 1 } { "_id" : ObjectId("53a7bd6c08c322644599914c"), "name" : 2 } { "_id" : ObjectId("53a7bd6c08c322644599914d"), "name" : 3 } { "_id" : ObjectId("53a7bd6c08c322644599914e"), "name" : 4 } { "_id" : ObjectId("53a7bd6c08c322644599914f"), "name" : 5 } { "_id" : ObjectId("53a7bd6c08c3226445999150"), "name" : 6 } { "_id" : ObjectId("53a7bd6c08c3226445999151"), "name" : 7 } { "_id" : ObjectId("53a7bd6c08c3226445999152"), "name" : 8 } { "_id" : ObjectId("53a7bd6c08c3226445999153"), "name" : 9 } { "_id" : ObjectId("53a7bd6c08c3226445999154"), "name" : 10 } { "_id" : ObjectId("53a7bd6c08c3226445999155"), "name" : 11 } { "_id" : ObjectId("53a7bd6c08c3226445999156"), "name" : 12 } { "_id" : ObjectId("53a7bd6c08c3226445999157"), "name" : 13 } { "_id" : ObjectId("53a7bd6c08c3226445999158"), "name" : 14 } { "_id" : ObjectId("53a7bd6c08c3226445999159"), "name" : 15 } { "_id" : ObjectId("53a7bd6c08c322644599915a"), "name" : 16 } { "_id" : ObjectId("53a7bd6c08c322644599915b"), "name" : 17 } { "_id" : ObjectId("53a7bd6c08c322644599915c"), "name" : 18 } { "_id" : ObjectId("53a7bd6c08c322644599915d"), "name" : 19 } Type "it" for more ## 정상이군

Auto failover가 잘 되는지 보자

## Primary DB에 접속해 디비 서버 종료 $ mongo localhost:10002/admin MongoDB shell version: 2.6.2 connecting to: localhost:10002/admin first:PRIMARY> db.shutdownServer() ## Sencondary DB에 접속해 자신이 Primary DB로 승격되었는지 확인 $ mongo localhost:10001/admin MongoDB shell version: 2.6.2 connecting to: localhost:10001/admin first:PRIMARY> rs.status() { "set" : "first", "date" : ISODate("2014-06-23T08:33:50Z"), "myState" : 1, "members" : [ { "_id" : 1, "name" : "localhost:10001", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 7083, "optime" : Timestamp(1403511736, 100), "optimeDate" : ISODate("2014-06-23T08:22:16Z"), "electionTime" : Timestamp(1403512372, 1), "electionDate" : ISODate("2014-06-23T08:32:52Z"), "self" : true }, { "_id" : 2, "name" : "localhost:10002", "health" : 0, "state" : 8, "stateStr" : "(not reachable/healthy)", "uptime" : 0, "optime" : Timestamp(1403511736, 100), "optimeDate" : ISODate("2014-06-23T08:22:16Z"), "lastHeartbeat" : ISODate("2014-06-23T08:33:46Z"), "lastHeartbeatRecv" : ISODate("2014-06-23T08:32:44Z"), "pingMs" : 0 }, { "_id" : 3, "name" : "localhost:10003", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 1427, "lastHeartbeat" : ISODate("2014-06-23T08:33:48Z"), "lastHeartbeatRecv" : ISODate("2014-06-23T08:33:49Z"), "pingMs" : 0 } ], "ok" : 1 }

Auto Failover 동작 테스트

S와 A의 수가 장애극복 처리에 어떤 영향을 끼치는지, 

S와 A서버에 장애가 발했을 때, P서버에도 장애가 발생하면 어떻게 되는지 테스트 해보았다. 

 Primary = P, Secondary = S, Arbiter = A

 DB Type = O(alive) or X(dead)

동작 여부

 P = X

S = O

A = O

 O

 P = X

S = O

A = X

 X

 P = X

S = O

S = O

A = O

 O

 P = X

S = O

S = O

A = X

 X

 P = X

S = O

S = X

A = O

 X

 P = X

S = O

A = O

A = O

 O

 P = X

S = O

A = O

A = X

 X

 P = X

S = O

S = O

S = O

A = O

 O

 P = X

S = O

S = O

S = X

A = O

 O

 P = X

S = O

S = O

S = X

A = X

 X

 P = X

S = O

S = X

S = X

A = X

 X(P가 S로 자동으로 변경)

 P = X

S = O

S = O

A = O

A = O

 O

 P = X

S = O

S = O

A = X

A = X

 X

 P = X

S = O

S = X

A = X

A = X

 X(P가 S로 자동으로 변경)

 P = X

S = O

S = O

S = O

A = O

A = O

 O

 P = X

S = O

S = X

S = O

A = O

A = O

 O

 P = X

S = O

S = X

S = O

A = O

A = X

 X

 P = X

S = O

S = O

S = O

A = X

A = X

 X

 P = X

S = O

S = X

S = X

A = O

A = X

 X(P가 S로 자동으로 변경)


테스트 결론

A와 S에 속한 맴버 중 과반수에 장애가 발생한다면, P는 S로 자동으로 변경됨.

ReplicaSet 전체 맴버 중 살아있는 수가 과반수가 아니면 장애극복이 이루어지지 않는다. 즉, S가 2대 이상이 살았어도 살아남은 수가 과반수가 아니면 장애극복이 안된다.