들어가며
오늘은 "부하가 심한 상황에서 Rolling Update 를 언제 해야 되나"는 이사님의 질문으로부터 시작되어
사내 테크데이에 발표까지 하게된 Mongo의 Rolling Update에 대해서 다뤄볼까 합니다.
MongoDB Atlas 환경에서 예상 가능한 부하를 대비하기 위해 어떤 전략으로 클러스터를 확장할지
어떻게 안정적인 서비스를 제공할 것인가에 대한 고민을 하는 분들을 위한 글입니다.
Rolling Update, 어떻게 진행될까요?
1. 문제가 없는 일반적인 상황에서의 Update

기본적으로 Atlas의 클러스터는 1대의 Primary와 2대의 secondary 노드로 구성된 형태를 지닙니다.
Mongo DB의 Patch 를 적용해야 하는 상황이 왔습니다.
관리자는 Update 트리거를 동작시킵니다.

Secondary 노드 중 한대는 Update 를 진행하고 Primary 와의 연결을 끊습니다.

업데이트가 완료된 Secondary 노드는 Primary 노드와 연결되며
같은 흐름으로 남은 Secondary 노드의 업데이트가 진행됩니다.

모든 Secondary 노드의 업데이트가 완료되면 Primary 노드가 업데이트 준비를 합니다.

Primary 노드가 down 되면서 선거(election)을 트리거합니다.
여기서 선거(election)란, 차기 Primary 를 선출하기 위한 과정입니다.
이 선거 트리거를 가장 먼저 받은 Secondary 노드가 선거를 발생시킵니다.
선거를 결정짓는 요소는 크게 term(임기번호) 와 optime(작업 기록)이 있습니다.
term 은 현재 몇대 대통령 선거인지를 나타내는 값이고
optime 은 primary 노드로부터 받은 작업 기록에 대한 값입니다.
(기본적으로 secondary 노드는 쓰기 작업을 할 수 없으므로 primary 의 쓰기 작업에 대한 oplog 를 전달받아 기록합니다.)
즉, 누가 가장 최근의 데이터를 기록했는가에 대한 내용이라고 볼 수 있습니다.
선거가 발생했을 때 Secondary 노드들은 이 두가지 값을 기본으로 요청받은 노드의 Primary 찬/반 여부를 선택합니다.

optime 이 조금 더 빨랐던 한 secondary 노드가 primary 노드가 됩니다.
선거가 완료되면 term(임기) 값이 증가합니다.
secondary 로 강등된 노드는 업데이트를 진행합니다.

업데이트가 완료되면 새롭게 선출된 primary 노드와 통신을 하며 업데이트가 마무리됩니다.
진행과정을 보면 아시겠지만 Raft 합의 알고리즘 기반의 동작임을 확인할 수 있습니다.
2. Primary 가 접근이 불가능한 경우 (Failover)

secondary 노드들은 election timeout 값을 가지고 있습니다.
이 timeout 시간이 지나게 되면 secondary 노드들은 선거를 발생시킵니다.

primary 노드가 살아있을 때는 주기적으로 secondary 노드들에 heart beat 신호를 보냅니다.
heart beat 신호를 받은 secondary 노드들은 이 timeout 시간을 갱신하며 반역(?)을 참습니다.

그러다 primary 가 죽자 더 이상 보낼 heart beat 가 없습니다.
여기서 모든 secondary 노드들이 한 번에 선거를 발생시키지 않도록
노드별로 랜덤 한 offset 값이 timeout 타임에 추가됩니다.

offset 시간까지 합친 timeout 시간이 지나면 secondary 노드는 선거를 일으킵니다.

남은 노드들에 선거를 보내 투표를 획득합니다.

선거로 새로운 pirmary 가 선출됩니다.
그런데 죽어있던 priamry 가 갑자기 눈을 뜬다면?

이때 깨어난 primary 는 자신의 term 과 새로운 primary 의 term 을 비교합니다.

본인이 밀렸다는 사실을 깨달은 primary 노드는 secondary 노드로의 유배를 받아들이고 term 값을 증가시키며 마무리됩니다.
3. 부하가 심한 경우

서비스가 흥행하여 secondary 노드들에 CPU 75% 정도의 부하가 발생하고 있다고 가정해 보겠습니다.

관리자가 이를 발견합니다(!).

문제를 인지한 관리자는 Mongo Atlas 의 스펙을 올리게 됩니다.

Secondary 한대가 업데이트 모드로 들어가면서 Primary 와의 통신을 끊습니다.
그러자 남아있던 Secondary 노드는 모든 부하를 몰아 받습니다.

참다못한 남아있던 Secondary 노드가 죽어버리는 불상사가 일어나게 됩니다.
여기까지 이론적인 내용을 다루었고 실제로도 그렇게 동작하는지 실험을 통해 알아보겠습니다.
직접 확인해 보자
1. 3대로 구성된 클러스터를 준비합니다 (M20)
→ 초당 1천 명 유저 정도의 부하를 줌 (읽기)

- 1번부터
- 00-00 클러스터 (S)
- 00-01 클러스터 (S)
- 00-02 클러스터 (P)
→ Primary 는 읽기 작업에 대해 손하나 까딱 안 함
2. Rolling Update 진행 (M20 → M30)

- 01 클러스터가 먼저 Update 됨 → (Down)
- 00 클러스터가 부하를 다 받음
3. 2번 클러스터 업데이트 완료

- primary 로 등극
- 03 클러스터 강등
- 01 클러스터 Update (Down)
4. 1번 클러스터 업데이트 진행

- 01 클러스터는 정지
- primary 에서 강등된 03 클러스터 열일 중
5. 3번 클러스터 업데이트까지 진행된 이후 최종 정상화

- 03 클러스터의 그래프 부분에 보면 Down time 동안 값이 없는 것을 확인 가능
?? Secondary 부터 다 업데이트되고 Primary 가 변경된다면서요?
→ priority 에 따라 Priority Takeover 가 적용됨
- priority 란? → 노드 자체의 우선순위를 말함
- 이 우선순위가 높은 노드가 등장하면 강제로 선거가 진행됨


- 기본적으로 Region 이 다른 경우 Region을 배치한 순서가 적용된다고 함
- Region 이 같은 경우에는 공식적으로 기준이 문서에 기록되어있지는 않음
- 실험 결과로 스펙이 priority 에 영향을 준다고 유추가능
Primary 일을 시킬 순 없나요?
물론 가능합니다.
Mongo Atlas는 총 5가지의 읽기 모드를 지원하는데
| 모드 | 읽기 대상 | 폴백 | 설명 |
| primary | Primary만 | 없음 (에러 발생) | 기본값. 가장 강력한 일관성 보장. Primary가 없으면 읽기 실패 |
| primaryPreferred | Primary 우선 | Primary 없으면 Secondary | 평소엔 Primary에서 읽고, Primary 장애 시 Secondary에서 읽음 |
| secondary | Secondary만 | 없음 (에러 발생) | Secondary에서만 읽음. 가용한 Secondary가 없으면 읽기 실패 |
| secondaryPreferred | Secondary 우선 | 모든 Secondary 없으면 Primary | 평소엔 Secondary에서 읽고, 모든 Secondary가 죽었을 때만 Primary에서 읽음 |
| nearest | Primary/Secondary 구분 없음 | - | 네트워크 지연시간이 가장 짧은 노드에서 읽음. Primary든 Secondary든 무관 |
스낵 서비스에서 선택한 모드는
- secondaryPreferred
모드로 살아있는 secondary가 있는 경우 secondary에서 데이터를 읽도록 되어있습니다.
trade-off 를 적절하게 판단하자
부하가 있는 경우에 secondary 노드들의 CPU 사용량 임계값을 높이고 싶다면
클러스터 개수를 늘려 Scale-Out 을 고려하거나 primaryPreferred 를 선택해야 합니다.
→ 다만 이 경우 기본적으로 처리할 수 있는 양이 줄어듦
- secondaryPreferred 읽기 모드를 기준으로
클러스터거 N 대인 경우 - 안전 CPU 상한(%) = (N - 2) / (N - 1) × 100
- ex)
- 3대 → CPU 50%
- 5대 → CPU 75%
- 7대 → CPU 83%
- ex)
일반적으로 쓰기 vs 읽기 비율이 1:9 정도로 보기 때문에 scale-out 이 효율이 좋으나
scalue-out 의 경우 primary 의 성능을 높여줄 수 없기 때문에
쓰기 성능이 중요한 경우 노드 자체의 성능이 올라가기 때문에 scale-up 도 고려할만합니다.


Mongo Atlas 에서 투표 가능한(Electable) 노드의 개수를 늘리는 것은 3, 5, 7 단위의 홀수로 scale-out이 가능합니다.
근데 스케일 아웃이 왜 홀수 단위로만 되나요?
Primary 의 장애 상황에서 클러스터가 3대의 노드로 되어있는 경우에도
Primary 가 죽으면 Secondary 2대로도 결국 투표가 가능해서 선거가 가능한데?

→ 사실 반드시 홀수일 필요는 없습니다.
Mongo Atlas 에서 홀수로 제한한 것은 효율적인 운영을 위한 정책일 뿐
왜 효율적인지는 다음 그림을 예시로
몇 대의 클러스터 구성에서 몇대의 오류를 허용할 수 있는가의 관점으로 보겠습니다.



허용 장애 수 가 유의미하게 늘어나는 단계가 홀수단위
- floor(N/2) +1
| 노드 수 | 과반수 | 허용 장애 수 | 비용 |
| 3 | 2 | 1 | 3 |
| 4 | 3 | 1 | 4 |
| 5 | 3 | 2 | 5 |
| 6 | 4 | 2 | 6 |
| 7 | 4 | 3 | 7 |
추가로 네트워크 파티션이 분리되는 경우도 예상해 볼 수 있습니다.


마치며
오늘은 Raft 합의 알고리즘을 통한 분산 시스템인 Mongo Atlas 의 클러스터 정책에 대해서 알아봤습니다.
이를 통해 유사한 구조의 분산 시스템에서
- 과반수 합의
- 홀수 구성
- 부하 분산 여유 확보
3가지에 대한 판단 기준만 세울 수 있다면
안정적인 서비스를 위한 운영 전략을 확립할 수 있을 것입니다.
*참고한 자료
'백엔드' 카테고리의 다른 글
| 하네스 엔지니어링 - 2 (1) | 2026.05.16 |
|---|---|
| 하네스 엔지니어링 - 1 (2) | 2026.05.04 |
| [GC] 나야 메모리, 근데 이제 누수를 곁들인 (2) | 2024.10.22 |
| [Base64] 빼앗긴 Parameter 찾습니다. (3) | 2024.10.11 |
| [Transactional] 거래가 왜 없었을까요? (0) | 2024.08.18 |