Post List

2015년 8월 16일 일요일

MongoDB Study #21 Replica 와 Replica Set (Server 안정성을 위한 이중화)

1. Replica

1.1 Replica 란 ?

MongoDB 여러대의 Server를 일정시간(매 2초)마다 동기화(Sync)를 하여 Data를 동일하게 유지하는 System을 뜻합니다. 하지만 실제 장애배디시 Slave Server를 Master로 자동으로 사용하는 그런 개념은 아닙니다. 단지 Data 복사만 해 둘 뿐이어서 해당 Data를 이용해서 복구 작업을 따로 수행해 줘야 합니다. 사용 목적자체가 장애를 대비하기 위함이기 때문에 당연히 다른 Server에서 가동시키는게 좋으며, 최소 3대 정도의 Slave를 설정하는것을 권장합니다.

1.2 Replica Master & Slave Server 실행하기 (실습)

먼저 MongoDB Server 3대를 가동합니다.
실습에서는 1대의 PC에 3개의 Folder를 생성하여 DB를 가동시키겠습니다.

- Replica0 : Port 50000
- Replica1 : Port 50001
- Replica2 : Port 50002

먼저 Folder를 생성합니다.
C:\>CD /MongoDB
C:\MongoDB>mkdir Replica0
C:\MongoDB>mkdir Replica1
C:\MongoDB>mkdir Replica2

3대의 MongoDB를 가동시킵니다. Replica0 은 Master로 나머지는 Slave로 실행합니다.
C:\MongoDB>MongoD --dbpath C:/MongoDB/Replica0 --port 50000 --master
C:\MongoDB>MongoD --dbpath C:/MongoDB/Replica1 --port 50001 --slave --source localhost:50000
C:\MongoDB>MongoD --dbpath C:/MongoDB/Replica2 --port 50002 --slave --source localhost:50000

이제 Test를 하나 해보겠습니다.
Master로 접속하여 Collection에 Document를 하나 입력한 다음에 Slave1,2에도 같은 Data가 있는지 살펴보겠습니다.
C:\>mongo localhost:50000
> db.emp.save( { empno : 7707, ename : 'Luna' , dept :'Develop' } )
> show dbs
local  1.078GB
test   0.078GB
> show collections
emp
system.indexes
> db.emp.find()
{ "_id" : ObjectId("55cfe069c9819ddd76d80b31"), "empno" : 7707, "ename" : "Luna", "dept" : "Develop" }
> exit
bye

C:\>mongo localhost:50001
MongoDB shell version: 3.0.5
connecting to: localhost:50001/test
> show dbs
2015-08-16T10:46:28.015+0900 E QUERY    Error: listDatabases failed:{ "note" : "from execCommand", "ok" : 0, "errmsg" :"not master" }
...
> db.emp.find()
{ "_id" : ObjectId("55cfe069c9819ddd76d80b31"), "empno" : 7707, "ename" : "Luna", "dept" : "Develop" }
> show collections
2015-08-16T10:46:53.325+0900 E QUERY    Error: listCollections failed: { "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" }
...
> exit
bye

C:\>mongo localhost:50002
MongoDB shell version: 3.0.5
connecting to: localhost:50002/test
> db.emp.find()
{ "_id" : ObjectId("55cfe069c9819ddd76d80b31"), "empno" : 7707, "ename" : "Luna", "dept" : "Develop" }
> exit
bye 

중간에 오타가 난다든지 등 다른 이유로 접속이 잘 안된다던지 설정이 잘못되었다던지 그럴땐 DB File들이 생성되어 있는 Folder 내에 파일들을 다 지우고 새로 시도해보시기 바랍니다.
위 실습대로 한 결과를 보면 알 수 있듯, Master Server에만 입력한 Data가 Slave Server 들에게도 똑같이 입력됬다는 것을 확인 할 수 있습니다. ObjectId도 똑같이 들어가 있습니다. show dbs, show collections 같은 명령어는 Slave Server에서는 실행이 되지 않습니다.

2. Replica Set

2.1 Replica Set 이란 ?

앞서 살펴본 Replica는 실시간으로 자동 복구가 되지 않습니다. 그 문제점을 보완한 기능이 Replica Set 입니다.
Replica Set에서 실 시간으로 사용되는 Main Server를 Primary Server 라고 합니다. User는 Primary Server에 접속해서 작업을 합니다.
Replica Set에서 두 번째 Server를 Secondary Server라고 합니다. Primary Server에 장애가 발생하였을 경우, Secondary Server는 Primary Server의 마지막 수행 작업부터 연속적으로 작업을 수행해 줍니다. 그 이후부터는 Secondary Server가 Primary Server가 되고, 원래 Primary Server는 복구 후 Secondary Server가 됩니다.
Secondary Server에 장애가 발생할 것을 대비하여 추가 복제 Server로 설정한 것을 Secondary for Backup 이라고 부릅니다.
매 2초마다 Primary Server와 Secondary Server의 상태를 동기화하는데 이 작업을 HeartBeat이라 부릅니다.
Primary Server가 Secondary Server로 Data를 복제하다가 장애로 동기화 못한 경우 Oplog에 백업을 해두고 추후 Secondary Server에 반영을 합니다. (Oplog의 최초 크기는 1GB 입니다.)

2.2 구성 유형

- Active Data Center 내에 Primary ,Secondary, Secondary for Backup을 모두 구성
  Network에 의한 Traffic을 최소화 할 수 있어서 성능 지연 문제를 최소화 할 수 있습니다만, 해당 지역에 화제, 지진 발생 등의 문제로 회선이 끊겼을 경우 모든 Server를 사용 할 수 없게 됩니다.

- Activa Data Center에 Primary, Secondary를 두고 다른 지역에 StandBy Data Center 를 두고 Secondary for Backup을 운영하는 경우
   첫번째 유형의 문제점을 어느 정도 해소 할 수 있습니다.

- 각각 다른 지역에 Data Center를 두고 거기에 각각 Primary , Secondary , Secondary for Backup을 두고 운영하는 경우
  이 경우에는 Data 동기화 통신을 위한 Network Traffic이 보장되지 않는다면 성능 지연 문제가 발생할 수도 있습니다.

2.3 Priority

Replica Set에서 Primary Server에 장애가 발생했을 경우 10초 이내에 다음 Primary Server를 결정해야 합니다.
이 경우 Abiter Server가 설정되어 있다면 Abiter가 적절한 Server를 Primary Server로 선출합니다.
하지만 사용자가 미리 각 Server의 Priority를 설정해 놓은 경우 가장 높은 값을 부여 받은 Server가 Primary Server가 됩니다.
Priority 값은 0 ~ 1000 사이에서 정의 할 수 있습니다.
> conf = { _id : "ReplicaSet명칭" , members: [
    { _id : 0 , host : "Server주소:Port번호", priority : 3 }, // <- 현재 가장 높으므로 Primary Server 가 됨
    { _id : 1, host : "Server주소:Port번호", priority : 1 },
    { _id : 2 , host : "Server주소:Port번호", priority : 2 }, // <- 장애 발생시 Primary Server가 될 Secondary Server
    { _id : 3 , host : "Server주소:Port번호", hidden : true , slaveDelay : 1800 } ] } // 장애가 발생하더라도 투표에 참가하지 않으며, Data 동기화는 1800초 이후부터 수행
> rs.initiate(conf)

2.4 Member 유형

- Secondary Only Member
   절대 Primary Server가 되지 않는 Only Secondary Server를 의미합니다.
> cfg = rs.conf()
> cfg.members[0].priority = 0 // Secondary Only로 설정
> rs.reconfig(cfg)

- Hidden Member
   Client Application에는 숨겨진 Member 입니다. Abiter가 Primary Server로 선출하는데 사용되지 않습니다.
> cfg = rs.conf()
> cfg.members[1].priority = 0
> cfg.members[1].hidden = true // Hidden Member로 설정
> rs.reconfig(cfg)

- Arbiter Member
   Data를 저장하지 않으며, 장애 발생 시 Primary Server를 선출하기 위한 투표에만 사용됩니다.
> db.runCommand( { replSetInitiate : { _id : "ReplicaSet명칭" , members: [
    { _id : 0 , host : "Server주소:Port번호"  },
    { _id : 1, host : "Server주소:Port번호"  },
    { _id : 2 , host : "Server주소:Port번호", arbiterOnly : true } ] } // Arbiter Member로 설정

- Delayed Member
   Primary Server의 Oplog를 정의된 시간 동안 Delay 한 후에 적용합니다.
> cfg = rs.conf()
> cfg.members[2].priority = 0
> cfg.members[2].slaveDelay = 3600 // 1시간 이후에 Oplog를 적용하는 Delayed Member로 설정
> rs.reconfig(cfg)

- Non Voting Member
   Data를 저장하고는 있지만 투표시에 Primary Server로 선택되지 않습니다.
> cfg = rs.conf()
> cfg.members[3].votes = 0 // Non Voting Member로 설정
> rs.reconfig(cfg)

2.5 실습

2.5.1 ReplicaSet 구동

먼저 실습을 위한 DB 공간을 위한 별도의 Folder를 생성합니다.
C:\>CD /MongoDB
C:\MongoDB>mkdir R1
C:\MongoDB>mkdir R2
C:\MongoDB>mkdir A

2대의 Server와 1대의 Arbiter Server를 실행합니다.
mongod --dbpath c:/mongodb/r1 --port 10001 --replSet RPT --oplogSize 10 --rest
mongod --dbpath c:/mongodb/r2 --port 10002 --replSet RPT --oplogSize 10 --rest
mongod --dbpath c:/mongodb/a --port 10003 --replSet RPT --oplogSize 10 --rest

Client로 접속하여 ReplicaSet을 초기화 해줍니다.
C:\>mongo localhost:10001/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10001/admin
Server has startup warnings:
2015-08-16T12:21:24.818+0900 I CONTROL  ** WARNING: --rest is specified without --httpinterface,
2015-08-16T12:21:24.820+0900 I CONTROL  **          enabling http interface
> db.runCommand( { replSetInitiate : { _id : "RPT" , members : [
    { _id : 1 , host : 'localhost:10001' } ,
    { _id : 2 , host : 'localhost:10002' } ,
    { _id : 3 , host : 'localhost:10003' , arbiterOnly : true } ] } } )
{ "ok" : 1 }
RPT:OTHER> rs.conf()
{
        "_id" : "RPT",
        "version" : 1,
        "members" : [
                {
                        "_id" : 1,
                        "host" : "localhost:10001",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {
                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                },
                {
                        "_id" : 2,
                        "host" : "localhost:10002",
                        "arbiterOnly" : false,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {
                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                },
                {
                        "_id" : 3,
                        "host" : "localhost:10003",
                        "arbiterOnly" : true,
                        "buildIndexes" : true,
                        "hidden" : false,
                        "priority" : 1,
                        "tags" : {
                        },
                        "slaveDelay" : 0,
                        "votes" : 1
                }
        ],
        "settings" : {
                "chainingAllowed" : true,
                "heartbeatTimeoutSecs" : 10,
                "getLastErrorModes" : {
                },
                "getLastErrorDefaults" : {
                        "w" : 1,
                        "wtimeout" : 0
                }
        }
} 

2.5.2 Fail Over

Primary Server로 설정된 Server를 Shutdown 시킨 후 Secondary Server로 접속하여 해당 Server가 Primary로 된 것을 확인합니다.
그 다음 다시 원래 Primary Server 였던 것을 다시 가동 시킨 후 접속하여 Secondary로 된 것을 확인해 보겠습니다.

먼저 Primary Server를 Shutdown 시키고 Secondary Server 였던 10002에 접속하여 Primary Server가 된 것을 확인합니다.
C:\>mongo localhost:10001/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10001/admin
Server has startup warnings:
2015-08-16T12:21:24.818+0900 I CONTROL  ** WARNING: --rest is specified without --httpinterface,
2015-08-16T12:21:24.820+0900 I CONTROL  **          enabling http interface
RPT:PRIMARY> db.printReplicationInfo()
configured oplog size:   10MB
log length start to end: 0secs (0hrs)
oplog first event time:  Sun Aug 16 2015 12:21:43 GMT+0900 (대한민국 표준시)
oplog last event time:   Sun Aug 16 2015 12:21:43 GMT+0900 (대한민국 표준시)
now:                     Sun Aug 16 2015 12:25:37 GMT+0900 (대한민국 표준시)
RPT:PRIMARY> db.printSlaveReplicationInfo()
source: localhost:10002
        syncedTo: Sun Aug 16 2015 12:21:43 GMT+0900 (대한민국 표준시)
        0 secs (0 hrs) behind the primary
RPT:PRIMARY> show dbs
local  0.078GB
RPT:PRIMARY> db.shutdownServer()
...
> exit
bye
C:\>mongo localhost:10002/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10002/admin
Server has startup warnings:
2015-08-16T12:21:28.775+0900 I CONTROL  ** WARNING: --rest is specified without --httpinterface,
2015-08-16T12:21:28.777+0900 I CONTROL  **          enabling http interface
RPT:PRIMARY> exit
bye

다시 10001 Server를 가동합니다.
C:\>mongod --dbpath c:/mongodb/r1 --port 10001 --replSet RPT --oplogSize 10 --rest
...
2015-08-16T12:32:55.994+0900 I REPL     [ReplicationExecutor] transition to RECOVERING
2015-08-16T12:32:55.996+0900 I REPL     [ReplicationExecutor] transition to SECONDARY
2015-08-16T12:32:55.997+0900 I REPL     [ReplicationExecutor] Member localhost:10002 is now in state PRIMARY
2015-08-16T12:32:55.997+0900 I REPL     [ReplicationExecutor] Member localhost:10003 is now in state ARBITER
...

10001로 접속해서 Secondary로 된 것을 확인해 보겠습니다.
C:\>mongo localhost:10001/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10001/admin
Server has startup warnings:
2015-08-16T12:32:55.924+0900 I CONTROL  ** WARNING: --rest is specified without --httpinterface,
2015-08-16T12:32:55.926+0900 I CONTROL  **          enabling http interface
RPT:SECONDARY> exit
bye
C:\>mongo localhost:10003/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10003/admin
Server has startup warnings:
2015-08-16T12:21:32.738+0900 I CONTROL  ** WARNING: --rest is specified without --httpinterface,
2015-08-16T12:21:32.740+0900 I CONTROL  **          enabling http interface
RPT:ARBITER> exit
bye

2.5.3 복제 Server 추가 및 삭제

먼저 복제 Server를 하나 실행합니다.
C:\MongoDB>mkdir D3
C:\MongoDB>mongod --dbpath c:/mongodb/d3 --port 10004 --replSet RPT --oplogSize 10 --rest

10002에 접속하여 해당 복제 Server를 추가 후 삭제 해 보겠습니다.
C:\>mongo localhost:10002/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10002/admin
Server has startup warnings:
2015-08-16T12:21:28.775+0900 I CONTROL  ** WARNING: --rest is specified without --httpinterface,
2015-08-16T12:21:28.777+0900 I CONTROL  **          enabling http interface
RPT:PRIMARY> db.printSlaveReplicationInfo()
source: localhost:10001
        syncedTo: Sun Aug 16 2015 12:21:43 GMT+0900 (대한민국 표준시)
        0 secs (0 hrs) behind the primary
RPT:PRIMARY> rs.add("localhost:10004")
{ "ok" : 1 }
RPT:PRIMARY> db.printSlaveReplicationInfo()
source: localhost:10001
        syncedTo: Sun Aug 16 2015 12:42:45 GMT+0900 (대한민국 표준시)
        0 secs (0 hrs) behind the primary
source: localhost:10004
        syncedTo: Thu Jan 01 1970 09:00:00 GMT+0900 (대한민국 표준시)
        1439696565 secs (399915.71 hrs) behind the primary
RPT:PRIMARY> rs.remove("localhost:10004")
{ "ok" : 1 }
RPT:PRIMARY> db.printSlaveReplicationInfo()
source: localhost:10001
        syncedTo: Sun Aug 16 2015 12:43:10 GMT+0900 (대한민국 표준시)
        0 secs (0 hrs) behind the primary

2.5.4 Oplog 크기 설정

먼저 현재 Primary로 설정되어 있는 10001에 접속하여 종료한 후 Oplog 내용을 dump로 저장합니다.
(dump 저장을 이번 실습에서 사용하지 않으므로 그냥 어떻게 한다는 것만 알고 있으셔도 됩니다.)
C:\>mongo localhost:10001/admin
...
RPT:PRIMARY> db.shutdownServer()
...
> exit
bye
C:\>mongodump --db local --collection 'oplog.rs' --port 10002 // Oplog.rs를 Dump로 Backup

10001을 Replica Set 옵션 없이 StandAlone으로 실행후 Client로 접속하여 Oplog를 다른 임시 Collection으로 Backup 후 20 MB의 크기로 생성하여 다시 Backup한 Data를 다시 입력 한 뒤 Shutdown 시킵니다.
mongod --dbpath c:/mongodb/r1 --port 10001
C:\>mongo localhost:10001/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10001/admin
Server has startup warnings:
2015-08-16T13:30:57.972+0900 I STORAGE  [initandlisten]
2015-08-16T13:30:57.973+0900 I STORAGE  [initandlisten] ** WARNING: mongod started without --replSet yet 1 documents are present in local.system.replset
2015-08-16T13:30:57.973+0900 I STORAGE  [initandlisten] **          Restart with --replSet unless you are doing maintenance and  no other clients are connected.
2015-08-16T13:30:57.973+0900 I STORAGE  [initandlisten] **          The TTL collection monitor will not start because of this.
2015-08-16T13:30:57.974+0900 I STORAGE  [initandlisten]
> use local
switched to db local
> db.temp.save(db.oplog.rs.find().sort( { $natural : -1 } ).limit(1).next() ) // oplog의 내용을 temp라는 임시 Collection으로 Backup
WriteResult({ "nInserted" : 1 })
> db.temp.find()
{ "_id" : ObjectId("55d0125235a391d22d841e6a"), "ts" : Timestamp(1439698979, 2), "h" : NumberLong("4869370157673258169")
, "v" : 2, "op" : "i", "ns" : "test.emp", "o" : { "_id" : ObjectId("55d0102385ac9ce83b70f4bf"), "empno" : 7707, "ename"
: "Luna", "dept" : "Develop" } }
> db.oplog.rs.drop()
true
> db.runCommand( { create : 'oplog.rs' , capped : true , size : 20 * 1024 * 1024 } ) // oplog 크기를 변경하여 새로 생성
{ "ok" : 1 }
> db.oplog.rs.save( db.temp.findOne() ) // temp의 내용으 oplog로 복사
WriteResult({
        "nMatched" : 0,
        "nUpserted" : 1,
        "nModified" : 0,
        "_id" : ObjectId("55d0125235a391d22d841e6a")
})
> db.oplog.rs.find()
{ "_id" : ObjectId("55d0125235a391d22d841e6a"), "ts" : Timestamp(1439698979, 2), "h" : NumberLong("4869370157673258169")
, "v" : 2, "op" : "i", "ns" : "test.emp", "o" : { "_id" : ObjectId("55d0102385ac9ce83b70f4bf"), "empno" : 7707, "ename"
: "Luna", "dept" : "Develop" } }
> use admin
switched to db admin
> db.shutdownServer()
...
> exit
bye

다시 10001을 oplog Size 설정 없이 ReplicaSet으로 실행한 다음
10002를 강제로 Secondary로 만든 후
10001에 접속하여 oplog Size를 확인해보겠습니다.

mongod --dbpath c:/mongodb/r1 --port 10001 --replSet RPT --rest
C:\>mongo localhost:10002/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10002/admin
Server has startup warnings:
2015-08-16T13:45:29.296+0900 I CONTROL  ** WARNING: --rest is specified without --httpinterface,
2015-08-16T13:45:29.299+0900 I CONTROL  **          enabling http interface
RPT:PRIMARY> rs.stepDown(10)  // Primary를 강제로 Secondary로 변경
...
RPT:SECONDARY> exit
bye
C:\>mongo localhost:10001/admin
MongoDB shell version: 3.0.5
connecting to: localhost:10001/admin
Server has startup warnings:
2015-08-16T13:42:33.952+0900 I CONTROL  ** WARNING: --rest is specified without --httpinterface,
2015-08-16T13:42:33.954+0900 I CONTROL  **          enabling http interface
RPT:PRIMARY> db.printReplicationInfo()  // 바뀐 Oplog 의 Size를 확인
configured oplog size:   20MB
log length start to end: 0secs (0hrs)
oplog first event time:  Wed Aug 19 2015 09:00:21 GMT+0900 (대한민국 표준시)
oplog last event time:   Wed Aug 19 2015 09:00:21 GMT+0900 (대한민국 표준시)
now:                     Wed Aug 19 2015 09:04:49 GMT+0900 (대한민국 표준시)

2.5.5 Member Server 들 간의 Data 동기화

Replica Set 운영중 장애가 발생해서 기존 Server 를 재설치해서 Disk의 내용이 다 날라갔다던지, 새로운 H/W로 Server를 구성한던지 할 경우 기존 Data를 동기화 해야 합니다.
물론 그냥 Server 로 가동을 하면 자동으로 동기화를 해줍니다.

10001 Server를 Shutdown 시킨 후 R1 폴더안의 내용을 다 삭제해보겠습니다.
C:\>mongo localhost:10001/admin
...
RPT:PRIMARY> db.shutdownServer()
...
> exit
bye
C:\>cd /MongoDB/R1
C:\MongoDB\R1>del *.*
C:\MongoDB\R1>rmdir journal
C:\MongoDB\R1>dir           0개 파일                   0 바이트 

10001을 다시 ReplicaSet으로 실행한 후 R1 폴더 내의 File들을 확인해 보겠습니다.
mongod --dbpath c:/mongodb/r1 --port 10001 --replSet RPT --rest
C:\MongoDB\R1>dir
...
2015-08-16  오후 01:59    <DIR>          journal
2015-08-16  오후 01:59        67,108,864 local.0
2015-08-16  오후 02:00     1,073,741,824 local.1
2015-08-16  오후 01:59        16,777,216 local.ns
2015-08-16  오후 01:59                 4 mongod.lock
2015-08-16  오후 01:59                69 storage.bson
2015-08-16  오후 02:00        67,108,864 test.0
2015-08-16  오후 02:00        16,777,216 test.ns
2015-08-16  오후 02:00    <DIR>          _tmp
               7개 파일       1,241,514,057 바이트 

하지만 이 과정에서 네트워크 상의 Traffic을 대량으로 발생시킬수도 있기 때문에
해당 File들을 Disk로 저장이 가능하다면 복사를 하는 방법도 있습니다.

DB 폴더 내의 모든 파일 및 Journal 폴더를 복사 한 뒤에 새로 설치한 Server의 DB 폴더에 복사를 하고 Server를 활성화 해도 됩니다.

댓글 없음:

댓글 쓰기