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대 이상이 살았어도 살아남은 수가 과반수가 아니면 장애극복이 안된다.