Last Updated on 12월 11, 2023 by Jade(정현호)
안녕하세요
이번 포스팅은 MongoDB의 복제 환경 환경인 Replica Set 구성에 대해서 내용을 확인해보도록 하겠습니다.
Contents
복제
MongoDB의 Replica set는 동일한 데이터 세트를 유지 관리하는 mongod 프로세스의 그룹입니다.
Replica set 은 중복성과 고가용성을 제공하며 모든 프로덕션 배포의 기반이 됩니다
Replica set 은 단일 Replica Set 과 샤딩 기능이 추가된 Shared Cluster 로 구분 할 수 있습니다.
이중화 및 데이터 가용성
Replication 은 데이터의 중복성을 제공하고, 데이터 가용성을 높이게 됩니다. 서로 다른 데이터베이스 서버에 있는 여러 데이터 복사본을 사용하여 복제 환경은 단일 데이터베이스 서버의 손실에 대한 내결함성(fault tolerance) 수준을 제공합니다.
설정과 경우에 따라서 클라이언트가 요청한 읽기 작업에 대해서는 다른 서버로 보낼 수 있으며, 그에 따라서 복제 구성을 통해서 읽기 선능과 용량을 늘릴 수 있게 됩니다.
여러 데이터센터 또는 서버에서 데이터 복사본을 유지 관리함으로 분산 애플리케이션의 데이터 지역성과 가용성을 높일 수 있습니다.
또한 재해 복구, 보고 또는 백업과 같은 전용 목적을 위해서 추가 복사본을 유지할 수도 있습니다.
MongoDB 의 복제
MongoDB 의 Replica set 은 동일한 데이터 세트를 유지 관리하는 mongod 인스턴스 그룹입니다.
Replica set 에는 여러 데이터 베어링 노드와 선택적으로 하나의 중재자(arbiter) 노드가 포함되게 됩니다.
데이터 베어링 노드 중 하나의 구성원만 Primary(기본) 노드가 되며 다른 노드는 Secondary 노드가 되게 됩니다.
단일 Replica set 구조에서는 별도의 관리용 컴포넌트가 필요하지는 않지만, 단일 노드(Standalone) 에 비해서 추가로 MongoDB 서버가 필요 합니다.
Replica set은 특정 서버에 장애가 발생했을 때 자동 복구를 위한 최소 단위이므로 자동 복구가 필요하다면 항상 Replica set 형태로 MongoDB 를 배포를 해야 합니다.
MongoDB의 고가용성은 Replica set 내부에서 처리됩니다. 샤딩된 클러스터 구조뿐만 아니라 단일 Replica set 에서 고가용성이 처리됩니다.
고가용성을 위해서 MongoDB Replica set 의 각 멤버는 서로 다른 멤버가 살아 있는지 계속 확인 메세지를 주고받게 되는데, 이를 Heartbeat 메세지라고 합니다.
만약 Primary 노드(멤버)가 통신이 되지 않는다면 다른 멤버들이 새로운 Primary 멤버를 선출해서 서비스가 계속될 수 있도록 처리를 하게 됩니다.
이렇게 새로운 Primary 멤버 선출 과정에서의 MongoDB의 샤딩이나 MongoDB 컨피그 서버와는 무관하게 Replica set 내에서 처리되게 됩니다.
복제의 또 다른 목적으로는 부하 분산이 있습니다. 데이터 조회 쿼리의 로드를 분산하여 사용할 수 있습니다.
MongoDB Replica set 에서 고가용성을 위해 많은 멤버를 투입할 필요는 없습니다.
일반적으로 고가용성만을 위한 멤버 구성으로 3대 정도의 서버로 구성하는 것이 일반적이며, 만약 데이터 조회 쿼리가 아주 많거나 부하가 있는 서비스에서는 멤버를 더 추가하는 것을 고려해볼 수 있습니다.
MongoDB 의 Replica set 에서 쓰기 쿼리를 처리할 수 있는 것은 오직 Primary 노드(멤버) 만 가능 합니다. 그래서 멤버를 더 많이 추가한다고 해도 쓰기 쿼리에 대한 확장 또는 부하 분산은 할 수 없습니다. 하지만 멤버가 늘어난 만큼 읽기 쿼리는 부하 분산할 수 있습니다.
MongoDB 드라이버를 통해 단일 노드(Standalone) 로 접속 시 MongoDB 서버로 접속을 하지만, Replica set 에 접속할 때는 Replica set 옵션을 사용해야 하며, Replica set 에 접속하여 읽기 쿼리는 Primary 에서 수행이 기본 설정입니다.
MongoDB 드라이버에서 접속 시 Read Preference 옵션을 통해서 해당 부분을 제어할 수 있습니다.
Replica set 구성 형태
MongoDB 에서도 다른 RDBMS의 Replication 과 동일(유사) 하게 Primary node 에서 모든 쓰기 작업을 수행하게 됩니다.
그리고 기본적으로는 읽기 작업 또한 Primary 노드에서 수행됩니다.
위의 이미지와 같이 하나의 Replica set 은 항상 하나의 Primary 노드와 1개 이상의 Secondary 노드로 구성되게 됩니다.
데이터의 모든 변경 사항은 oplog 에 기록을 하게 되며, Secondary 노드는 Primary 노드나 다른 Secondary 노드의 oplog 를 전달받아서 데이터 동기화를 진행하게 됩니다.
Primary 와 Secondary 노드 서로 간의 heartbeat 을 통한 상태 체크가 이루어지며, Primary 노드를 사용할 수 없는 경우(장애나 네트워크 이슈 등) 적격한 Secondary 노드는 새로운 Primary 노드를 선택하기 위한 투표를 개최합니다.
투표를 통해서 Primary 노드가 결정하므로 가능한 홀수 개의 노드로 구성하는 것이 좋습니다.
짝수개의 노드로 Replica set 을 구성할 수 있지만 실제 Replica set의 가용성은 홀수 개의 노드로 구성했을 때와 다르지 않아서 서버의 낭비로 이어질 수도 있습니다.
또한 짝수 멤버로 Replica set 을 구성하면 쿼럼(Quorum) 구성이 어려울 수도 있습니다.
Replica set 의 맴버(노드)가 과반수 이상 통신할 수 있는 상태가 되어야 투표를 실행할 수 있기 때문에 투표를 할 수 있는 최소 노드는 아래와 같게 됩니다.
전체 노드 수 | 투표를 위한 최소 노드수 ------------------------------ 2 | 2 3 | 2 4 | 3 5 | 3 6 | 4 7 | 4
그래서 4개의 노드로 구성한 것과 3개 노드로 구성한 것 모두 2개 이상의 노드가 응답이 불가 하면 투표가 실행하지 못함으로 투표를 위한 최소 필요 노드 수는 2로 같게 됩니다.
Replica set 을 3대 서버로 구축하는 것도 사용에 따라서 서버 낭비라고 생각 될수도 있습니다. 이럴 경우 Arbiter 모드의 MongoDB 를 사용할 수 있으며 이 Arbiter 모드의 MongoDB는 Primary 노드가 불능일 때 Primary 노드의 선출을 위한 투표에만 참여하게 됩니다(Vote Only)
그래서 Arbiter 는 디스크에 데이터를 저장하지 않으며, Primary 노드로 부터 데이터를 받지 않기 때문에 서버의 사양도 다른 Primary 나 Secondary 노드처럼 고사양일 필요는 없으며 하나의 Replica set 에는 여러 개의 Arbiter 가 존재할 수 있지만 실제 정상적인 상황에서는 하나 이상의 Arbiter 는 필요하지 않습니다.
MongoDB 매뉴얼에서도 Replica set 별로 1개 이상의 arbiter 를 사용하는 것을 피하라는 내용의 warning 메세지가 있습니다.
Avoid deploying more than one arbiter per replica set.
See also: Concerns with Multiple Arbiters
그리고 중요한 점은 실제 운영 환경에서는 arbiter 모드의 MongoDB를 Primary 나 Secondary 멤버와 같은 물리적으로 같은 서버에서 사용하지 않는 것이 중요 합니다.
컨센서스 알고리즘(Consensus Algorithm)
복제에 참여하는 멤버 중에서 특정 멤버가 응답이 불능 상태일 경우 어떻게 대처할 것인지 등을 결정을 해야 하고, 이때 어떻게 동작 할지를 결정하는 것을 컨센서스 알고리즘(Consensus Algorithm) 이라고 합니다.
MongoDB 는 확장된 형태의 Raft 컨센서스 모델을 사용하고 있습니다.
관련된 내용은 아래 포스팅을 참조하시면 됩니다.
Replica set 구성
Replica set 을 구성은 3개의 서버로 진행하도록 하겠으며, 1개의 Primary , 1개의 Secondary , 1개의 Arbiter 로 구성하도록 하겠습니다.
MongoDB 가 설치가 완료된 이후 부터의 내용으로 설치에 관한 내용은 이전 포스팅을 참조하시면 됩니다.
MongoDB Replica Set 구성시 멤버수 50개, 투표 멤버 7개로 제한됩니다. 다른 멤버 간의 Heartbeat 메세지를 보내는 데 필요한 네트워크 트래픽량을 줄이고 Primary 멤버 선출에 소요되는 시간을 단축하거나 제한하기 위함입니다.
포스팅 구성 환경
- OS : CentOS 7.x
- MongoDB ver: 4.4
연결 확인 및 mongod.conf 설정
MongoDB 노드 간에 통신이 되는지를 먼저 확인을 해봐야 합니다.
간단하게 OS 명령어인 telnet 이나 curl 로 MongoDB 포트로 접속하여, 접속 유무를 확인할 수 있으며, mongo client 로 접속해서 확인할 수도 있습니다.
MongoDB 노드 간에 서로 통신이 되는지 확인
mongodb1# mongo --host mongodb2 --port 27017 MongoDB shell version v4.4.13 connecting to: mongodb://mongodb2:27017/?compressors=disabled&gssapiServiceName=mongodb Implicit session: session MongoDB server version: 4.4.13 mongodb1# mongo --host mongodb3 --port 27017 MongoDB shell version v4.4.13 connecting to: mongodb://mongodb3:27017/?compressors=disabled&gssapiServiceName=mongodb Implicit session: session MongoDB server version: 4.4.13
여기에서 노드 간의 통신이 안된다면(접속이 안된다면) MongoDB의 설정에서 bindIp 를 먼저 살펴보고, 그 다음 OS 나 네트워크 구성상의 방화벽 또는 보안 장비등에 의한 포트 block 등을 확인해 봐야 합니다.
## /etc/mongod.conf 내용중 # network interfaces net: port: 27017 bindIp: 0.0.0.0 <--!! 확인
참고로 RPM계열(RHEL,CentOS,RockyLinux 등) 리눅스 7, 8, 9 버전의 OS방화벽에서 다음과 같이 TCP 27017 포트를 오픈 할 수 있습니다.
# TCP 27017 포트 오픈 firewall-cmd --permanent --add-port=27017/tcp # TCP 27017 포트 오픈 정책 삭제 firewall-cmd --permanent --remove-port=27017/tcp # 특정 IP(123.123.123.123)에 대해서 27017포트 오픈 firewall-cmd --permanent --add-rich-rule='rule family="ipv4" \ source address=123.123.123.123 port port="27017" protocol="tcp" accept' # 특정 IP(123.123.123.123)에 대해서 27017포트 오픈 정책 삭제 firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" \ source address=123.123.123.123 port port="27017" protocol="tcp" accept' # 정책 재반영 및 방화벽 정책 확인 firewall-cmd --reload;firewall-cmd --list-all
Replication 설정 입력
모든 Replica set 참여 노드에서 mogod.conf 파일에서 replication 관련 설정을 하도록 하겠습니다.
vi /etc/mongod.conf ## 아래 내용을 입력 replication: oplogSizeMB: 2000 replSetName: "test-rs-0"
위의 예제에서는 Replica set 이름을 test-rs-0 으로 하였으며, oplog size 는 2000M 로 설정하였습니다.
(명칭 및 용량은 샘플(예시) 입니다)
설정이 완료되었다면 MongoDB 를 시작하거나 재시작 하도록 하겠습니다.
sudo systemctl start(or restart) mongod
Initiates a replica set
rs.initiate() 를 통해서 Replica set 초기화를 진행하면 되며, 한곳의 MonogDB 에서 진행하면 됩니다.
Primary 로 설정을 희망하는 서버에서 진행하도록 하겠습니다.
구성할 때때 크게 2가지 방법이 있습니다. initiate 를 먼저 한 다음 이후에 멤버를 추가하는 방법 과 처음부터 멤버를 지정하여 initiate 를 하는 방법입니다.
initiate 후에 멤버 추가
## rs.initiate 수행 rs.initiate() { "info2" : "no configuration specified. Using a default configuration for the set", "me" : "mongodb1:27017", "ok" : 1 } ## 멤버 추가 rs.add("mongodb2:27017") 또는 rs.add({ _id: 2, host: "mongodb3:27017" })
옵션 없이 rs.initiate() 만 실행하게 되면 실행한 노드로만 구성된 Replica set 이 구성됩니다.
그 위의 예제와 같이 다른 노드(멤버)를 추가하면 되며, 멤버 추가시 _id 등의 추가 필드를 지정할 수 있으며, 가능한 configuration field 는 다음과 같습니다.
{ _id: <int>, host: <string>, // required arbiterOnly: <boolean>, buildIndexes: <boolean>, hidden: <boolean>, priority: <number>, tags: <document>, slaveDelay: <int>, votes: <number> }
멤버를 지정하여 initiate 실행
## Primary 와 Secondary 멤버 구성 rs.initiate( { _id : "test-rs-0", members: [ { _id: 0, host: "mongodb1:27017" }, { _id: 1, host: "mongodb2:27017" }, ] }) ## Arbiter 모드 멤버 추가 rs.addArb("mongodb3:27017") 또는 rs.add( { host: "mongodb3:27017", arbiterOnly: true } )
포스팅에서는 1개의 Arbiter 를 사용하기로 하였으므로 위의 예제와 같이 Arbiter 를 구성하였습니다.
또는 아래와 같이 데이터 노드와 arbiter 노드의 내용을 한 번에 입력하여 구성할 수 있습니다.
rs.initiate( { _id : "test-rs-0", members : [ {_id : 0, host: "mongodb1:27017"}, {_id : 1, host: "mongodb2:27017"}, {_id : 2, host: "mongodb3:27017",arbiterOnly:true} ] } )
Replica set 멤버 삭제는 remove 를 이용하면 됩니다.
rs.remove("mongodb3:27017")
MongoDB 예전 버전의 문서에서는 Arbiter 추가 관련 Start Up Configuration 에서 스토리지 별 설정이 별도로 명시된 것이 있었습니다.
4.0 버전까지는 MMAPv1 사용할 경우 storage.mmapv1.smallFiles 을 true 로 설정에 관한 내용이 확인되고 그 이후 버전인 4.2 버전 부터는 storage 관련 설정 가이드는 없습니다.
• MongoDB 4.0 tutorial/add-replica-set-arbiter 내용 중에서
For MMAPv1 storage engine, storage.mmapv1.smallFiles to true.
Do not set storage.mmapv1.smallFiles on a data-bearing node unless specifically indicated.
관련해서 MongoDB 4.2 버전부터 MMAPv1 옵션이 사라졌으며, MongoDB 4.0 부터는 WiredTiger 스토리지 엔진을 사용하는 Replica set 의 멤버에 대해 "--nojournal" 옵션 또는 storage.journal.enabled: false 를 지정할 수 없는 내용이 있습니다.
Note:
Starting in MongoDB 4.0, you cannot specify --nojournal option or storage.journal.enabled: false for replica set members that use the WiredTiger storage engine.
initiate시 아래와 같은 에러가 발생할 경우에는 MongoDB설정에서(mongod.conf) security의 authorization가 enabled 로 설정된 상태일 수 있습니다.
{ "ok" : 0, "errmsg" : "replSetInitiate quorum check failed because not all proposed set members responded affirmatively: mongodb3:27017 failed with Authentication failed., mongodb2:27017 failed with Authentication failed.", "code" : 74, "codeName" : "NodeNotFound" } * 가로 길이에 따라서 위의 내용은 개행 되어 있습니다.
security 부분을 주석 처리 후 Replica Set을 구성하거나 Replica Set 구성 전에 admin 유저를 활성화(생성) 후 진행해야 합니다.
Replica set 정보 확인
Replica set 구성이 완료되었으며 구성된 내역 등을 확인해보도록 하겠습니다.
rs.conf()
rs.conf() { "_id" : "test-rs-0", <--!! Replica set 이름 "version" : 4, "term" : 1, "protocolVersion" : NumberLong(1), "writeConcernMajorityJournalDefault" : true, "members" : [ { "_id" : 0, "host" : "mongodb1:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 }, { "_id" : 1, "host" : "mongodb2:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 }, { "_id" : 2, "host" : "mongodb3:27017", "arbiterOnly" : true, <--!! Arbiter 모드 설정 "buildIndexes" : true, "hidden" : false, "priority" : 0, "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("6241da4c826dbf781c4e0559") } }
Replica Set 멤버의 정보를 확인할 수 있으며 3개의 멤버가 모두 확인되고 있습니다.
구성한 Replica set 이름인 "test-rs-0" 이 확인되고 있으며, 3번째 멤버("_id" : 2) 에서는 arbiterOnly : true 인 것을 확인할 수 있으며, 다른 멤버와 달리 Priority 가 0 인 것을 확인할 수 있습니다.
Note
MongoDB 3.6 버전 부터 Arbiter 의 우선 순위는 0 이 되었습니다. MongoDB 3.6으로 Replica set 을 업그레이드할 때 기존 구성에서 Arbiter의 우선 순위가 1인 경우 MongoDB 3.6 에서 Arbiter의 우선 순위가 0 이 되도록 재구성하게 됩니다.
Arbiter 는 1개의 선출 투표권을 가지고 있으며, 기본적으로 우선 순위는 0으로 설정됩니다.
rs.status()
status() 는 설정 내역이 아닌 Replica set 과 구성 멤버들의 상태 정보를 확인할 수 있습니다.
"stateStr" 필드의 값 등에서 멤버의 역할을 확인할 수 있습니다.
test-rs-0:PRIMARY> rs.status() { "set" : "test-rs-0", "date" : ISODate("2022-03-28T16:21:33.100Z"), "myState" : 1, "term" : NumberLong(1), "syncSourceHost" : "", "syncSourceId" : -1, "heartbeatIntervalMillis" : NumberLong(2000), "majorityVoteCount" : 2, "writeMajorityCount" : 2, "votingMembersCount" : 3, "writableVotingMembersCount" : 2, "optimes" : { "lastCommittedOpTime" : { "ts" : Timestamp(1648484485, 1), "t" : NumberLong(1) }, "lastCommittedWallTime" : ISODate("2022-03-28T16:21:25.007Z"), "readConcernMajorityOpTime" : { "ts" : Timestamp(1648484485, 1), "t" : NumberLong(1) }, "readConcernMajorityWallTime" : ISODate("2022-03-28T16:21:25.007Z"), "appliedOpTime" : { "ts" : Timestamp(1648484485, 1), "t" : NumberLong(1) }, "durableOpTime" : { "ts" : Timestamp(1648484485, 1), "t" : NumberLong(1) }, "lastAppliedWallTime" : ISODate("2022-03-28T16:21:25.007Z"), "lastDurableWallTime" : ISODate("2022-03-28T16:21:25.007Z") }, "lastStableRecoveryTimestamp" : Timestamp(1648484444, 1), "electionCandidateMetrics" : { "lastElectionReason" : "electionTimeout", "lastElectionDate" : ISODate("2022-03-28T15:54:52.986Z"), "electionTerm" : NumberLong(1), "lastCommittedOpTimeAtElection" : { "ts" : Timestamp(0, 0), "t" : NumberLong(-1) }, "lastSeenOpTimeAtElection" : { "ts" : Timestamp(1648482892, 1), "t" : NumberLong(-1) }, "numVotesNeeded" : 1, "priorityAtElection" : 1, "electionTimeoutMillis" : NumberLong(10000), "newTermStartDate" : ISODate("2022-03-28T15:54:52.991Z"), "wMajorityWriteAvailabilityDate" : ISODate("2022-03-28T15:54:53.098Z") }, "members" : [ { "_id" : 0, "name" : "mongodb1:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 1632, "optime" : { "ts" : Timestamp(1648484485, 1), "t" : NumberLong(1) }, "optimeDate" : ISODate("2022-03-28T16:21:25Z"), "lastAppliedWallTime" : ISODate("2022-03-28T16:21:25.007Z"), "lastDurableWallTime" : ISODate("2022-03-28T16:21:25.007Z"), "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "", "electionTime" : Timestamp(1648482892, 2), "electionDate" : ISODate("2022-03-28T15:54:52Z"), "configVersion" : 4, "configTerm" : 1, "self" : true, "lastHeartbeatMessage" : "" }, { "_id" : 1, "name" : "mongodb2:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 383, "optime" : { "ts" : Timestamp(1648484485, 1), "t" : NumberLong(1) }, "optimeDurable" : { "ts" : Timestamp(1648484485, 1), "t" : NumberLong(1) }, "optimeDate" : ISODate("2022-03-28T16:21:25Z"), "optimeDurableDate" : ISODate("2022-03-28T16:21:25Z"), "lastAppliedWallTime" : ISODate("2022-03-28T16:21:25.007Z"), "lastDurableWallTime" : ISODate("2022-03-28T16:21:25.007Z"), "lastHeartbeat" : ISODate("2022-03-28T16:21:32.731Z"), "lastHeartbeatRecv" : ISODate("2022-03-28T16:21:32.730Z"), "pingMs" : NumberLong(0), "lastHeartbeatMessage" : "", "syncSourceHost" : "mongodb1:27017", "syncSourceId" : 0, "infoMessage" : "", "configVersion" : 4, "configTerm" : 1 }, { "_id" : 2, "name" : "mongodb3:27017", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 355, "lastHeartbeat" : ISODate("2022-03-28T16:21:32.731Z"), "lastHeartbeatRecv" : ISODate("2022-03-28T16:21:32.182Z"), "pingMs" : NumberLong(0), "lastHeartbeatMessage" : "", "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "", "configVersion" : 4, "configTerm" : 1 } ], "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1648484485, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1648484485, 1) }
"optime"과 "optimeDate"는 동일한 값이며 표현 방법만 다릅니다.
optime는 1970년 1월 1일 이후의 시간을 밀리초로 표현("t": 135..)한 UnixTime 이고,
optimeDate는 좀 더 사람이 읽기 편하게 표현되어 있습니다.
stateStr 종류
서버의 상태를 나타내는 문자열입니다. 멤버들은 하트비트를 통해 상태(state)를 서로 주고받습니다.
대표적으로 가질 수 있는 두 가지 상태는 즉 PRIMARY 와 SECONDARY 이고 이외에 멤버들이 가질 수 있는 일반적인 상태는 다음과 같습니다.
- STARTUP : 멤버를 처음 시작할 때의 상태로 MongoDB가 멤버의 리플리카 셋 구성 정보 로드를 시도할 때 이 상태가 됩니다.
구성 정보가 로드 되면 상태가 STARTUP2 로 전환됩니다. - STARTUP2 : 이 상태는 초기 동기화 과정 전반에 걸쳐 지속되는데, 과정은 일반적으로 단 몇 초 동안만 지속됩니다.
복제와 선출을 다루기 위해 몇몇 스레드로 분리되며 다음 상태인 RECOVERING으로 변경됩니다. - RECOVERING : 멤버가 현재 올바르게 작동하지만 읽기 작업은 수행할 수 없음을 의미합니다.
조금 과부화 된 상태로 다양한 상황에서 나타냅니다.
시작 시 멤버는 여러 검사를 수행해 읽기 요청을 받아들이기 전에 유효한 상태인지 확인해야 합니다.
그러므로 모든 멤버는 시작하고 세컨더리가 되기 전에 짧게 RECOVERING 상태를 거치게 됩니다.
멤버는 조각 모음(compacting) 같은 긴 연산이 진행될 때 나 replSetMaintenance 명령에 대한 응답으로 RECOVERING 상태가 될 수 있습니다. - DOWN : 다른 리플리카 셋 멤버로 부터 네트워크 등의 문제로 연락이 불가능한 멤버에 대해서 DOWN 으로 표기됩니다.
멤버가 살아 있지만 상태 확인이 될 수 없는 상태가 되었을 경우 DOWN으로 표기될 수 있습니다.
그래서 단지 네트워크 문제 때문에 상태 체크가 도달할 수 없을 수 있는 상태를 의미하기도 합니다. - UNKNOWN : 멤버가 다른 멤버에 도달한 적이 없었다면 상태를 전혀 알 수 없었으므로 UNKNOWN으로 상태가 표기됩니다.
이는 일반적으로 알 수 없는 멤버가 다운되거나 두 멤버 간에 네트워크의 문제가 있음을 나타냅니다. - ARBITER : ARBITER는 데이터를 복제하지 않으며 오로지 선거에 참여하기 위해 존재합니다. 투표할 자격이 있습니다.
- REMOVED : 멤버가 리플리카 셋으로 부터 제거된 상태입니다. 제거된 멤버가 리플리카 셋에 다시 추가되면 정상적인 상태로 변환됩니다.
- ROLLBACK : 멤버가 데이터를 롤백 할 때 표기됩니다. 롤백 과정 마지막에서 서버는 RECOVERING 상태로 전환되고 그 다음 세컨더리가 됩니다.
rs.hello()
hello 명령어는 mongod 인스턴스의 role에 대한 정보를 확인하는 명령어입니다.
(Primary,secondary 노드 정보 확인)
hello 는 4.4.2 버전(and 4.2.10, 4.0.21, and 3.6.21) 에서 새로 추가되었으며, isMaster 명령어가 Deprecated 되면서 hello 명령어로 대체되었습니다.
test-rs-0:PRIMARY> rs.hello() { "topologyVersion" : { "processId" : ObjectId("6241da2d826dbf781c4e053e"), "counter" : NumberLong(9) }, "hosts" : [ "mongodb1:27017", "mongodb2:27017" ], "arbiters" : [ "mongodb3:27017" ], "setName" : "test-rs-0", "setVersion" : 4, "isWritablePrimary" : true, "secondary" : false, "primary" : "mongodb1:27017", "me" : "mongodb1:27017", "electionId" : ObjectId("7fffffff0000000000000001"), "lastWrite" : { "opTime" : { "ts" : Timestamp(1648484705, 1), "t" : NumberLong(1) }, "lastWriteDate" : ISODate("2022-03-28T16:25:05Z"), "majorityOpTime" : { "ts" : Timestamp(1648484705, 1), "t" : NumberLong(1) }, "majorityWriteDate" : ISODate("2022-03-28T16:25:05Z") }, "maxBsonObjectSize" : 16777216, "maxMessageSizeBytes" : 48000000, "maxWriteBatchSize" : 100000, "localTime" : ISODate("2022-03-28T16:25:05.678Z"), "logicalSessionTimeoutMinutes" : 30, "connectionId" : 1, "minWireVersion" : 0, "maxWireVersion" : 9, "readOnly" : false, "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1648484705, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1648484705, 1) }
접속
Replica set 으로 접속하기 위해서 Connection String Format 으로는 Replica set 구성의 멤버 인스턴스(서버)의 호스트 이름 또는 IP 을 입력하면 되며, replicaSet 옵션을 사용해야 합니다.
(물론 직접 접속도 가능은 합니다)
RS Connection String Format
mongo "mongodb://mongodb1:27017,mongodb2:27017,mongodb3:27017/?replicaSet=test-rs-0"
include user credentials(유저 인증) - example
mongo "mongodb://myDBReader:D1fficultP%40ssw0rd@mongodb0.example.com:27017,mongodb1.example.com:27017,mongodb2.example.com:27017/?authSource=admin&replicaSet=myRepl"
유저 인증 설정이 되어 있어서 계정 정보 입력하여 접속을 해야 할 경우 위의 예제와 같이 접속하며, 포스팅 환경에서는 다음과 같이 접속합니다. 계정명 admin , 비밀번호 admin 일 경우입니다.
## 아이디와 패스워드를 함께 입력 mongo "mongodb://admin:admin@mongodb1:27017,mongodb2:27017,mongodb3:27017/?authSource=admin&replicaSet=test-rs-0" ## 아이디만 입력, 패스워드는 대화형으로 입력 mongo "mongodb://admin@mongodb1:27017,mongodb2:27017,mongodb3:27017/?authSource=admin&replicaSet=test-rs-0"
authSource=admin는 MongoDB 인스턴스에 연결할 때 사용자 인증 정보를 찾을 데이터베이스를 지정하는 옵션입니다. 이 옵션을 사용하면 사용자가 인증 정보를 찾을 데이터베이스를 지정할 수 있습니다.
이 경우, admin 데이터베이스에서 사용자 인증 정보를 찾습니다. 이 옵션을 생략하면 기본값으로 admin 데이터베이스에서 사용자 인증 정보를 찾습니다.
사용자 이름 또는 암호에 다음 문자가 포함된 경우 percent encoding을 통해서 문자를 변환해서 사용해야 합니다.
: / ? # [ ] @
예를 들어서 패스워드가 Ad:min?123 일 경우에 Ad%3Amin%3F123 입력하면 됩니다.
mongo "mongodb://admin:Ad%3Amin%3F123@mongodb1:27017,......
각 문자 별 percent encoding에 대한 정보는 여러 곳에서 확인할 수 있으며 아래 링크에서도 확인할 수 있습니다.
Read Preference 와 maxStalenessSeconds
maxStalenessSeconds 는 MongoDB 3.4버전 부터 readPreference 옵션 설정할 시 사용할 수 있는 옵션으로 maxStalenessSeconds 에서 지정한 시간보다 복제 지연(Replication Gap) 이 낮은 Secondary 멤버에서 읽기 작업을 수행을 하게 되며 모든 Secondary 노드의 복제 지연 시간이 maxStalenessSeconds 보다 크다면 Secondary 에서 읽기 작업을 수행하지 않게 됩니다.
Read Preference 관련하여 추가 옵션을 설정하여 접속할 수 있습니다.
읽기 쿼리를 수행할 노드에 관한 설정과 관련되어 있습니다. 기본 값은 primary 이며 사용할 수 있는 항목과 의미는 다음과 같습니다.
- primary : Primary 멤버에서 읽기 쿼리를 실행되며, primary 가 없는 경우 에러가 발생하게 됩니다.
- secondary : Secondary 멤버에서 읽기 쿼리를 실행되며, Secondary 가 없는 경우 에러가 발생하게 됩니다.
- primaryPreferred: Primary 에서 읽기 쿼리를 실행되며, 장애 조치 상황에서와 같이 Primary 가 없는 경우에는 Secondary 에서 읽기 쿼리를 실행하게 되며, maxStalenessSeconds 충족을 고려하게 됩니다.
- secondaryPreferred: Secondary 멤버에서 읽기 쿼리를 실행되며, 장애 조치 상황에서와 같이 Primary 이외 다른 구성 멤버가 없을 경우(Secondary 가 없는 경우)에는 Primary 에서 읽기 쿼리를 실행하게 되게 됩니다.
또한 maxStalenessSeconds 설정 충족을 고려하게 됩니다.
- nearest : network latency 기준으로 클라이언트로부터 가까이 있는 멤버에서 읽기 쿼리를 실행
read preference / maxStalenessSeconds
read preference 과 maxStalenessSeconds 를 사용할 경우 아래와 같은 Connection String Format 을 사용하면 되며, 아래 에서는 Read Preference Mode: secondary , maxStalenessSeconds :120 (seconds) 로 설정 하였습니다.
mongo "mongodb://mongodb1:27017,mongodb2:27017,mongodb3:27017/?replicaSet=test-rs-0&readPreference=secondary&maxStalenessSeconds=120"
[참고] Replication Gap 확인
복제 지연에 관한 정보는 rs.printSecondaryReplicationInfo 명령어를 이용하면 됩니다.
이전에 사용한 명령어는 rs.printSlaveReplicationInfo() 으로 MongoDB 4.4 기준으로 Deprecated 되었습니다.
test-rs-0:PRIMARY> rs.printSecondaryReplicationInfo() source: mongodb2:27017 syncedTo: Tue Mar 29 2022 15:07:20 GMT+0900 (KST) 0 secs (0 hrs) behind the primary <--!! 지연 없음
포스팅에서는 2개의 Secondary 멤버 중에 1개는 Arbiter 로 동작하기 때문에 복제 지연에 정보가 1개 만 출력이 되고 있습니다.
데이터 입력 과 조회 테스트
데이터 입력에 관한 확인 및 테스트를 위해서 각 서버에서 직접 접속하여 테스트 내역을 실행해보도록 하겠습니다.
쓰기 : Primary 노드
use test db.users.insertOne({username:"smith"}) { "acknowledged" : true, "insertedId" : ObjectId("6240447cb8fd7276da8fc7f2") } <-- 특이사항 없음
쓰기 : Secondary 노드
use test db.users.insertOne({username:"Martin"}) uncaught exception: WriteCommandError({ "topologyVersion" : { "processId" : ObjectId("6241da2f1897476469d67209"), "counter" : NumberLong(4) }, < .. 중략 .. > }) : WriteCommandError({ "topologyVersion" : { "processId" : ObjectId("6241da2f1897476469d67209"), "counter" : NumberLong(4) < .. 중략 .. > }) WriteCommandError@src/mongo/shell/bulk_api.js:417:48 executeBatch@src/mongo/shell/bulk_api.js:915:23 Bulk/this.execute@src/mongo/shell/bulk_api.js:1163:21 DBCollection.prototype.insertOne@src/mongo/shell/crud_api.js:264:9 @(shell):1:1 <--!! 에러 발생됨
읽기 : Secondary 노드
db.users.find() Error: error: { "topologyVersion" : { "processId" : ObjectId("62403cfa4547e0fa4489caf5"), "counter" : NumberLong(4) }, "operationTime" : Timestamp(1648381503, 1), "ok" : 0, "errmsg" : "not master and slaveOk=false", "code" : 13435, "codeName" : "NotPrimaryNoSecondaryOk", "$clusterTime" : { "clusterTime" : Timestamp(1648381503, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } } } <--!! Secondary 에서 읽기 에러가 발생함
Replica set 에서 Secondary 멤버로 직접 접속하여 읽기 작업은 기본적으로 비허용 되어 있으며, 쿼리를 수행이 필요할 경우 secondaryOk 을 사용하면 읽기 쿼리 수행이 가능해집니다.
MongoDB 4.4 기준으로 slaveOk 는 deprecated 되었으며 secondaryOk 명령어로 대체되었습니다.
secondaryOk 설정 후 조회
## 설정 상태 조회 test-rs-0:SECONDARY> db.getMongo().getSecondaryOk() false <--!! false 이고 조회 불가 ## db.getMongo().setSecondaryOk() 또는 rs.secondaryOk() 실행 test-rs-0:SECONDARY> db.getMongo().setSecondaryOk() ## 설정 상태 조회 test-rs-0:SECONDARY> db.getMongo().getSecondaryOk() true <--!! ## 데이터 조회 시도 : 조회 가능 test-rs-0:SECONDARY> db.users.find() { "_id" : ObjectId("6240447cb8fd7276da8fc7f2"), "username" : "smith" }
위의 예제는 setSecondaryOk 과 getSecondaryOk 명령어에 대한 사용 예시와 Secondary 멤버에서의 쿼리 수행에 대한 설정을 확인해보고자 진행한 내역이며, 일반적인 경우는 위에서 설명한 Replica set Connection String 에서 Read Preference 옵션을 사용하여 읽기 쿼리를 Secondary 에서 수행하는 것이 적절한 방법이라고 생각 합니다.
위에서 setSecondaryOk로 enable 한 설정을 다시 disable(원복) 위해서는 다음과 같이 진행합니다.
## 설정 상태 조회 test-rs-0:SECONDARY> db.getMongo().getSecondaryOk() true ## false 옵션을 사용 test-rs-0:SECONDARY> db.getMongo().setSecondaryOk(false) ## 설정 상태 다시 조회 test-rs-0:SECONDARY> db.getMongo().getSecondaryOk() false
이어지는 다음 글
Reference
Reference Link
• mongodb.com/replication
• mongodb.com/4.2-compatibility
• mongodb.com/4.4-compatibility
• mongodb.com/rs.remove
• mongodb.com/isMaster
• mongodb.com/hello
• mongodb.com/replace-replica-set-member
• mongodb.com/replica-set-arbiter
• mongodb.com/add-replica-set-arbiter
• mongodb.com/connection-string
• mongodb.com/difference-between-setslaveok
• mongodb.com/replica-states
Reference Book
• Real MongoDB
연관된 다른 글
Principal DBA(MySQL, AWS Aurora, Oracle)
핀테크 서비스인 핀다에서 데이터베이스를 운영하고 있어요(at finda.co.kr)
Previous - 당근마켓, 위메프, Oracle Korea ACS / Fedora Kor UserGroup 운영중
Database 외에도 NoSQL , Linux , Python, Cloud, Http/PHP CGI 등에도 관심이 있습니다
purityboy83@gmail.com / admin@hoing.io
getSecondaryOk 설정 후 조회 - ## getSecondaryOk 실행 에서
rs.getSecondaryOk() -> rs.secondaryOk() 로 수정이 필요합니다!
좋은 글 감사합니다.
많은 도움이 되었습니다.
안녕하세요
글 작성 과정에서 잘못 복사가 된 것 같네요
피드백 주신 부분은 본문에 수정 반영하였습니다.
좋은 피드백 감사하며, 좋은 하루 되세요!
찾던 내용인데, 고맙습니다. ㅎㅎ
안녕하세요
도움이 되었다니 다행이십니다.
댓글 감사합니다.
좋은 하루 되세요