DB/몽고DB 완벽 가이드 3판

[13장] 복제 셋 관리

꾸준함. 2025. 4. 26. 12:49

1. 독립 실행형 모드에서 멤버 시작

  • 많은 유지 보수 작업은 쓰기와 관련되어 있어 Secondary에서 수행될 수 없으며, 애플리케이션 성능에 영향을 미치기 때문에 Primary에서 수행하면 안 됨
    • 따라서 해당 절에서는 독립 실행형 모드 서버 시작을 자주 언급함
    • 이는 멤버가 복제 셋의 멤버가 아닌 독립 실행형 서버로 재시작함을 의미

 

  • 독립 실행형 모드에서 멤버를 시작하려면 먼저 명령행 인수를 확인해야 하며 인수는 다음과 같다고 가정
    • 해당 서버에서 유지보수를 수행하려면 replSet 옵션 없이 서버를 재시작하면 되며 이는 일반적인 독립 실행형 mongod처럼 읽기와 쓰기가 가능하게 해 줌
    • 복제 셋에 있는 다른 서버에서 해당 서버와 통신하기를 원치 않으므로 다른 멤버들이 서버를 발견하지 못하도록 서버가 다른 포트로 수신하게 함
    • 마지막으로, 서버 데이터를 조작하려고 이런 방식으로 시작하려고 dbpath는 그대로 유지

 

 

 

  • 먼저 mongo 셸에서 서버를 종료한 뒤 운영체제 셸에서 replSet 매개변수 없이 다른 포트에서 mongod를 시작하면 독립 실행형 서버로 구동 가능
    • 독립 실행형 서버로 연결을 위한 포트는 30000번
    • 복제 셋의 다른 멤버는 27017번 포트로 해당 서버에 접속을 시도하고, 곧 서버가 다운됐다고 추정하게 됨

 

 

 

  • 서버에서 유지 보수 수행을 마치면, 서버를 종료하고 원래 옵션으로 재시작 가능
    • 서버는 유지 보수 모드 기간 동안 놓친 연산들을 복제해 복제 셋의 다른 멤버와 자동으로 동기화 진행

 

2. 복제 셋 구성

  • 복제 셋 구성은 항상 local.system.replset 컬렉션의 도큐먼트에 보관되며 해당 도큐먼트는 복제 셋의 모든 멤버에서 같음
    • 절대 update를 이용해 도큐먼트를 변경하지 말고 항상 rs 보조자나 replSetReconfig 명령을 통해 업데이트하는 것을 권장

 

2.1 복제 셋 생성하기

  • 멤버로 만들 mongod들을 시작하고 그중 하나에 rs.initiate()를 사용해 구성 정보를 전달함으로써 복제 셋을 생성함
    • 복제 셋의 한 멤버에서만 rs.initiate를 호출 가능하며 멤버는 구성 정보를 받아 다른 멤버들에 전달함

 

 

 

2.2 복제 셋 멤버 교체하기

  • 복제 셋에 새로운 멤버를 추가할 때는 데이터 디렉토리에 아무것도 존재하지 않거나 다른 멤버 데이터의 복제본이 있어야 함

 

 

 

  • 재구성을 통해 멤버의 설정을 바꿀 수 있으며 멤버의 설정을 바꾸는 데는 다음과 같은 제약 사항이 있음
    • 멤버의 "_id"는 변경할 수 없음
    • 재구성 정보를 전달하려는 멤버의 우선순위를 0으로 할 수 없음
    • 아비터에서 아비터가 아닌 것으로 변경할 수 없으며, 반대도 마찬가지
    • 멤버의 "buildIndexes"를 falsae에서 true로 변경 불가

 

  • 멤버의 "host" 필드는 변경할 수 있으며 호스트명을 변경하려면 다음과 같이 해야 함
    • 호스트 정보를 잘 못 명시했을 때 다음과 같이 간단히 변경 가능


 

2.3 큰 복제 셋 만들기

  • 복제 셋에서 멤버는 50개, 투표 멤버는 7개로 제한됨
    • 이는 다른 멤버에 하트비트를 보내는 데 필요한 네트워크 트래픽량을 줄이고 선출에 걸리는 시간을 제한하기 위함

 

  • 멤버가 7개 이상인 복제 셋을 생성한다면, 모든 추가적인 멤버는 투표권이 0개여야 하며 이를 수행하려면 멤버 구성 정보에 다음처럼 명시해야 함
    • 이는 해당 멤버들이 거부권을 갖더라도 선출 과정에서 투표권을 행사하는 것을 방지함


 

2.4 재구성 강제하기

  • 복제 셋의 과반수를 영구적으로 잃으면, Primary가 없는 상태에서 설정을 재구성할 필요가 있음
    • 이때 Secondary에 재구성 명령을 보냄으로써 복제 셋 재구성을 강제할 수 있음
    • 셸에서 Secondary에 연결하고 "force" 옵션을 이용해 재구성 명령을 전달함으로써 재구성 가능


 

  • 강제 재구성은 일반 재구성과 같은 규칙을 따르며, 사용자는 유효하고 잘 구성된 구성 정보를 올바른 옵션으로 보내야 함
    • "force" 옵션은 유효하지 않은 구성 정보를 허용하지 않고, 다만 Secondary가 재구성 정보를 받아들이도록 허용함

 

  • 강제 재구성은 복제 셋 `version` 번호를 크게 높이는데 이는 재구성 정보가 네트워크 파티션 양쪽에 있을 때 버전 번호가 충돌하지 않도록 하기 위함
    • 수천 단위를 건너뛰는 것이 정상

 

  • Secondary는 재구성 정보를 받으면 자신의 구성 정보를 갱신하고 새로운 구성 정보를 다른 멤버에 전달함
    • 복제 셋의 다른 멤버는 정보를 전송하는 서버를 현재 구성 정보의 멤버로 인식하면 구성의 변화만을 찾아내므로 일부 멤버가 호스트명을 변경하면 예전 호스트명을 그대로 갖는 멤버를 강제 재구성해야 함
    • 모든 멤버가 새로운 호스트명을 가지면 복제 셋의 각 멤버를 다운시키고, 새로운 멤버를 독립 실행형 모드로 시작한 뒤 local.system.replset 도큐먼트를 수동으로 변경하고 멤버를 재시작함

 

3. 멤버 상태 조작

  • 유지 보수를 수행할 때나 부하에 대한 응답으로 수동으로 멤버 상태를 변경하는 방법은 여러 가지지만 
  • 멤버가 Primary가 되도록 강제하려면 복제 셋을 상황에 맞게 구성하는 것 외에는 방법이 없음

 

3.1 Primary에서 Secondary로 변경하기

  • stepDown 함수를 이용하면 Primary를 Secondary로 강등할 수 있음
    • Primary를 60초 동안 SECONDARY 상태로 만듦
    • 해당 기간 동안 다른 Primary가 선출되지 않으면 Secondary 상태로 변했던 Primary는 재선출을 시도할 수 있음
    • 더 길거나 짧은 시간 동안 Secondary 상태로 남겨두려면 몇 초간 SECONDARY 상태로 머물게 할지 지정함


 

3.2 선출 방지하기

  • Primary 상에서 유지 보수 작업을 수행해야 하고, 그 사이에 자격이 있는 다른 멤버가 Primary가 되지 않도록 하려면, 각각의 멤버에 freeze 명령을 실행함으로써 Secondary 상태에 머물게 해야 함
    • 마찬가지로 멤버는 몇 초 동안 Secondary 상태로 유지됨


 

  • 시간이 경과하기 전에 Primary에서 수행 중인 유지 보수를 완료해 다른 멤버의 freeze 상태를 해제하려면, 각 멤버에 타임아웃을 0초로 지정해 명령을 다시 실행
  • freeze 상태가 아닌 멤버는 원한다면 선출을 실시할 수 있고 강등했던 Primary도 rs.freeze(0)를 사용해 freeze 상태를 해제할 수 있음

 

4. 복제 모니터링

  • 복제 셋의 상태를 모니터링하는 것은 중요하며 모든 멤버가 정상적으로 기동 했는지, 멤버가 어떤 상태인지, 그리고 복제가 얼마나 최신 상태인지 모니터링함
  • 몇 가지 명령으로 복제 셋 정보를 볼 수 있음
    • 아틀라스, 클라우드 매니저, 옵스 매니저를 포함한 몽고DB 호스팅 서비스와 관리 도구는, 복제를 모니터링하는 메커니즘과 주요 복제 지표에 대한 대시보드를 제공

 

  • 복제와 관련된 문제는 일시적일 때가 많음
    • 서버가 다른 서버에 도달할 수 없었다가 다시 도달할 수 있게 될 때가 있으며 로그를 확인하면 비슷한 문제를 쉽게 볼 수 있음
    • 로그가 어디에 저장되는지 확인하고, 로그가 잘 저장되며 해당 로그에 접근할 수 있음을 확실히 확인해야 함

 

4.1 상태 정보 가져오기

  • 복제 셋의 모든 멤버의 정보를 얻는 데 가장 유용한 명령은 replSetGetStatus
  • 셸에는 해당 명령에 사용할 수 있는 보조자가 있음
  • 가장 유용한 필드들은 다음과 같음
    • "self": rs.status()가 실행된 멤버에만 존재하며, 예제에는 server-1
    • "stateStr": 서버의 상태를 나타내는 문자열
    • "uptime": 멤버에 도달할 수 있었던 시간 혹은 해당 서버가 "self" 멤버를 위해 시작된 이후부터의 시간
    • "optimeDate": 각 멤버의 oplog에서 마지막 연산 수행 시각을 의미; 이는 하트비트에 의해 보고된 각 멤버의 상태이고, 보고된 연산 수행 시각은 몇 초 정도 차이가 날 수 있음
    • "lastHeartbeat": 서버가 "self" 멤버로부터 하트비트를 받은 시간, 네트워크 문제가 있거나 서버가 분주하면 해당 시간은 2초 전보다 길어질 수 있음
    • "pingMs": 해당 서버에 대한 하트비트에 걸린 평균 실행 시간, 어느 멤버로부터 동기화할지 결정하는 데 사용
    • "errmsg": 멤버가 하트비트 요청에 반환하기로 선택한 모든 상태 메시지, 단지 정보 전달용이며 오류 메시지는 아님


 

4.2 복제 그래프 시각화하기

  • Secondary에서 rs.status()를 시행하면 `syncingTo`라는 최상위 필드를 확인 가능하며 이는 멤버가 복제를 수행하는 호스트를 제공
  • 복제 셋의 각 멤버에서 replSetGetStatus 명령을 실행하면 복제 그래프를 알아낼 수 있음
  • 아래 예제는 server1이 server1에 대한 연결, server2가 server2에 대한 연결이라 가정했음
    • server0은 server1의 복제 소스
    • server1은 server2와 server3의 복제 소스
    • server2는 server4의 복제 소스


 

  • 몽고DB는 핑 시간을 기준으로 동기화할 대상을 결정
    • 멤버는 다른 멤버에 하트비트를 보낼 때, 요청이 처리되기까지 걸리는 시간을 재며 몽고DB는 이러한 평균 실행 시간을 보관함
    • 동기화할 멤버를 선택할 때, 멤버는 가장 가깝고 복제에서 자신보다 앞서 있는 멤버를 찾으므로 복제 순환이 발생하지 않고 멤버는 자신보다 앞서 있는 Primary나 Secondary만 복제함
    • 그러므로 만약 Secondary가 있는 데이터 센터에 새로운 멤버를 투입하면 Primary가 있는 데이터 센터의 멤버보다 Secondary가 있는 데이터 센터와 다른 멤버와 동기화할 가능성이 높으므로 WAN 트래픽을 최소화 시킴

 

  • 그러나 자동 복제 사슬 (automatic replication chaining)에는 몇 가지 단점이 있음
    • 복제 홉이 많을수록 모든 서버에 쓰기를 복제하는 데 시간이 좀 더 오래 걸림
    • ex)  모든 멤버가 하나의 데이터 센터에 있는 상황에서 새로운 멤버를 추가할 때 네트워크 속도가 예상치 않게 변하면, MongoDB가 모든 복제본을 일렬로 복제하는 형태가 될 수 있으며 이런 경우, replSetSyncFrom 명령을 사용해 각 멤버가 복제할 대상을 직접 지정할 수 있음
      • 이를 통해 사슬 구조로 각 Secondary가 앞에 있는 Secondary보다 약간씩 느리게 동기화되도록 설정할 수 있으며, 이는 복제 지연을 단계적으로 분산시키는 바람직한 방법이 될 수 있음

 

4.3 복제 루프

  • 복제 루프는 모든 멤버가 다른 멤버로부터 복제를 수행하는 상태
    • ex) A는 B로부터 동기화하고, B는 C로부터, C는 다시 A로부터 동기화하는 상태
    • 모든 복제 루프 멤버는 Primary가 될 수 없으므로, 멤버들은 복제를 위한 새로운 명령을 받을 수 없고 뒤쳐지게 됨

 

  • 멤버가 동기화할 멤버를 자동으로 고를 때는 복제 루프가 발생하지 않음
    • 그러나 replSetSyncFrom 명령을 이용해 복제 루프를 강제로 수행할 수 있는데 수동으로 동기화 대상을 바꾸기 전에 rs.status() 결과를 주의 깊게 검사하고, 루프를 만들지 않도록 주의해야 함

 

4.4 복제 사슬 비활성화하기

  • 복제 사슬은 Secondary가 또 다른 Secondary와 동기화할 때 발생하며 멤버는 자동으로 다른 멤버와 동기화하도록 결정할 수 있음
  • "chaingingAllowed" 설정을 false로 변경해 모든 멤버가 Primary와 동기화하게 함으로써 복제 사슬을 사용하지 않도록 설정 가능
    • 단, Primary가 이용 불가능한 상태가 되면 Secondary와 동기화하게 됨


 

4.5 지연 계산하기

  • 복제를 추적하는 지표로, Secondary가 얼마나 Primary를 잘 따라잡는지가 중요함
  • 지연은 Secondary가 얼마나 뒤처져 있는지 나타내는데, Primary가 마지막으로 수행한 연산과 Secondary가 마지막으로 적용한 연산의 타임스탬프의 차이를 의미함
  • rs.status()를 사용해 멤버의 복제 상태를 볼 수 있으며, rs.printReplicationInfo()나 rs.printSlaveReplicationInfo()를 실행해 빠른 요약을 얻을 수 있음
    • rs.printReplicationInfo()는 연산의 크기와 날짜 범위를 포함하는 Primary에 oplog의 요약 정보를 제공함
    • 아래 예제에서 oplog는 약 10MB이며 약 1시간의 연산까지만 수행 가능


 

  • 실제 배포라면 oplog가 더 커야 하고 로그 길이는 적어도 전체적으로 재동기화하는 데 걸리는 시간만큼 길게 설정하는 것을 권장
    • 그러면 초기 동기화를 완료하기 전에 Secondary가 oplog의 끝에서 밀려나는 상황은 생기지 않음

 

  • 다음 예제와 같이 rs.printSlaveReplicationInfo() 함수를 사용해 각 멤버의 syncedTo 값과 마지막 oplog 항목이 각 Secondary에 기록된 시간을 가져올 수 있음
    • 복제 셋 멤버의 지연은 절대적인 시간이 아니라 Primary를 기준올 상대적으로 계산됨
    • 보통은 문제가 되지 않지만 쓰기 작업을 매우 드물게 수행하는 시스템에서는 유령 복제 지연인 `트래픽의 급격한 증가`를 발생시킬 수 있음


 

 

4.6 Oplog 크기 변경하기

  • Primary의 oplog는 유지 보수 시간으로 여겨짐
    • Primary의 oplog 길이가 한 시간 정도라면, 잘 못된 부분을 고칠 수 있는 시간은 한 시간 정도라는 의미
    • 따라서 뭔가가 잘못될 때 약간의 숨 쉴 틈이 있으려면 일반적으로 며칠에서 1주 정도 데이터를 보유할 수 있는 oplog가 바람직함

 

  • 아쉽게도 oplog가 얼마나 지속될지는 가득 차기 전까지 쉽게 알 수 없음
  • 와이어드타이거 스토리지 엔진 사용 시 서버가 실행되는 동안 oplog 크기를 조정할 수 있음
  • oplog의 크기를 늘리려면 다음 단계를 수행해야 함
    • 복제 셋 멤버에 연결하고 인증이 활성화됐다면 local 데이터베이스를 수정할 권한이 있는 사용자를 사용
    • oplog의 현재 크기를 확인
    • 복제 셋 멤버의 oplog 크기를 변경
    • 마지막으로 oplog의 크기를 줄였다면 compact를 실행해 할당된 디스크 공간을 회수해야 할 수도 있지만 대상 멤버가 Primary인 동안에는 실행해서는 안 됨


 

  • 일반적으로 oplog의 크기는 줄이면 안 됨
    • oplog 크기가 수개월이더라도 일반적으로 디스크 공간을 충분하며 램이나 CPU 같은 귀중한 리소스를 소모하지 않음

 

4.7 인덱스 구축하기

  • Primary에 인덱스 구축을 전송하면 Primary는 정상적으로 인덱스를 구축하며, Secondary는 "build index" 연산을 복제할 때 인덱스를 구축함
    • 이는 인덱스를 구축하는 가장 쉬운 방법이긴 하지만, 인덱스 구축은 멤버를 이용 불가능한 상태로 만들 수도 있는 리소스 집약적인 연산

 

  • 만약 모든 Secondary가 동시에 인덱스를 구축하기 시작한다면 복제 셋의 거의 모든 멤버는 인덱스 구축이 완료될 때까지 오프라인 상태가 되며 해당 과정은 복제 셋에만 해당됨
  • 따라서 애플리케이션에 대한 영향을 최소화하려면 인덱스는 한 번에 한 멤버씩 구축하는 것이 바람직하며 이를 수행하려면 다음과 같이 수행해야 함
    • Secondary를 종료함
    • 종료한 Secondary를 독립 실행형 서버로 재시작
    • 재시작한 서버에 인덱스 구축
    • 인덱스 구축이 완료되면 서버를 복제 셋 멤버로 재시작하며 멤버를 재시작할 때 명령행 옵션이나 구성 파일에 disableLogicalSessionCacheRefresh 매개변수가 있으면 제거해야 함
    • 복제 셋의 각 Secondary에 1단계부터 4단계까지 반복함

 

  • 이제 셋에서 Primary를 제외한 모든 멤버의 인덱스가 구축되었고 다음 두 가지 선택지 중 실제 시스템에 가장 적게 영향을 미치는 방법을 선택해야 함
    • Primary에 인덱스를 구축하는데 트래픽이 적을 때 "off" 시간을 가질 수 있다면 이때 구축하는 것을 권장
      • 읽기 선호도를 수정함으로써 구축이 진행 중일 때 Secondary로 더 많은 부하를 보내도록 일시적으로 경로를 변경할 수 있음
      • Primary는 인덱스 구축을 Secondary에 복제하지만, Secondary는 이미 인덱스가 있으므로 어떤 연산 작업도 발생하지 않음
    • Primary를 강등한 후 앞서 설명한 2~4 단계를 거침
      • 해당 방식에는 장애 조치가 필요하지만 기존 Primary가 인덱스를 구축하는 동안 정상적으로 작동하는 Primary가 있음
      • 인덱스 구축이 완료된 후 기존 Primary를 복제 셋에 재도입할 수 있음

 

  • 위 방법으로 Secondary에서 다른 복제 셋 멤버의 인덱스와 다른 인덱스를 구축할 수 있으며 이는 오프라인 처리에 유용할 수 있음
    • 다만 다른 멤버는 절대 Priamry가 될 수 없으며 우선순위가 항상 0임

 

  • 고유 인덱스를 구축할 때는 Primary가 중복 삽입을 하지 않아야 하며, 인덱스를 Primary에 먼저 구축해야 함
    • 그렇지 않으면 Primary는 중복 삽입을 하게 되고 Secondary에 복제 오류를 발생시킴
    • 이러한 오류가 발생하면 Secondary는 스스로 종료하며, 독립 실행형으로 재시작해 고유 인덱스를 삭제한 뒤 재시작해야 함

 

4.8 한정된 예산에서 복제하기

  • 고성능 서버를 두 개 이상 구하기 어렵다면, 적은 램과 CPU, 속도가 느린 디스크 입출력을 갖는 재해 복구용 Secondary 서버를 권장
    • 좋은 서버는 항상 Primary로 쓰고, 상대적으로 값싼 서버는 절대 클라이언트 트래픽을 처리하지 않게 해야 함

 

  • 다음은 값싼 서버를 설정하기 위한 옵션
    • "priority": 0 => 해당 서버가 절대 Primary가 되지 않도록 설정
    • "hidden": true => 클라이언트가 해당 Secondary에 읽기 요청을 절대 보내지 않도록 설정
    • "buildIndexes": false => 선택적이지만 해당 서버가 처리해야 하는 부하를 상당히 줄일 수 있음; 해당 서버로부터 복원할 경우 인덱스를 재구축해야 함
    • "votes": 0 => 서버가 두 개뿐이라면 Secondary의 "votes"를 0으로 설정해서 해당 서버가 다운되더라도 Primary가 Primary로 유지 되도록 함; 세 번째 서버가 있다면 해당 서버의 "votes"를 0으로 설정하는 대신 아비터를 실행

 

참고

몽고DB 완벽 가이드 3판 - 한빛미디어

반응형

'DB > 몽고DB 완벽 가이드 3판' 카테고리의 다른 글

[12장] 애플리케이션에서 복제 셋 연결  (0) 2025.04.25
[11장] 복제 셋 구성 요소  (0) 2025.04.24
[10장] 복제 셋 설정  (0) 2025.04.22
[9장] 애플리케이션 설계  (0) 2025.04.21
[8장] 트랜잭션  (0) 2025.04.16