MongoDB 의 복제 매커니즘 (Replica Set)
MongoDB 클러스터를 구축하려고 하는데, MongoDB 에서는 Replication 이 어떤식으로 동작하는지, 주의할 점이 어떤 게 있는지, 그리고 주요한 설정 값은 뭔지 정리해보았다. MongoDB 클러스터 구축시 적절하게 활용하고자 한다.
주로 서적으로 공부를 하는 편인데, MongoDB 는 최근 시장이 매우 커짐과 동시에 버전이 계속 업데이트 되어서 레퍼런스를 찾기가 쉽지 않았다. 국문 서적중에는 가장 최근에 나온 MongoDB 완벽가이드? 이게 4.x. 버전으로 글 작성 기준의 최신 버전인 8.0 버전과 비교해서 상당히 뒤떨어져 있기 때문에, 좀 아쉬울 것 같았다. (그래도 주문은 해놓았음...)
근데 MongoDB 의 공식 문서을 봤는데, 별도로 서적이 필요 없을 정도로 매우 정리가 잘되어 있어서 이걸 레퍼런스로 공부했다. MongoDB 는 국문 번역도 되어있긴 힌데, 읽어본 결과 번역이 그냥 번역기 돌린 수준이라서 국문은 볼 이유가 없는 것간다. 개인적으로 그냥 영문본을 꾸역꾸역 읽는 것이 더 낫다고 생각한다. (이 글을 한번 훑고 보면 더 읽기 수월할 지 모른다...)
레퍼런스 : https://www.mongodb.com/docs/manual/replication/
Replication - MongoDB Manual
A replica set in MongoDB is a group of mongod processes that maintain the same data set. Replica sets provide redundancy and high availability, and are the basis for all production deployments. This section introduces replication in MongoDB as well as the
www.mongodb.com
1. Replica Set
Mysql, Redis 에서의 Replication 은 Master 와 Slave 각각의 node 가 독립적으로 존재하고, 요청을 처리하는 역할과 복제되는 역할의 구분이 뚜렷하게 나뉘어 있었다. 그러나 MongoDB 에서는 Replication 에 참여하는 node 각각은 독립적인 주체라는 느낌보다는 Replica Set 이라고 하는 하나의 큰 집합으로 동작한다. Mysql 에서 Replication 을 하기 위해서는 Slave node 에서 Master node 로 복제를 하라는 명령만 하면 되지만, MongoDB 에서는 인스턴스를 실행하는 시점부터 Replica Set 구성 정보를 포함하여 실행해야 한다. 그리고 나서 Primary 에서 모든 노드의 존재를 Replica Set 에 포함시키는 작업을 해주면 하나의 큰 Set 으로 동작하게 된다. 물론 클라이언트 쪽에서도 모든 Replica Set 을 이루는 node 의 호스트를 알아야 한다.
Replica Set 은 다음과 같이 세 종류의 node 로 구성된다.
종류 | 역할 |
Primary | 데이터 읽기와 쓰기 작업을 처리하는 노드로 Replication 의 대상이 된다. |
Secondary | Primary 로부터 비동기적으로 Replication 을 수행하며 이를 기반으로 읽기 요청도 수행할 수 있다. |
Arbiter | 데이터를 저장하지 않고 장애 상황시 선거에만 참여하여 Primary 를 선출하는 역할을 한다. (여건상 2개의 node 만 운용할 수 있는 경우, 과반을 채우기 위한 목적) |
2. Oplog
MongoDB 에서 쓰기 작업이 일어나면 두가지 로그가 생성된다. 우선, 예기치 못한 인스턴스가 종료 상황에서 데이터를 다시 복구하기 위한 데이터인 Journal 이 생성 되고, 이어서 Replication 을 위한 로그인 Oplog 가 생성된다. (둘다 동기적으로 생성되는 데이터로, 트랜잭션 성공 시점에 두 로그 모두 존재하게 된다.) 둘 다 로그성 데이터로 비슷한 개념이지만 각각의 역할도 다르고 완전히 독립적으로 운영된다. 또한 Journal 은 데이터베이스의 적용 내용을 아주 간결하게 적성하고, 주기적으로 데이터베이스에 적용되는 Write Ahead Log (WAL) 개념의 로그이고, Oplog 는 쓰기 작업 내용을 좀 더 고수준에서 표현된다는 차이도 있다. 여기서는 Journal 에 대해서는 이야기하지 않고 Replication 을 위한 데이터인 Oplog 만 알아본다.
Oplog 는 쓰기 작업과 동기적으로 수행되며, 곧바로 디스크에 쓰이는 작업이다. 쓰기 작업이 성공했는데 oplog 저장에 실패하면 클라이언트에는 실패 응답이 반환되며, 전체 트랜잭션이 롤백된다. 다시 말해, 쓰기 작업이 성공적으로 완료된 순간 디스크에는 Oplog 가 존재한다는 것이다. Replication 은 생성된 Oplog 를 Secondary 로 복제하고, 이를 기반으로 Journal 이 쓰이며 데이터베이스를 갱신한다. 때문에 복제 속도는 I/O 성능에 매우 직접적인 영향을 받는다고 할 수 있다. oplog 는 local 이라는 데이터베이스에 기록된다.
Oplog 는 쓰기 명령을 기록하는 방식이 아니라 변경 내역을 기록한다. 말하자면 Mysql 의 Row Based Replication 과 비슷한 방식이다. 때문에 비결정적인 함수 문제, 복잡한 작업에서 발생할 수 있는 충돌 문제 등에서 자유롭기 때문에 데이터 일관성 측면에서 좋다. 그러나 대량의 데이터가 변경되는 쓰기 작업의 경우, 변경된 Document 각각에 대해서 oplog 가 생성이 되므로 oplog 의 크기가 매우 커질 수 있다. 이는 읽기 성능의 저하를 유발할 수 있고 이에 따른 Replication 지연이 일어날 수 있다. 또한 oplog 컬렉션 사이즈 문제, TTL 문제 등 여러 부작용이 있을 수 있기 때문에 대규모 update 가 예상되는 경우 oplog 컬렉션 사이즈를 늘린다거나 하는 적절한 조치가 필요하다.
3. Replication 프로세스
Replication 이 일어나는 과정은 아래와 같다.
- Primary 에서 쓰기 작업이 수행되면, 해당 변경 내역에 대한 oplog 가 생성된다.
- Secondary 는 Primary 디스크에 쓰인 oplog 를 자신의 디스크에 동일하게 복제한다.
- Secondary 가 다수인 경우 Secondary 간의 oplog 복제가 일어나기도 한다.
- Secondary 는 저장된 oplog 를 토대로 동기화 작업을 수행한다.
4. Replication Lag 와 Slow Operation
상기한 대로 비동기적인 Replication 으로 인해 Primary 와 Secondary 사이에는 필연적으로 불일치가 있게 되며, 이를 두고 Replication Lag, 복제 지연이라고 한다. 때문에 일관성이 중요한 읽기 작업, 예를 들어 분산 트랜잭션이 포함된 읽기 작업은 Secondary 가 읽기 요청을 처리할 수 있는지와는 별개로 반드시 Primary 에서 수행해야한다.
Replication Lag 는 다음과 같은 요인으로 인해 커질 수도, 작아질 수도 있다.
- 낮은 디스크 I/O 성능 (oplog 복제를 수반하기 때문에)
- 높은 쓰기 부하 (쓰기 부하가 너무 크면 Replication 이 이를 따라잡지 못할 수 있다.)
- 잘못된 설계로 인해 오랜 시간이 소요되는 복제 (인덱스가 없다거나)
- 이외 네트워크 지연 및 리소스 부족 등
위와 같이 복제 지연은 여러가지 이유로 발생하게 된다. 이 때 Replication Lag 가 일어나는 원인은 Primary 에서 요청 처리에 오랜 시간이 소요 된다거나 병목이 발생하는 원인과는 결이 다르다. (왜냐하면 Primary 는 쓰기 명령을 직접 수행하는 반면 Secondary 는 변경 내역을 적용하기 때문이다.) 그래서 Primary 에서 수행하는 프로파일링과는 별도로 Secondary 에서는 Replication 관련 프로파일링을 수행한다. 이때 Replication 에 오랜 시간이 소요된는 Operation 을 Slow Operation 이라고 지칭하며, 특정 시간 기준값 설정을 해두면 그보다 더 오랜 시간이 소요된 oplog 항목을 diagnostic log 에 별도 기록한다. 개발자는 이를 토대로 인덱스를 다시 설계하거나 리소스를 추가적으로 투입하는 등의 조치를 할 수 있을 것이다. Slow Operation 값은 기본적으로 100ms 로 설정이 된다.
Replication Lag 가 불가피한 부작용이긴 하지만, 적어도 데이터의 일관성의 수준을 조정할 수는 있다. Primary node 의 쓰기 속도를 늦춰 Replication Lag 최소화하는 것이다. 이를 Flow Control 기능이라고 한다. flowControlTargetLagSeconds 설정값으로 복제 지연 시간의 최대값을 설정해두면 해당 지연시간 미만으로 유지하기 위해 Primary 는 Flow Control 을 수행한다. 기본적으로 10 초로 설정된다.
참고로 Secondary 가 2개 이상인 경우 노드마다 리소스 상황이 다 다르기 때문에 지연 시간의 차가 날 수 있는데, Primary node 는 과반이상의 Secondary node 에서 동기화 ACK 을 받아야 동기화 된 것으로 인식한다. (이를 majority committed 라 한다.)
5. 자동 Failover
Primary 가 electionTimeoutMillis 로 설정된 값(기본 10초)을 초과하여 Secondary 나 Arbiter 와 소통하지 않으면 (Heartbeat) 장애로 인식하고 Failover 과정이 시작된다. 이 과정이 끝날 때 까지 쓰기 작업은 중단되지만, 만약 Secondary 에서 읽기를 허용해 놓은 경우 읽기 요청은 계속 처리된다.
장애 발생 시, Secondary 에서 Primary 를 선출하고 정상화 되기까지 보통 12초를 넘지 않도록 설정하도록 권장된다. 12초에는 장애 발생을 감지하는 시간까지 포함되는데 서비스 중단 시간을 최소화하기 위해 electionTimeoutMillis 을 짧게 설정 해놓으면 더 자주 장애 탐지를 하게 된다. 이는 w : 1 쓰기 작업 롤백 등 부작용을 가지고 있으므로 신중하게 선택해야한다.
w : 1 쓰기란
MongoDB 의 쓰기 요청에서는 특이하게도 클라이언트 측에서 Write Concern 이라는 옵션을 줄 수 있다. 이는 내가 요청한 데이터가 MongoDB 클러스터에 얼마나 동기화 될 때까지 기다릴 것인지를 조정 할 수 있는 옵션으로 옵션 별로 다음과 같은 효과를 가진다. (서버에서는 write concern 기본값을 지정하여, 별도의 write concern 설정이 없는 경우 적용할 write concern 을 설정할 수 있다.)
Write Concern | 의미 |
w: 0 | Primary node 에 요청을 보낸 즉시 성공 응답 |
w: 1 | Primary node 에 쓰기 완료 후 성공 응답 (1 개 node 에 쓰기 보장) |
w: n(숫자) | Primary node 를 포함해 n 개 node 에 쓰기 완료 후 성공 응답 (n 개 node 에 쓰기 보장) |
w: majority | Primary node 포함 과반수 노드에 반영되면 성공 응답 |
이 때 w: 1 쓰기를 하면 아래와 같은 예시 상황이 생겨 Rollback 이 일어나게 된다.
- Primary node A 에 데이터 쓰기 성공
- 그러나 Replication Lag 으로 인해 Secondary node 인 B, C 에 해당 데이터 동기화 실패
- 이 때 A 가 다운되어 B 가 새로운 Primary node 로 승격
- 이후 A 가 복구 되었을 때, A 에만 쓰여진 데이터가 Primary 와 일치하지 않아 Rollback 발생
6. 이외 사항
클러스터를 이루는 노드마다 각 역할 관련 설정을 줄 수 있다.
- 특정 Secondary node 는 Primary node 로 승격되지 못하게 구성할 수 있다.(cold stanby)
- 특정 Secondary node 는 읽지 못하게 할 수 있다. (일반 트래픽과 분리하기 위함)
- 특정 Secondary node 는 실수로 데이터를 삭제하는 등 예기치 못한 사고에 대비해 스냅샷을 저장하도록 할 수 있다.