티스토리 뷰
728x90
반응형
10.1 복제 소개
- 복제는 데이터의 동일한 복사본을 여러 서버상에서 보관하는 방법
- 실제 서비스를 배포할 때 권장
- 한 대 또는 그 이상의 서버에 이상이 발생하더라도 복제 애플리케이션이 정상적으로 동작하게 하고 데이터를 안전하게 보존
- 복제 셋은 클라이언트 요청을 처리하는 프라이머리 서버 한대,
- 프라이머리 데이터의 복사본을 갖는 세컨더리 서버 여러 대로 구성
- 프라이머리 서버에 장애가 발생하면 세컨더리 서버 중 새로운 프라이머리 서버를 선출
- 복제를 사용하는 상태에서 서버가 다운되면, 복제 셋에 잇는 다른 서버를 통해 데이터에 접근 가능
10.2 복제 셋 설정
각 별도의 터미널에서 mongod 실행
mongod --replSet mdbDefGuide --dbpath ~/data/rs1 --port 27017 --oplogSize 200
mongod --replSet mdbDefGuide --dbpath ~/data/rs2 --port 27018 --oplogSize 200
mongod --replSet mdbDefGuide --dbpath ~/data/rs3 --port 27019 --oplogSize 200
10.3 네트워크 고려 사항
인스턴스를 각기 다른 서버의 멤버와 함께 복제 셋의 멤버로 실행하려면
명령행 매개변수 --bint_ip 를 지정하거나 인스턴스 구성 파일에 있는 bind_ip를 사용
mongod --bind_ip localhost,192.51.100.1 --replSet mdbDefGuide --dppath ~/data/rs1 --port 27017 --oplogSize 200
10.4 보안 고려 사항
복제 셋을 구성할 때, 권한 제어를 활성화하고 인증 메커니즘을 지정해야함
디스크의 데이터를 암호화하고, 복제 셋 멤버 간 통신 및 셋과 클라이언트 간 통신을 암호화
10.5 복제 셋 설정
- 환경설정 문서를 작성한 후 rs.initiate() 에 전달하여 복제 set을 시작
- 3개의 구성원이 포함된 replica set이 시작되고, 복제본 set이 형성되도록 구성이 나머지 mongod로 전파
test> rsconf = {
... _id : "mdbDefGuide",
... members: [
... {_id : 0, host:"localhost:27017"},
... {_id : 1, host:"localhost:27018"},
... {_id : 2, host:"localhost:27019"}
... ]
... }
{
_id: 'mdbDefGuide',
members: [
{ _id: 0, host: 'localhost:27017' },
{ _id: 1, host: 'localhost:27018' },
{ _id: 2, host: 'localhost:27019' }
]
}
test> rs.initiate(rsconf)
{ ok: 1 }
복제 셋의 상태는 rs.status()
mdbDefGuide [direct: other] test> rs.status()
{
set: 'mdbDefGuide',
date: ISODate("2023-07-11T07:32:51.737Z"),
myState: 1,
term: Long("1"),
syncSourceHost: '',
syncSourceId: -1,
heartbeatIntervalMillis: Long("2000"),
majorityVoteCount: 2,
writeMajorityCount: 2,
votingMembersCount: 3,
writableVotingMembersCount: 3,
optimes: {
lastCommittedOpTime: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
lastCommittedWallTime: ISODate("2023-07-11T07:32:42.146Z"),
readConcernMajorityOpTime: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
appliedOpTime: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
durableOpTime: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
lastAppliedWallTime: ISODate("2023-07-11T07:32:42.146Z"),
lastDurableWallTime: ISODate("2023-07-11T07:32:42.146Z")
},
lastStableRecoveryTimestamp: Timestamp({ t: 1689060712, i: 1 }),
electionCandidateMetrics: {
lastElectionReason: 'electionTimeout',
lastElectionDate: ISODate("2023-07-11T07:31:11.651Z"),
electionTerm: Long("1"),
lastCommittedOpTimeAtElection: { ts: Timestamp({ t: 1689060659, i: 1 }), t: Long("-1") },
lastSeenOpTimeAtElection: { ts: Timestamp({ t: 1689060659, i: 1 }), t: Long("-1") },
numVotesNeeded: 2,
priorityAtElection: 1,
electionTimeoutMillis: Long("10000"),
numCatchUpOps: Long("0"),
newTermStartDate: ISODate("2023-07-11T07:31:11.983Z"),
wMajorityWriteAvailabilityDate: ISODate("2023-07-11T07:31:12.753Z")
},
members: [
{
_id: 0,
name: 'localhost:27017',
health: 1,
state: 1,
stateStr: 'PRIMARY',
uptime: 1043,
optime: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
optimeDate: ISODate("2023-07-11T07:32:42.000Z"),
lastAppliedWallTime: ISODate("2023-07-11T07:32:42.146Z"),
lastDurableWallTime: ISODate("2023-07-11T07:32:42.146Z"),
syncSourceHost: '',
syncSourceId: -1,
infoMessage: '',
electionTime: Timestamp({ t: 1689060671, i: 1 }),
electionDate: ISODate("2023-07-11T07:31:11.000Z"),
configVersion: 1,
configTerm: 1,
self: true,
lastHeartbeatMessage: ''
},
{
_id: 1,
name: 'localhost:27018',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 111,
optime: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
optimeDurable: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
optimeDate: ISODate("2023-07-11T07:32:42.000Z"),
optimeDurableDate: ISODate("2023-07-11T07:32:42.000Z"),
lastAppliedWallTime: ISODate("2023-07-11T07:32:42.146Z"),
lastDurableWallTime: ISODate("2023-07-11T07:32:42.146Z"),
lastHeartbeat: ISODate("2023-07-11T07:32:49.833Z"),
lastHeartbeatRecv: ISODate("2023-07-11T07:32:50.867Z"),
pingMs: Long("0"),
lastHeartbeatMessage: '',
syncSourceHost: 'localhost:27017',
syncSourceId: 0,
infoMessage: '',
configVersion: 1,
configTerm: 1
},
{
_id: 2,
name: 'localhost:27019',
health: 1,
state: 2,
stateStr: 'SECONDARY',
uptime: 111,
optime: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
optimeDurable: { ts: Timestamp({ t: 1689060762, i: 1 }), t: Long("1") },
optimeDate: ISODate("2023-07-11T07:32:42.000Z"),
optimeDurableDate: ISODate("2023-07-11T07:32:42.000Z"),
lastAppliedWallTime: ISODate("2023-07-11T07:32:42.146Z"),
lastDurableWallTime: ISODate("2023-07-11T07:32:42.146Z"),
lastHeartbeat: ISODate("2023-07-11T07:32:49.833Z"),
lastHeartbeatRecv: ISODate("2023-07-11T07:32:51.336Z"),
pingMs: Long("0"),
lastHeartbeatMessage: '',
syncSourceHost: 'localhost:27018',
syncSourceId: 1,
infoMessage: '',
configVersion: 1,
configTerm: 1
}
],
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1689060762, i: 1 }),
signature: {
hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
keyId: Long("0")
}
},
operationTime: Timestamp({ t: 1689060762, i: 1 })
}
10.6 복제 관찰
isMaster() 으로 rs.status() 보다 간결한 형태로 복제 셋의 상태 출력
mdbDefGuide [direct: primary] test> db.isMaster()
{
topologyVersion: {
processId: ObjectId("64ad01909b0affd66e870a7e"),
counter: Long("6")
},
hosts: [ 'localhost:27017', 'localhost:27018', 'localhost:27019' ],
setName: 'mdbDefGuide',
setVersion: 1,
ismaster: true,
secondary: false,
primary: 'localhost:27017',
me: 'localhost:27017',
electionId: ObjectId("7fffffff0000000000000001"),
lastWrite: {
opTime: { ts: Timestamp({ t: 1689064253, i: 1 }), t: Long("1") },
lastWriteDate: ISODate("2023-07-11T08:30:53.000Z"),
majorityOpTime: { ts: Timestamp({ t: 1689064253, i: 1 }), t: Long("1") },
majorityWriteDate: ISODate("2023-07-11T08:30:53.000Z")
},
maxBsonObjectSize: 16777216,
maxMessageSizeBytes: 48000000,
maxWriteBatchSize: 100000,
localTime: ISODate("2023-07-11T08:30:57.018Z"),
logicalSessionTimeoutMinutes: 30,
connectionId: 35,
minWireVersion: 0,
maxWireVersion: 17,
readOnly: false,
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1689064253, i: 1 }),
signature: {
hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
keyId: Long("0")
}
},
operationTime: Timestamp({ t: 1689064253, i: 1 }),
isWritablePrimary: true
}
27019에 대한 연결을 인스턴스화해서 세컨더리 사용
세컨더리는 기본적으로 읽기 요청을 거부
setReadPref('secondary') 로 읽기 허용
세컨더리는 쓰기 불가
mdbDefGuide [direct: primary] test> secondaryConn = new Mongo("localhost:27019")
mongodb://localhost:27019/?directConnection=true&serverSelectionTimeoutMS=2000
mdbDefGuide [direct: secondary] test> secondaryDB = secondaryConn.getDB("test")
test
//읽기 불가
mdbDefGuide [direct: secondary] test> secondaryDB.coll.find()
MongoServerError: not primary and secondaryOk=false - consider using db.getMongo().setReadPref() or readPreference in the connection string
mdbDefGuide [direct: secondary] test> secondaryConn.setSlaveOk()
//에러
MongoshDeprecatedError: [COMMON-10003] Setting slaveOk is deprecated, use setReadPref instead.
mdbDefGuide [direct: secondary] test> secondaryConn.setReadPref('secondary')
//읽기 가능
mdbDefGuide [direct: secondary] test> secondaryDB.coll.find()
[
{ _id: ObjectId("64ad11e2ff820f08127df906"), count: 0 },
{ _id: ObjectId("64ad11e2ff820f08127df907"), count: 1 },
{ _id: ObjectId("64ad11e2ff820f08127df909"), count: 3 },
{ _id: ObjectId("64ad11e2ff820f08127df90a"), count: 4 },
{ _id: ObjectId("64ad11e2ff820f08127df908"), count: 2 },
//쓰기 불가
mdbDefGuide [direct: secondary] test> secondaryDB.coll.insert({"count" : 1001})
Uncaught:
MongoBulkWriteError: not primarymdbDefGuide [direct: secondary] test> secondaryDB.coll.insert({"count" : 1001})
Uncaught:
MongoBulkWriteError: not primary
장애 조치
프라이머리가 중단되면 세컨더리 중 하나가 자동으로 프라이머리로 선출
프라이머리의 중단을 가장 먼저 발견한 세컨더리가 선출
mdbDefGuide [direct: secondary] test> db.adminCommand({"shutdown" : 1})
MongoNetworkError: connection 1 to 127.0.0.1:27017 closed
test> secondaryDB.isMaster()
{
topologyVersion: {
processId: ObjectId("64acfe82a4c8ceec5bcc9d18"),
counter: Long("1064")
},
hosts: [ 'localhost:27017', 'localhost:27018', 'localhost:27019' ],
setName: 'mdbDefGuide',
setVersion: 1,
ismaster: false,
secondary: true,
primary: 'localhost:27018',
me: 'localhost:27019',
lastWrite: {
opTime: { ts: Timestamp({ t: 1689126873, i: 1 }), t: Long("544") },
lastWriteDate: ISODate("2023-07-12T01:54:33.000Z"),
majorityOpTime: { ts: Timestamp({ t: 1689126873, i: 1 }), t: Long("544") },
majorityWriteDate: ISODate("2023-07-12T01:54:33.000Z")
},
maxBsonObjectSize: 16777216,
maxMessageSizeBytes: 48000000,
maxWriteBatchSize: 100000,
localTime: ISODate("2023-07-12T01:54:40.450Z"),
logicalSessionTimeoutMinutes: 30,
connectionId: 3537,
minWireVersion: 0,
maxWireVersion: 17,
readOnly: false,
ok: 1,
'$clusterTime': {
clusterTime: Timestamp({ t: 1689126873, i: 1 }),
signature: {
hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0),
keyId: Long("0")
}
},
operationTime: Timestamp({ t: 1689126873, i: 1 }),
isWritablePrimary: false
}
핵심 개념 3가지
- 클라이언트는 독립 실행형 서버에 보낼 수 있는 모든 작업을 프라이머리 서버에 보낼 수 있다. (읽기, 쓰기, 명령, 인덱스 구축 등)
- 클라이언트는 세컨더리에 쓰기를 할 수 없다
- 기본적으로 클라이언트는 세컨더리로부터 읽을 수 없다. 세컨더리에서 읽고 있음을 알수 있다를 뜻하는 설정을 연결에 명시적으로 설정하면 읽기를 활성화
10.7 복제 셋 구성 변경
복제 셋에 새로운 멤버를 추가할 때는 rs.add
제거할 때는 rs.remove
재구성 성공 여부는 rs.config()으로 현재 구성을 출력
> rs.add("localhost:27020")
> rs.remove("localhost:27017")
> rs.config()
수정을 하려면 구성 도큐먼트를 만들고 rs.reconfig 호출
> var config = rs.config()
> config.members[0].host = "localhost:27017"
> rs.reconfig(config)
10.8 복제 셋 설계 방법
- 프라이머리는 과반수 이상이어야만 프라이머리 자격 유지
- 쓰기는 과반수 이상에 복제되면 안전
- 과반수는 복제 셋 내 모든 멤버의 절반보다 많은 것
- 과반수는 복제 셋의 구성에 따라 산정되므로, 얼마나 많은 멤버가 다운되거나 사용할 수 없는 상태인지는 관련 X
- 네트워크 파티션의 경우 파티션 양쪽에서 프라이머리를 선출하기를 원하지 않는데,
- 복제 셋이 두개의 프라이머리를 갖게 될 수 있기 때문에 방지하는 해결책
- 몽고DB는 오직 단일 프라이머리만 지원
- 개발이 쉬워질 수 있지만 복제 셋이 읽기 전용 상태일 때는 어느 정도 시간이 걸림
10.8.1 어떻게 선출하는가
- 요청받은 멤버가 프라이머리에 도달할 수 있는가?
- 선출되고자 하는 멤버의 복제 데이터가 최신인가?
- 대신 선출돼야 하는 우선순위가 더 높은 멤버는 없는가?
10.9 멤버 구성 옵션
10.9.1 우선순위
- 우선순위는 특정 멤버가 얼마나 프라이머리가 되기를 원하는지 나타내는 지표
- "priority"의 절대값은 다른 복제 셋 멤버의 우선순위와 비교해 큰지 혹은 작은지만 중요
> rs.add({"host": "server-4:27017", "priority":1.5})
10.9.2 숨겨진 멤버
- 클라이언트는 숨겨진 멤버에 요청을 라우팅하지 않음
- 숨겨진 멤버는 복제 소스로서 바람직하지 않음
- 덜 강력한 서버 또는 백업 서버를 숨김
- hidden : true 필드를 구성에 추가해야함
- 우선순위는 0
- 클라이언트는 복제 셋에 연결할 때 isMaster()를 호출해 복제 셋 멤버를 확인하는데 숨겨진 멤버는 hidden
> var config = rs.config()
> config.members[2].hidden = true
> config.members[2].priority = 0
10.9.3 아비터 선출
- 프라이머리 선출에 참여하는 용도로만 사용되는 특수한 멤버
- 데이터를 가지지 않으며 클라이언트에 의해 사용되지 않음
- 오로지 멤버 복제 셋에서 과반수를 구성하는데 사용
- 아비터는 복제 셋에 추가되고 나면 영원히 아비터
> rs.addArb("server-5:27017") //아비터를 복제 셋에 추가
> rs.add("_id" : 4, "host" : "server-5:27017", "arbiterOnly" : true}) //멤버 구성에서 아비터 옵션
- 큰 클러스터상에서 동정 상황을 없앨 수 있다는 장점
- 아비터는 최대 하나까지만 사용
- 노드의 갯수가 홀수이면 아바타는 필요 없음
- 데이터 노드와 아비터 중 하나를 골라야 한다면 데이터 노드를 선택
10.9.4 인덱스 구축
- 세컨더리를 데이터 백업이나 오프라인 배치 작업에만 사용한다면 "buildIndexes" : false 를 멤버구성에 명시
- 세컨더리가 인덱스를 구축하지 않도록하는 옵션
- 영구적인 설정
- 멤버의 우선순위는 0
728x90
반응형
'DB > MongoDB' 카테고리의 다른 글
[MongoDB] 애플리케이션에서 복제 셋 연결 (0) | 2023.08.01 |
---|---|
[MongoDB] 복제 셋 구성 요소 (0) | 2023.07.27 |
[MongoDB] 트랜잭션 (0) | 2023.07.02 |
[MongoDB] 애플리케이션 설계 (0) | 2023.06.20 |
[MongoDB] 집계 프레임워크 (0) | 2023.05.10 |
반응형
300x250