티스토리 뷰

안녕하세요. 요즘 실제로 어플리케이션을 개발해서 서비스할 때, 단순히 하나의 어플리케이션을 구동하는 것이 아니라, 재해, 재난, 장애 등 여러 문제가 발생하여 어플리케이션이 다운되었을 경우 대비해 여러개의 서버에 어플리케이션을 구동시켜서 클러스터링을 통해 대응하는데, 데이터베이스도 마찬가지로 하나의 디비 서버가 죽었을 때를 대비해 복제를 통한 대응을 하려고하는데 기본적으로 몽고 DB에서는 복제의 기능을 지원합니다. 따라서 오늘은 Mongo DB의 Replication에 대해서 포스팅 해보려고 합니다.



1. Replication 설정(replica set)


MongoDB에서는 기본적으로 Replication(복제)를 지원합니다. 이는 서비스의 지속성과 안전성을 제공하는 데이터베이스 시스템의 설비이며, MongoDB는 단순하게 데이터 복제를 위한 것뿐만 아니라 Master가 장애시, Slave를 Master로 자동 승격시켜줍니다. 수 많은 Slave 중에서 어떤 것을 Master로 할지를 투표를 통해 결정하게 되는데, MongoDB는 투표에 참여하기만 하는 것으로 Arbiter를 설정할 수 있습니다.

(기본적으로 MongoDB에서는 Master --> Primary노드, Slave --> Secondary노드라고 부르기도 합니다.)



2. Replica Set의 기본 아키텍처



3. Test 노드들을 위한 디렉토리(node1, node2, node3, node4)를 만든 후, 아래의 명령어를 사용해 각각의 노드에 대한 mongod 인스턴스를 실행


mongod --port 27017 --dbpath /data/node1/ --replSet rs0 --smallfiles --oplogSize 128    // -- master
mongod --port 27018 --dbpath /data/node2/ --replSet rs0 --smallfiles --oplogSize 128    // -- slave 1
mongod --port 27019 --dbpath /data/node3/ --replSet rs0 --smallfiles --oplogSize 128    // -- slave 2
mongod --port 27020 --dbpath /data/node4/ --replSet rs0 --smallfiles --oplogSize 128    // -- slave 3
(--port는 노드 별로 다르게 설정, --dbpath는 위에서 생성한 노드 별 데이터 디렉토리 설정, Replica set의 이름은 rs0으로 설정, --smallfiles와 --oplogSize 옵션은 디스크 공간을 적게 사용하기 위한 유용한 옵션)


4. mongo 쉘을 통해 앞서 실행한 mongod 인스턴스 중 하나에 연결


mongo --port 27017



5. rsconf 변수에  Replica Set의 환경설정 정보 저장(localhost 대신에 hostname을 입력해도 됩니다.)


> rsconf = {_id: "rs0", members: [{_id: 0, host: "localhost:27017"}]}
{
    "_id" : "rs0",
    "members" : [
        {
            "_id" : 0,
            "host" : "localhost:27017"
        }
    ]
}



6. rs.initiate() 명령어를 사용해 Replica Set 초기화

> rs.initiate(rsconf)
{
    "ok" : 1,
    "operationTime" : Timestamp(1541734382, 1),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1541734382, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}



7. 나머지 포트번호(27018, 27019, 27020)를 res.add() 명령어를 사용해 Replica Set에 추가


rs0:PRIMARY> rs.add("localhost:27018")
{
    "ok" : 1,
    "operationTime" : Timestamp(1541734643, 1),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1541734643, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}
rs0:PRIMARY> rs.add("localhost:27019")
{
    "ok" : 1,
    "operationTime" : Timestamp(1541734659, 1),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1541734659, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}
rs0:PRIMARY> rs.add("localhost:27020")
{
    "ok" : 1,
    "operationTime" : Timestamp(1541734670, 1),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1541734670, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}



8. secondary로 지정된 port로 접속을 시도(명령어 창에 PRIMARY가 아닌 SECONDARY로 노출)


mongo --port 27018
MongoDB shell version v4.0.3
connecting to: mongodb://127.0.0.1:27018/
.....
rs0:SECONDARY>



9. db 쉘에 접속 후, db.system.replset.find().pretty() 명령어를 통해 PRIMARY와 SECONDARY 멤버를 확인


rs0:PRIMARY> db.system.replset.find().pretty()
{
    "_id" : "rs0",
    "version" : 4,
    "protocolVersion" : NumberLong(1),
    "writeConcernMajorityJournalDefault" : true,
    "members" : [
        {
            "_id" : 0,
            "host" : "localhost:27017",
            "arbiterOnly" : false,
            "buildIndexes" : true,
            "hidden" : false,
            "priority" : 1,
            "tags" : {
 
            },
            "slaveDelay" : NumberLong(0),
            "votes" : 1
        },
        {
            "_id" : 1,
            "host" : "localhost:27018",
            "arbiterOnly" : false,
            "buildIndexes" : true,
            "hidden" : false,
            "priority" : 1,
            "tags" : {
 
            },
            "slaveDelay" : NumberLong(0),
            "votes" : 1
        },
        {
            "_id" : 2,
            "host" : "localhost:27019",
            "arbiterOnly" : false,
            "buildIndexes" : true,
            "hidden" : false,
            "priority" : 1,
            "tags" : {
 
            },
            "slaveDelay" : NumberLong(0),
            "votes" : 1
        },
        {
            "_id" : 3,
            "host" : "localhost:27020",
            "arbiterOnly" : false,
            "buildIndexes" : true,
            "hidden" : false,
            "priority" : 1,
            "tags" : {
 
            },
            "slaveDelay" : NumberLong(0),
            "votes" : 1
        }
    ],
    "settings" : {
        "chainingAllowed" : true,
        "heartbeatIntervalMillis" : 2000,
        "heartbeatTimeoutSecs" : 10,
        "electionTimeoutMillis" : 10000,
        "catchUpTimeoutMillis" : -1,
        "catchUpTakeoverDelayMillis" : 30000,
        "getLastErrorModes" : {
 
        },
        "getLastErrorDefaults" : {
            "w" : 1,
            "wtimeout" : 0
        },
        "replicaSetId" : ObjectId("5be4ffee440935fb73928bf0")
    }
}



10. rs.status() 명령어를 통해 현재 상태를 확인


rs0:PRIMARY> rs.status()
{
    "set" : "rs0",
    "date" : ISODate("2018-11-09T03:59:12.512Z"),
    "myState" : 1,
    "term" : NumberLong(1),
    "syncingTo" : "",
    "syncSourceHost" : "",
    "syncSourceId" : -1,
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1541735945, 1),
            "t" : NumberLong(1)
        },
        "readConcernMajorityOpTime" : {
            "ts" : Timestamp(1541735945, 1),
            "t" : NumberLong(1)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1541735945, 1),
            "t" : NumberLong(1)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1541735945, 1),
            "t" : NumberLong(1)
        }
    },
    "lastStableCheckpointTimestamp" : Timestamp(1541735945, 1),
    "members" : [
        {
            "_id" : 0,
            "name" : "localhost:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 1851,
            "optime" : {
                "ts" : Timestamp(1541735945, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2018-11-09T03:59:05Z"),
            "syncingTo" : "",
            "syncSourceHost" : "",
            "syncSourceId" : -1,
            "infoMessage" : "",
            "electionTime" : Timestamp(1541734382, 2),
            "electionDate" : ISODate("2018-11-09T03:33:02Z"),
            "configVersion" : 4,
            "self" : true,
            "lastHeartbeatMessage" : ""
        },
        {
            "_id" : 1,
            "name" : "localhost:27018",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 1309,
            "optime" : {
                "ts" : Timestamp(1541735945, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1541735945, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2018-11-09T03:59:05Z"),
            "optimeDurableDate" : ISODate("2018-11-09T03:59:05Z"),
            "lastHeartbeat" : ISODate("2018-11-09T03:59:11.874Z"),
            "lastHeartbeatRecv" : ISODate("2018-11-09T03:59:11.874Z"),
            "pingMs" : NumberLong(0),
            "lastHeartbeatMessage" : "",
            "syncingTo" : "localhost:27017",
            "syncSourceHost" : "localhost:27017",
            "syncSourceId" : 0,
            "infoMessage" : "",
            "configVersion" : 4
        },
        {
            "_id" : 2,
            "name" : "localhost:27019",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 1292,
            "optime" : {
                "ts" : Timestamp(1541735945, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1541735945, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2018-11-09T03:59:05Z"),
            "optimeDurableDate" : ISODate("2018-11-09T03:59:05Z"),
            "lastHeartbeat" : ISODate("2018-11-09T03:59:11.874Z"),
            "lastHeartbeatRecv" : ISODate("2018-11-09T03:59:10.944Z"),
            "pingMs" : NumberLong(0),
            "lastHeartbeatMessage" : "",
            "syncingTo" : "localhost:27018",
            "syncSourceHost" : "localhost:27018",
            "syncSourceId" : 1,
            "infoMessage" : "",
            "configVersion" : 4
        },
        {
            "_id" : 3,
            "name" : "localhost:27020",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 1282,
            "optime" : {
                "ts" : Timestamp(1541735945, 1),
                "t" : NumberLong(1)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1541735945, 1),
                "t" : NumberLong(1)
            },
            "optimeDate" : ISODate("2018-11-09T03:59:05Z"),
            "optimeDurableDate" : ISODate("2018-11-09T03:59:05Z"),
            "lastHeartbeat" : ISODate("2018-11-09T03:59:11.874Z"),
            "lastHeartbeatRecv" : ISODate("2018-11-09T03:59:12.476Z"),
            "pingMs" : NumberLong(0),
            "lastHeartbeatMessage" : "",
            "syncingTo" : "localhost:27018",
            "syncSourceHost" : "localhost:27018",
            "syncSourceId" : 1,
            "infoMessage" : "",
            "configVersion" : 4
        }
    ],
    "ok" : 1,
    "operationTime" : Timestamp(1541735945, 1),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1541735945, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}


11. PRIMARY가 Fail일 경우, SECONDARY 중에서 어떤 것이 PRIMARY로 될지 투표를 수행하는 ARBITER 생성


mongod --port 30000 --dbpath [DATA_PATH] --replSet rs
> rs.addArb("localhost:30000")


이렇게 몽고 DB를 설치하고 몽고 DB에서 지원하는 Replication 기능을 이용해서 장애시 현명하게 복제된 인스턴스를 사용할 수 있는 방법을 간단한 예제를 통해서 알아봤습니다. 이것으로 포스팅은 마치도록 하겠습니다.