본문 바로가기
T./Mastering MongoDB 7.0

2. The MongoDB Architecture

by IT Journeyman 2025. 3. 10.

 

Mastering MongoDB 7.0 - Fourth Edition

Achieve data excellence by unlocking the full potential of MongoDB

매 주 한 챕터씩 번역하고 있습니다. 관련 자료와 용례도 참고 부탁드립니다.

 


2. MongoDB 아키텍처

MongoDB는 여러 핵심 아키텍처 기반을 바탕으로 현대 애플리케이션의 요구를 충족하는 개발자 데이터 플랫폼을 제공합니다. 이를 통해 트랜잭션, 운영, 분석 애플리케이션을 구축하는 가장 혁신적인 방법을 활용할 수 있습니다. 이 장에서는 MongoDB 아키텍처를 살펴보며, 특히 복제(replication)샤딩(sharding)이라는 두 가지 핵심 요소에 초점을 맞춥니다.

 

복제(Replication)

복제는 MongoDB의 분산 아키텍처에서 중요한 요소로, 데이터의 접근성을 보장하고 장애에 대한 복원력을 제공합니다. 동일한 데이터 세트를 여러 데이터베이스 서버에 분산 저장함으로써 단일 서버 장애로 인한 데이터 손실을 방지할 수 있습니다.

 

샤딩(Sharding)

샤딩은 데이터를 여러 머신에 분산 저장하는 수평적 확장(horizontal scaling) 전략입니다. 애플리케이션의 인기가 높아지고 생성되는 데이터의 양이 증가함에 따라, 여러 머신으로 확장하여 충분한 읽기 및 쓰기 처리량을 확보하는 것이 필수적입니다.

 

이 장에서 다룰 내용

  • 복제와 샤딩으로 신뢰성과 가용성을 높이는 방법
  • MongoDB에서 복제 및 샤딩을 구현하는 다양한 방식
  • MongoDB 7.0에서 새롭게 추가된 샤딩 클러스터 기능

복제(Replication) vs 샤딩(Sharding)

많은 사람들이 복제와 샤딩을 혼동합니다. 둘 다 데이터베이스 관리에 사용되는 시스템이지만, 각각의 목적과 용도가 다릅니다.

복제(Replication)는 데이터를 중복 저장하여 여러 위치에 보관하는 프로세스로, 데이터의 중복성(redundancy)과 신뢰성(reliability) 을 보장합니다. 이는 데이터 보호와 접근성 확보에 중요한 역할을 합니다.

 

반면, 샤딩(Sharding) 은 대규모 데이터베이스를 보다 작은 단위로 나누어 관리하기 쉽게 만드는 기법입니다. 각 샤드(shard)는 전체 데이터 세트의 일부를 별도의 데이터베이스 서버 인스턴스 에 저장합니다. 그러나 각 샤드도 데이터를 안전하게 유지하기 위해 반드시 복제 기능을 적용해야 합니다.

 

복제와 샤딩을 결합하는 이유는 데이터의 내구성(durability)과 높은 가용성(high availability) 을 확보하기 위함입니다. 만약 특정 샤드의 서버 인스턴스가 장애로 인해 다운되고, 그 샤드의 데이터가 단 하나의 복사본만 존재한다면 해당 데이터는 서버가 복구될 때까지 사용할 수 없게 됩니다. 하지만 각 샤드 내부에서 복제 기능 을 활용하면 서버 장애가 발생하더라도 데이터 가용성을 유지할 수 있으며, 시스템 중단 없이 지속적인 운영이 가능합니다.

 

이 접근 방식은 샤딩 클러스터의 점진적 업그레이드(rolling upgrades) 를 가능하게 하며, 원활한 시스템 유지보수를 지원합니다.

이후 섹션에서는 복제와 샤딩의 세부적인 구성 요소를 심층적으로 다룰 예정입니다.

복제(Replication)

MongoDB에서 레플리카 셋(replica set) 은 동일한 데이터 세트를 유지하는 mongod 프로세스의 집합 을 의미합니다. 이를 통해 데이터 중복성과 높은 가용성 을 제공하며, 모든 운영 환경에서 필수적인 요소로 작용합니다.

여러 데이터베이스 서버에 다수의 데이터 복사본을 유지함으로써 내결함성(fault tolerance) 을 보장하며, 특정 데이터베이스 서버가 장애를 일으켜도 데이터 손실을 방지할 수 있습니다.

(Primary 노드는 Secondary 노드와 복제를 수행하며, Heartbeat 신호를 통해 상태를 유지합니다.)

 

 

Primary 노드 는 모든 쓰기 작업을 처리하며, 데이터 세트의 모든 변경 사항을 Operation Log(oplog)에 기록합니다. MongoDB 레플리카 셋에는 단 하나의 Primary 노드만 존재 할 수 있습니다.

Secondary 노드 는 Primary 노드의 oplog를 복제하여 해당 작업을 자체 데이터 세트에서 실행합니다. 즉, Secondary 노드는 Primary 노드의 데이터를 그대로 복사하여 유지합니다. 만약 Primary 노드가 접근 불가능한 상태가 되면, 자격을 갖춘 Secondary 노드가 선출(election)되어 새로운 Primary가 됩니다.

 

여러 서버에 데이터를 저장함으로써 복제 기능은 시스템의 신뢰성을 높입니다. 또한, 롤링 업그레이드(rolling upgrades) 를 지원하여 개별 서버의 소프트웨어 또는 하드웨어를 중단 없이 업그레이드할 수 있으며, 이를 통해 데이터베이스의 지속적인 가용성이 보장됩니다. 복제 기능은 읽기 부하가 많은 애플리케이션(read-heavy applications) 의 성능을 크게 향상시키며, 여러 서버에 부하를 분산하여 빠른 데이터 검색을 가능하게 합니다.


레플리카 셋 선출(Replica set elections)

MongoDB는 Raft 합의 알고리즘(Raft consensus algorithm) 을 기반으로 하는 프로토콜을 사용하여 레플리카 셋 선출을 조정합니다. 이를 통해 분산 시스템에서 데이터 일관성을 유지 할 수 있습니다. 이 프로토콜은 투표 메커니즘(voting mechanism) 을 포함하며, 이를 통해 어떤 노드가 Primary 역할을 수행할지 결정 합니다.

다음과 같은 이벤트가 발생하면 선출이 시작될 수 있습니다.

  • 레플리카 셋에서 노드를 추가하거나 제거할 때
  • 레플리카 셋을 초기화할 때
  • Primary 노드와 Secondary 노드 간 하트비트(heartbeat) 신호가 사전에 설정된 시간 초과값을 넘을 때
    • 기본값: 설치형 MongoDB(self-managed hosts)에서는 10초, MongoDB Atlas에서는 5초

애플리케이션의 연결 처리(connection-handling) 로직은 자동 장애 조치(failover) 및 선출 과정에 대비하여 설계되어야 합니다. MongoDB 드라이버는 Primary 노드 손실을 감지하고 특정 읽기 또는 쓰기 작업을 자동으로 재시도 하도록 설계되어 있어, 내장된 복구 기능을 제공합니다.

 

Primary 노드가 사용할 수 없게 되면, Secondary 노드들은 새로운 Primary를 선출하기 위해 투표를 진행합니다. 가장 최근의 쓰기 타임스탬프(write timestamp)를 가진 레플리카가 선출에서 승리할 가능성이 높습니다. 이러한 전략을 통해 이전 Primary가 다시 합류했을 때 롤백(rollback) 가능성을 최소화 합니다.

 

선출이 완료된 후, 노드들은 일정 기간 동안 선출을 다시 시작할 수 없는 "동결 상태(freeze period)" 에 들어갑니다. 이는 빈번한 선출(rapid elections)이 발생하여 시스템이 불안정해지는 것을 방지하기 위한 설계입니다.

현재 MongoDB의 Replica Set은 프로토콜 버전 1 (pv1*) 만을 지원합니다.

 

(* 역자주 - 여기서 pv는 Protocol Version의 약자입니다. 이는 Replica Set에서 Primary Election(Primary 선출) 및 기타 동작 방식을 정의하는 프로토콜 버전을 나타냅니다. MongoDB는 현재 Protocol Version 1 (pv1)만을 지원하며, 이는 MongoDB 3.2 버전에서 도입되어 기본값으로 설정되었습니다. 예전 3.0이하 버젼에서 사용되던 pv0는 30초 이상 선출이 지연되는 현상이 있어 이를 개선하는 pv1이 도입되었습니다. 관련 공식 매뉴얼 : https://www.mongodb.com/ko-kr/docs/manual/reference/replica-set-protocol-versions/)

그림 2.3은 샤드 클러스터 내 구성 요소의 상호 작용을 보여줍니다.

Replica Set이 Primary를 잃은 경우
Replica Set은 새로운 Primary가 선출될 때까지 쓰기 작업을 실행할 수 없습니다. 그러나 Secondary 노드를 대상으로 읽기 작업을 수행하도록 설정되어 있다면, 읽기 작업은 계속 처리할 수 있습니다.

 

선출 시간
일반적으로, 기본 Replica Set 구성 설정에서는 클러스터가 새로운 Primary를 선출하는 데 걸리는 시간이 12초를 초과하지 않아야 합니다. 이 시간은 Primary가 접근 불가능하다고 선언되고, 선출을 시작하고 완료하는 데 필요한 시간을 포함합니다. 이 시간은 settings.electionTimeoutMillis 복제 구성 옵션을 수정하여 조정할 수 있습니다.

멤버 우선순위 (Member Priority)

우선순위 기반 선출
안정적인 Primary가 설정되면, 선출 알고리즘은 가장 높은 우선순위를 가진 Secondary가 선출을 시작하도록 허용합니다. 우선순위가 높은 Secondary는 더 빨리 선출에 참여하고, Primary로 선출될 가능성이 높습니다. 그러나 낮은 우선순위의 멤버가 일시적으로 Primary 역할을 수행할 수도 있으며, 이는 더 높은 우선순위 멤버가 사용 가능할 때까지 지속됩니다.

 

우선순위 값
우선순위 값이 0인 멤버는 Primary로 승격될 수 없으며, 선출에 참여하지 않습니다. 선출 과정은 가장 높은 우선순위 멤버가 Primary로 승격될 때까지 계속됩니다.

 

레플리카 셋 멤버 구성
레플리카 셋은 최대 50개의 멤버 를 포함할 수 있으며, 이 중 최대 7개만 투표 멤버(voting members)로 설정 할 수 있습니다.
비투표(non-voting) 멤버 를 포함하면 7개 이상의 멤버를 추가할 수 있습니다.
비투표 멤버는 투표 수가 0이며(priority 값이 0이어야 함), 이는 레플리카 셋의 확장성을 높이는 데 활용됩니다.


레플리카 셋 Oplog (Replica set oplog)

Oplog(Operations Log) 는 MongoDB 데이터베이스에서 데이터를 변경하는 모든 작업을 기록하는 특수한 고정 크기 컬렉션(capped collection) 입니다.
특정 크기 제한이 있지만, 데이터 손실을 방지하기 위해 필요할 경우 설정된 크기를 초과하여 확장될 수 있습니다.

Oplog 작동 방식:

  1. 쓰기 작업은 Primary에서 실행 되고, 그 작업이 Primary의 Oplog에 기록됨.
  2. Secondary 멤버들은 Primary의 Oplog를 비동기적으로 복제(replication)하여 적용
  3. 모든 레플리카 셋 멤버는 각자의 Oplog 사본을 보유 하며, 이는 local.oplog.rs 컬렉션에 저장됨.
  4. 레플리카 셋의 멤버들은 하트비트(heartbeat, ping) 메시지를 주고받아 서로의 상태를 모니터링함.
  5. Secondary 노드는 어느 멤버에서든 Oplog 항목을 가져올 수 있음
  6. Oplog의 모든 연산은 멱등성(idempotent)을 가짐: 즉, 같은 연산을 한 번 또는 여러 번 실행하더라도 동일한 결과를 보장함.

Oplog 윈도우 (Oplog Window)

Oplog는 각 엔트리(entry)에 타임스탬프를 포함 하며, Oplog 윈도우Oplog에서 가장 최신 타임스탬프와 가장 오래된 타임스탬프 간의 시간 간격을 의미 합니다.

 

Oplog 동기화 조건

  • Secondary 노드가 Primary와의 연결이 끊어진 경우,
    → Oplog 윈도우 내에서 다시 연결되면 기존 복제 과정으로 동기화 가능
    → Oplog 윈도우를 초과하면 초기 동기화(full resync)가 필요

Oplog 보존 정책
MongoDB는 Oplog 보존 기간을 최소 지속 시간(시간 단위) 또는 특정 크기로 설정 가능 합니다.
다음 조건이 충족되면 Oplog 엔트리가 삭제됩니다.

  1. Oplog가 최대 구성된 크기(limit)까지 채워졌고, 해당 Oplog 엔트리가 설정된 보존 기간을 초과한 경우
  2. 최소 Oplog 보존 기간을 별도로 지정하지 않았다면,
    • MongoDB는 기본 동작(default behavior)으로 Oplog의 가장 오래된 엔트리부터 삭제(truncation)하여, 설정된 최대 크기를 초과하지 않도록 유지

레플리카 셋 배포 아키텍처(Replica set deployment architectures)

일반적으로 운영 환경에서의 권장 배포 방식은 3개의 멤버로 구성된 레플리카 셋입니다.
이러한 구조는 고가용성과 내결함성을 제공 합니다.
불필요한 복잡성은 피하는 것이 좋지만, 궁극적으로 아키텍처 설계는 애플리케이션의 요구사항을 따라야 합니다.

다음과 같은 규칙을 따르는 것이 일반적입니다.

  • 투표 멤버(voting members)의 개수를 홀수로 유지
    네트워크 장애 발생 시 투표가 동수로 나뉘는 상황을 방지하고, 더 큰 그룹이 쓰기 작업을 허용 할 수 있도록 보장
  • 투표 멤버가 짝수라면 추가 멤버를 고려
    데이터가 저장되는 추가 투표 멤버를 추가하는 것이 이상적이며, 만약 물리적 제한이 있다면 Arbiter(중재자)를 추가
  • 백업 또는 리포팅을 위해 숨김(hidden) 또는 지연(delayed) 멤버 추가
  • 데이터센터 장애 대비: 다른 데이터센터에 최소 1개 이상의 멤버를 배치

레플리카 셋 중재자(Arbiter)*

Primary 노드와 Secondary 노드만 있는 경우,
예산 등의 제한으로 추가 Secondary를 추가하기 어렵다면, Arbiter를 사용할 수 있습니다.

  • Arbiter는 Primary 선출 과정에 참여하지만, 데이터 복사본을 보유하지 않음.
  • Primary가 될 수 없음.
  • 선거에서 1표의 투표권을 가짐.
  • 기본적으로 우선순위(priority)는 0으로 설정됨.

숨김(hidden) 멤버*

숨김(hidden) 멤버레플리카 셋의 일부지만, 클라이언트 애플리케이션에서 보이지 않음.

  • Primary가 될 수 없음.
  • 선거에는 참여할 수 있지만, 외부에서 조회되지 않음.
  • 주로 백업 및 보고서용 쿼리를 수행하는 전용 노드로 사용됨.

숨김 멤버 설정 예제

cfg = rs.conf()
cfg.members[n].hidden = true
cfg.members[n].priority = 0
rs.reconfig(cfg)

지연(Delayed) 레플리카 셋 멤버*

지연 멤버(Delayed Members)는 숨겨진(Hidden) 멤버의 일종 으로, 일정 시간만큼 늦게 Primary의 oplog를 복제 및 적용 하여 과거 상태의 데이터를 유지 합니다.
예를 들어, 현재 시간이 08:01 이고 특정 멤버의 지연 시간이 1시간 으로 설정되어 있다면, 해당 멤버의 최신 데이터는 07:01 이전의 상태 를 유지합니다.

cfg = rs.conf()
cfg.members[n].hidden = true
cfg.members[n].priority = 0
cfg.members[n].secondaryDelaySecs = 3600  // 1시간(3600초) 지연 설정
rs.reconfig(cfg)
 
지연 멤버의 역할 및 장점
  •   백업(Backup) 및 데이터 보호: 
      Primary에서 발생한 변경 사항이 바로 적용되지 않기 때문에 실수로 데이터가 삭제되었을 때 복구 가능
  •   과거 데이터의 실시간 보관:  
      실시간으로 변하는 데이터를 일정 시간 동안 과거 상태로 유지하며, 이전 버전의 데이터를 분석 하는 데 활용 가능
  •   애플리케이션 오류 복구:
      잘못된 애플리케이션 업데이트나 운영자의 실수(예: 데이터베이스 또는 컬렉션 삭제)로부터 복구 가능

(* 역자주 - Arbiter, Hidden, Delayed 설정은 설치형에서만 가능하고 MongoDB Atlas같은 DBaaS에서는 설정할 수 없습니다. 관련 문서 :https://www.mongodb.com/docs/manual/reference/method/rs.reconfig/. 그러나 MongoDB Atlas에서는 Arbiter가 없는 대신 경제적인 비용으로 Secondary를 추가할 수 있습니다. 또한 Hidden 대신에 Read-Only, Analytic 노드를 추가할 수 있고, Delayed 대신에 1분 단위로 Time Based Recovery가 가능한 Continuous Backup을 제공합니다. )


Write Concern

Write Concern은 MongoDB가 레플리카 셋에서 쓰기 작업을 확인하는 방식을 결정합니다.
즉, 데이터를 몇 개의 노드에 복제한 후 쓰기 작업을 성공으로 간주할지 지정 합니다.

Write Concern의 구성 요소

MongoDB의 Write Concern은 다음과 같은 필드를 포함할 수 있습니다:

{ w: <value>, j: <boolean>, wtimeout: <number> }

 

Write Concern(w)의 값 종류

  • 0: 확인 없이 즉시 반환 (최고 성능, 가장 낮은 내구성)
  • 1: Primary 노드에서만 확인
  • majority: 레플리카 셋의 과반수 이상이 확인 (MongoDB 5.0부터 기본값)
  • <number>: 특정 개수의 레플리카 노드에서 확인
  • j: true → 저널(Journal)에 기록될 때까지 대기
  • wtimeout: <ms> → 설정한 시간(ms) 내에 확인되지 않으면 오류 반환
    • 단, 이 오류는 쓰기가 취소된다는 의미가 아니라, 지정한 시간 내 확인을 받지 못했다는 의미

MongoDB 4.4부터 Replica Set 및 Sharded Cluster 환경에서 글로벌 기본 Write Concern 을 설정할 수 있습니다.
별도로 Write Concern을 지정하지 않은 모든 작업은 자동으로 글로벌 기본 Write Concern을 사용 합니다.

 

Read/Write Concern을 설정하는 명령어:

db.adminCommand({
 setDefaultRWConcern : 1,
 defaultReadConcern: { <read concern> },
 defaultWriteConcern: { <write concern> },
 writeConcern: { <write concern> },
 comment: <any>
 })

 

 

MongoDB 5.0부터 기본 Write Concern은 { w: "majority" } 로 설정됩니다.

그러나 Arbiter(조정자)가 포함된 경우 일부 예외가 발생할 수 있습니다.

  •   투표 다수 기준 계산(Voting Majority)
      투표 가능한 멤버 수의 절반 + 1을 계산한 후, 소수점 이하를 버립니다.
      데이터 저장을 수행하는 투표 가능 멤버가 이 기준보다 적다면, 기본 Write Concern이 { w: 1 }로 변경 됩니다.
  •   그 외의 경우: 기본 Write Concern은 { w: "majority" } 로 유지됩니다.

Write Concern의 중요성

Write Concern의 선택은 성능(Performance)내구성(Durability) 에 영향을 미칩니다.

성능 최적화:

  • { w: 0 } (Acknowledgement 없음) 사용 시 쓰기 속도가 증가 하지만, 데이터 손실 위험 증가

데이터 내구성 보장:

  • { w: "majority" } 사용 시 다수 노드에서 데이터가 정상적으로 저장될 때까지 대기
  • 내구성이 강화되지만 쓰기 지연(latency)이 증가할 가능성 있음

Read Preference (읽기 우선순위)

MongoDB에서는 Read Preference 설정을 통해 클라이언트가 어떤 Replica Set 멤버에게 읽기 요청을 보낼지 결정 합니다.

기본 동작:

  • 기본적으로 모든 읽기 요청은 Primary 노드로 전송
  • 하지만 Read Preference를 변경하면 Secondary 노드로 읽기 요청을 분산하여 성능과 가용성을 향상 시킬 수 있음

읽기 우선순위(Read Preference) 구성 요소

MongoDB는 다섯 가지 읽기 우선순위 모드를 지원합니다.

  • primary: 모든 읽기 작업을 Primary 멤버로 보냅니다. (기본값)
  • primaryPreferred: Primary 멤버가 사용 가능하면 Primary로 보내고, 그렇지 않으면 Secondary 멤버로 보냅니다.
  • secondary: 모든 읽기 작업을 Secondary 멤버로 보냅니다.
  • secondaryPreferred: Secondary 멤버가 있으면 해당 멤버로 보내고, 없으면 Primary 멤버로 보냅니다.
  • nearest: 네트워크 지연 시간이 가장 낮은 멤버로 읽기 작업을 보냅니다. (멤버 상태와 무관)

또한, 다음과 같은 옵션을 추가로 지정할 수 있습니다.

  • 태그 세트(Tag sets): 레플리카 셋 멤버에 커스텀 태그를 부여해 특정 태그를 가진 멤버로 읽기 작업을 보낼 수 있습니다.
  • 최대 지연 시간(Max staleness): Primary 멤버와 Secondary 멤버 간 복제 지연(staleness) 정도를 설정할 수 있으며, 지정된 제한을 초과하면 해당 Secondary 멤버는 읽기 작업에서 제외됩니다.

읽기 우선순위의 중요성

  • 성능 향상: 읽기 작업을 보조 멤버로 분산하면 기본 멤버의 부하를 줄여 성능을 개선할 수 있습니다.
  • 가용성 확보: 기본 멤버가 사용할 수 없는 경우에도 보조 멤버를 활용하여 읽기 작업을 지속할 수 있습니다.

Read Concern

Read Concern은 레플리카 셋과 샤딩 클러스터에서 읽기 데이터의 일관성과 격리 수준을 결정합니다.
이를 통해 읽기 작업에서 원하는 수준의 데이터 일관성을 보장할 수 있습니다.

 

읽기 컨선(Read Concern) 구성 요소

MongoDB는 다음과 같은 읽기 컨선(Read Concern) 수준을 지원합니다.

  • "local": 쿼리가 시작될 때 MongoDB 인스턴스에서 사용할 수 있는 가장 최신 데이터를 반환합니다.
    (복제 상태와 관계없이 반환, 기본값)
  • "available": 쿼리 시점에 분산 시스템에서 사용할 수 있는 데이터를 반환합니다.
    (지연 시간이 최소화되지만 일관성이 보장되지 않음)
  • "majority": 레플리카 셋(Replica Set)의 과반수 멤버가 승인한 데이터를 반환합니다.
    (높은 수준의 일관성(Consistency) 보장)
  • "linearizable": 가장 높은 수준의 일관성을 제공하며,
    과반수 승인된 모든 성공적인 쓰기 작업을 반영한 데이터를 반환합니다.
  • "snapshot": 특정 시점(Point-in-Time)의 데이터를 반환,
    레플리카 셋의 모든 멤버에서 동일한 데이터를 조회할 수 있도록 보장합니다.

읽기 컨선의 중요성

읽기 컨선 선택은 데이터의 일관성(Consistency)과 격리(Isolation)에 영향을 미칩니다.

  • 일관성(Consistency)
    • 높은 수준의 읽기 컨선(e.g., "majority", "linearizable")을 사용할 경우,
      모든 레플리카 셋 멤버에서 동일한 데이터를 읽을 수 있음
  • 격리(Isolation)
    • "snapshot" 읽기 컨선을 사용하면 동시 쓰기 작업(Concurrent Writes)으로부터 트랜잭션을 격리할 수 있음
    • 트랜잭션 전체에서 일관된 데이터 뷰(Consistent View) 제공

복제(replication) 메서드

MongoDB Shell(mongosh)은 레플리카 셋(Replica Set)과 상호 작용할 수 있는 다양한 헬퍼 메서드를 제공합니다.
이 메서드들은 MongoDB 복제 환경을 효과적으로 관리하는 데 필수적인 도구입니다.


주요 복제 관련 메서드

  • rs.add() → 레플리카 셋에 멤버 추가
  • rs.addArb() → 레플리카 셋에 중재자(Arbiter) 추가
  • rs.conf() →레플리카 셋 설정(Configuration) 문서 확인
  • rs.initiate() → 새로운 레플리카 셋 초기화
  • rs.printReplicationInfo() → 기본(primary) 노드에서 본 복제 상태 요약 출력
  • rs.status() → 현재 레플리카 셋의 상태 정보를 문서 형태로 제공
  • rs.reconfig() → 기존 레플리카 셋을 재구성(설정 덮어쓰기)
  • rs.stepDown() → 현재 Primary 노드를 Secondary 노드로 변경(선거 트리거)

자세한 복제(replication) 메서드 목록은 다음 리소스를 참고하세요:MongoDB 복제 메서드

 

앞서 언급한 복제 메서드 외에도 특정 작업을 수행하기 위한 복제 명령을 사용할 수 있습니다.
예를 들어, replSetResizeOplog 명령은 레플리카 셋oplog 크기를 변경합니다.
이 명령은 다음과 같은 필드를 사용합니다:

  • replSetResizeOplog: 1로 설정
  • size: oplog의 최대 크기를 MB 단위로 지정
    • 최소 990MB, 최대 1PB 설정 가능
    • mongosh에서 Double()을 사용하여 크기를 명시적으로 double 타입으로 변환
  • minRetentionHours: oplog 항목을 유지하는 최소 시간(시간 단위)
    • 소수점 값을 사용하여 시간 단위를 세분화 가능 (예: 1.5 → 1시간 30분)
    • 0 이상의 값이어야 하며, 0이면 mongod가 가장 오래된 항목부터 삭제하여 최대 oplog 크기를 유지
db.adminCommand({
  replSetResizeOplog: <int>,
  size: <double>,
  minRetentionHours: <double>
})
자세한 복제 명령 목록은 다음 리소스를 참고하세요: MongoDB 복제 명령

샤딩(Sharding)

MongoDB는 샤딩(Sharding)을 통해 수평적 확장(Horizontal Scaling)을 지원합니다.
샤딩은 데이터를 여러 서버에 분산 저장하는 방식으로, 대규모 데이터를 효과적으로 관리 및 구성하는 핵심 기술입니다.

이 방식은 대형 데이터베이스를 작은 단위(Shard)로 분할하는 구조이며,
각 샤드는 독립적인 데이터베이스 서버 인스턴스로 실행됩니다.

이를 통해 부하 분산(Load Balancing)이 가능하며, 효율적인 데이터 관리를 제공합니다.
또한 지리적으로 분산된 애플리케이션을 위한 데이터 레지던시(Data Residency) 정책을 적용할 수 있습니다.

샤딩이 필요한 이유?

데이터가 빠르게 증가하고 데이터베이스가 최대 용량에 도달하면 다양한 문제가 발생할 수 있습니다.

가장 심각한 문제는 성능 저하(Performance Degradation)입니다.
데이터베이스가 커질수록 쿼리 및 데이터 검색 시간이 증가하며,
이는 사용자 경험(UX) 저하로 이어져 애플리케이션이 느려지거나 응답하지 않는 것처럼 보일 수 있습니다.

또 다른 잠재적인 문제는 저장 공간의 제약(Storage Constraints)입니다.
대부분의 시스템은 효율적으로 관리할 수 있는 데이터 용량에 제한이 있습니다.
데이터 증가 속도가 시스템의 저장 용량을 초과하면 시스템 장애(System Failure)로 이어질 수 있습니다.

 

시스템 확장에는 두 가지 주요 전략이 있습니다:

  • 수직 확장(Vertical Scaling)
    단일 서버의 성능을 향상시키는 방식으로, 예를 들어 CPU 업그레이드, RAM 증가, 저장 공간 확장 등을 포함합니다.
    하지만 기술적 한계로 인해 단일 서버가 감당할 수 있는 한계가 존재하며, 따라서 실용적인 제약이 따릅니다.
  • 수평 확장(Horizontal Scaling)
    여러 대의 서버에 시스템의 데이터 및 워크로드를 분산하는 방식입니다.
    추가적인 서버를 도입하여 부하를 나누며,
    개별 서버가 전체 워크로드의 일부만 처리하므로 단일 고성능 서버보다 효율적일 수 있습니다.
    다만, 비용 절감 효과는 있을 수 있지만 인프라 복잡성과 유지보수 부담이 증가하는 단점이 있습니다.

샤딩 클러스터(Sharded Cluster)의 주요 구성 요소

MongoDB 샤딩 클러스터는 다음과 같은 주요 요소로 구성됩니다:

  • Shard(샤드)
    • 클러스터 내 일부 데이터를 저장하는 레플리카 셋(Replica Set)
    • 1개에서 n개까지 구성 가능하며, 각 샤드는 전체 데이터 중 특정 부분을 저장
    • MongoDB Atlas 기준 클러스터 티어별 스토리지 용량:
      • M10-M40: 4TB
      • M50-M60: 8TB
      • M80+: 14TB
  • mongos
    • 클라이언트 애플리케이션을 위한 쿼리 라우터(Query Router) 역할 수행
    • 읽기 및 쓰기 작업을 효과적으로 관리하며, 적절한 샤드로 요청을 전달하고 여러 샤드에서 가져온 결과를 통합
    • 클라이언트는 개별 샤드가 아닌 mongos 인스턴스에 연결
    • 고가용성을 위해 프로덕션 환경에서는 여러 개의 mongos 인스턴스를 실행하는 것이 권장됨
  • Config Servers(구성 서버)
    • 레플리카 셋(Replica Set)로 동작하며,
      샤딩 메타데이터(Sharding Metadata)를 저장하는 전용 데이터베이스 역할 수행
    • 샤딩된 데이터의 상태 및 구조 관리
    • 샤딩된 컬렉션 목록 및 라우팅 정보 등을 포함
    • 효율적인 데이터 관리 및 쿼리 라우팅을 지원하는 핵심 요소
그림 2.3은 샤딩 클러스터 내 구성 요소 간의 상호 작용을 보여줍니다.

 

MongoDB에서는 데이터 샤딩이 컬렉션 단위로 이루어집니다. 즉, 하나의 컬렉션 데이터가 클러스터 내 여러 샤드에 분산됩니다.

샤딩 클러스터 내 각 데이터베이스(Database)*는 자체적인 기본 샤드(Primary Shard)를 가지며,
이 기본 샤드는 해당 데이터베이스의 모든 비샤딩(미샤딩) 컬렉션을 저장하는 역할을 합니다.

여기서 중요한 점은 샤딩 클러스터의 기본 샤드(Primary Shard)와 레플리카 셋(Replica Set)의 Primary 노드와는 다른 개념이며,
각각의 목적이 다르므로 서로 연관되지 않는다는 점입니다.

(* 역자주 - 여기서 데이터베이스(DB)는 컬렉션의 집합 혹은 네임스페이스를 말합니다. 다시 말해 하나의 클러스터에서 여러 개의 DB가 존재할 수 있으며, 서로 다른 설정을 가질 수 있습니다. MongoDB에서는 구성 단위는 최상위로부터 Cluster, 네임스페이스로의 DB, 컬렉션, 다큐먼트, 필드입니다.)

 

새로운 데이터베이스를 생성하면, mongos 프로세스가 클러스터 내 가장 적은 데이터를 가진 샤드를 기본 샤드로 선택합니다.
이 선택 과정에서 listDatabases 명령의 totalSize 필드 값이 기준 중 하나로 사용됩니다.

 

기본 샤드는 초기 지정 이후에도 movePrimary 명령을 사용하여 변경할 수 있습니다.
movePrimary 명령은 우선 클러스터 메타데이터에서 기본 샤드를 변경한 후,
해당 데이터베이스의 모든 비샤딩 컬렉션을 새로운 샤드로 이동하는 과정을 수행합니다.


마이크로샤딩(Microsharding)

일부 시나리오에서는 MongoDB가 마이크로샤딩(Microsharding)이라는 보다 발전된 샤딩 구성을 제공합니다.
전통적인 샤딩 방식에서는 각 샤드를 전용 머신에 할당하는 반면,
마이크로샤딩은 단일 호스트 내에 여러 샤드를 함께 배치하여 보다 세밀한 제어와 자원 최적화를 가능하게 합니다.

마이크로샤딩은 특히 샤드당 데이터 크기가 작은 경우에 효과적인 전략이 될 수 있습니다.
여러 샤드를 단일 호스트에 통합하면 자원 활용도를 극대화할 수 있으며, 이를 통해 하드웨어 효율성이 향상됩니다.

그러나 이 접근 방식은 동일한 호스트에서 실행되는 여러 MongoDB 프로세스 간의 충돌을 방지하기 위해 신중한 자원 관리가 필요하다는 점을 유념해야 합니다. 각 MongoDB 프로세스는 원활하게 운영될 수 있도록 충분한 자원을 할당받아야 합니다.

마이크로샤딩 전략을 구현할 때, 각 MongoDB 프로세스의 캐시 크기를 적절히 구성하는 것이 매우 중요합니다. MongoDB의

WiredTiger 스토리지 엔진에서 캐시 크기는 storage.wiredTiger.engineConfig.cacheSizeGB 설정을 통해 조정할 수 있습니다.
이 캐시는 각 MongoDB 프로세스의 워킹 세트(자주 조회되는 데이터)를 충분히 저장할 수 있을 만큼 커야 합니다.
만약 캐시에 충분한 공간이 없으면, WiredTiger는 새로운 데이터를 로드하기 위해 기존 페이지를 캐쉬에서 제거합니다.

 

기본적으로 storage.wiredTiger.engineConfig.cacheSizeGB 값은 총 RAM의 50%에서 1GB를 뺀 값으로 설정됩니다. 하지만 하나의 머신에서 여러 MongoDB 프로세스를 실행할 경우, 모든 프로세스의 총 메모리 사용량이 가용 RAM을 초과하지 않도록 이 설정을 조정해야 합니다.

 

예를 들어, 16GB RAM을 가진 머신에서 두 개의 MongoDB 프로세스를 실행하는 경우, 기본 설정(50%)을 그대로 사용하면 각 프로세스가 8GB씩 메모리를 사용하여 총 16GB를 초과할 위험이 있습니다.
따라서 각 프로세스의 storage.wiredTiger.engineConfig.cacheSizeGB 값을 25%(4GB)로 설정하면,
MongoDB 프로세스들이 총 사용 가능한 RAM을 초과하지 않도록 하면서도 운영 체제와 다른 애플리케이션이 사용할 수 있는 여유 메모리를 남길 수 있습니다.

샤딩의 장점

샤딩은 여러 가지 이점을 제공합니다:

  • 읽기/쓰기 성능 향상:
    데이터를 여러 샤드에 분산함으로써 병렬 처리(parallel processing)가 가능해집니다.
    추가되는 샤드마다 전체 처리량이 증가하며, 잘 분산된 데이터는 쿼리 성능을 개선합니다.
    여러 샤드가 동시에 쿼리를 처리할 수 있어 응답 시간이 빨라집니다.
  • 저장 용량 확장:
    샤드 수를 증가시키면 전체 저장 용량도 함께 증가합니다.
    예를 들어, 하나의 샤드가 4TB를 저장할 수 있다면, 샤드 하나가 추가될 때마다 4TB씩 확장됩니다.
    이를 통해 사실상 무제한에 가까운 저장 용량을 제공할 수 있으며, 데이터 증가에 따라 유연하게 확장할 수 있습니다.
  • 데이터 지역성(Data Locality) 지원:
    존 샤딩(Zone Sharding) 을 활용하면 데이터를 지정된 지리적 위치에 배치할 수 있어, 분산 애플리케이션에 최적화됩니다.
    예를 들어, 특정 지역의 데이터를 해당 지역의 서버에 저장하도록 정책을 설정할 수 있습니다.
    각 지역에는 하나 이상의 샤드가 할당될 수 있으며, 이를 통해 데이터 관리의 효율성과 적응성을 향상시킬 수 있습니다.
    또한, 샤드 키 값의 범위를 특정 존(Zone)과 연관시키는 방식으로 데이터를 배치하면, 지리적 위치에 따라 더 빠르고 정밀한 데이터 액세스가 가능합니다.

데이터 분산

샤딩된 MongoDB 클러스터에서 올바른 데이터 분산작업 부하 균형, 성능 향상, 확장성 개선을 위해 매우 중요합니다.
잘못된 데이터 분배는 병목 현상을 초래할 수 있으며, 자원 활용을 비효율적으로 만들 가능성이 큽니다.

샤드 키(Shard Key)

MongoDB는 컬렉션 단위로 샤딩을 수행하므로, 어떤 컬렉션을 샤딩할 것인지 선택할 수 있습니다.
그러나 샤드 키를 어떻게 설정하느냐가 매우 중요합니다.
잘못된 샤드 키를 선택하면 데이터가 고르게 분산되지 않아 일부 샤드에 부하가 집중되거나,
특정 샤드가 과부하(Hot Shard) 상태에 빠져 전체 성능이 저하될 수 있습니다.
이처럼 비효율적인 샤드 키 선택은 쿼리 성능 저하 및 시스템 리소스 낭비로 이어질 수 있으므로, 신중한 결정이 필요합니다.

 

MongoDB는 샤드 키를 기반으로 데이터를 샤드에 분산시킵니다.
샤드 키는 하나 이상의 문서 필드로 구성되며, 이를 기반으로 데이터를 서로 겹치지 않는 청크(Chunk) 단위로 나누고, 여러 샤드에 균등하게 배분합니다.

 

최근 MongoDB 버전에서는 샤딩 기능이 크게 개선되었습니다:

  • MongoDB 4.4부터, 샤딩된 컬렉션의 문서에서 샤드 키 필드를 생략할 수 있습니다.
    이 경우, 누락된 샤드 키 필드는 null 값으로 간주되어 샤드 간 데이터 분배가 수행됩니다.
  • MongoDB 4.4부터, 기존 샤드 키에 접미사 필드(suffix field)를 추가하여 확장 가능합니다.
  • MongoDB 5.0부터, 샤드 키를 변경(Resharding) 할 수 있습니다.

샤딩 전략

MongoDB는 샤드 클러스터에서 데이터를 분배하는 두 가지 주요 샤딩 전략을 제공합니다:

  • 범위 샤딩(Ranged Sharding)
    샤드 키 값을 기반으로 연속적인 범위(Range)로 데이터를 분할합니다.
    비슷한 샤드 키 값을 가진 문서들은 동일한 샤드 또는 동일한 청크에 저장될 가능성이 높습니다.
    따라서, 연속된 범위 데이터를 효과적으로 쿼리하는 데 적합합니다.
    그러나 잘못된 샤드 키를 선택하면 읽기/쓰기 성능이 저하될 수 있습니다.
    범위 샤딩은 별도의 해시 샤딩(Hashed Sharding) 또는 존(Zone) 설정을 하지 않는 한 기본적으로 사용되는 샤딩 방식입니다.

    범위 샤딩이 가장 효율적인 경우:
    • 높은 고유값(High Cardinality) 샤드 키를 사용할 때
      샤드 키의 카디널리티(Cardinality, 고유값의 개수)가 높을수록 청크를 더욱 세분화할 수 있습니다.
      카디널리티가 낮은 샤드 키를 선택하면, 클러스터의 수평 확장(Horizontal Scaling) 효율성이 떨어질 수 있습니다.

    • 균등한 발생 분포(낮은 값 집중도, Low Frequency)
      샤드 키의 빈도수는 데이터에서 특정 샤드 키 값이 얼마나 자주 나타나는지를 나타냅니다.
      문서의 상당수가 소수의 샤드 키 값만을 가지면(High Frequency), 해당 샤드 키 값을 포함하는 청크(Chunk)가 클러스터의 병목 현상을 유발할 수 있습니다.
      이러한 청크가 커지면, 더 이상 분할할 수 없는 점보 청크(Jumbo Chunk)로 발전할 가능성이 있으며,
      이로 인해 클러스터의 수평 확장(Horizontal Scaling) 효율성이 저하됩니다.

    • 비단조적(Non-Monotonically Changing) 값 사용
      일정하게 증가 또는 감소하는 값(단조 증가/감소 값, Monotonic Values)을 기반으로 샤드 키를 설정하면,
      삽입 연산이 특정 청크로 집중되는 문제가 발생할 수 있습니다.이는 클러스터에서 가장 큰 키 값(MaxKey)을 포함하는 범위를 가진 청크가 항상 존재하기 때문입니다.
      MaxKey는 다른 모든 값보다 크다고 간주되며, MinKey는 모든 값보다 작다고 간주됩니다.
      이러한 특성으로 인해, 지속적으로 증가하는 값이 샤드 키로 사용되면 특정 청크로 데이터가 몰릴 위험이 있습니다.
  • 해시 샤딩(Hashed Sharding)
    해시 샤딩은 샤드 키 값의 해시(Hash) 값을 계산한 후, 해시된 값을 기준으로 청크를 할당합니다.
    따라서 서로 가까운 값이라도 해시된 값이 다르면 다른 청크에 저장될 가능성이 큽니다.
    해시 샤딩은 범위 기반(Range-based) 연산에는 비효율적이지만, 단조 증가(change monotonically)하는 값(예: ObjectId, timestamp)을 포함하는 샤드 키에는 적합합니다.
    MongoDB의 기본 _id 필드가 ObjectId 값을 포함할 경우, 좋은 해시 샤드 키가 될 수 있습니다.

MongoDB 4.4에서는 단일 해시 필드를 포함하는 복합 인덱스(Compound Index)를 생성할 수 있는 기능이 추가되었습니다.
이러한 복합 해시 인덱스를 생성하려면 인덱스 생성 중 단일 인덱스 키의 값으로 hashed를 지정하면 됩니다. 복합 해시 인덱스는 복합 인덱스 내의 단일 필드에 대한 해시 값을 계산합니다. 이 값은 인덱스의 다른 필드와 함께 샤드 키로 사용됩니다.
다음 예시를 살펴보겠습니다:

db.planets.createIndex({ "name":1, "_id": "hashed" })
sh.shardCollection("sample_guides.planets", { "name": 1, "_id": "hashed" })

위의 명령은 name 필드에 대해 오름차순으로, _id를 해시 필드로 하는 복합 해시 인덱스를 생성하고 planets 컬렉션을 샤딩합니다.

존 샤딩과 같은 기능은 복합 해시 샤딩에서 지원됩니다. 여기서 접두사(즉, 첫 번째) 비해시 필드 또는 필드들이 존 범위를 정의하고, 해시 필드는 샤딩된 데이터의 더 공평한 분배를 보장합니다. 또한, 복합 해시 샤딩은 해시 접두사가 있는 샤드 키를 지원하여 단조롭게 증가하는 필드와 관련된 데이터 분배 문제를 해결하는 데 도움을 줍니다.

샤드 키 인덱스(Shard Key Index)

  • 이미 데이터가 존재하는 컬렉션을 샤딩하려면, 샤드 키가 첫 번째 필드로 포함된 인덱스가 필요합니다.
  • 그러나 비어있는(empty) 컬렉션을 샤딩할 경우, MongoDB는 자동으로 적절한 샤드 키 인덱스를 생성합니다(이미 적합한 인덱스가 없다면).

청크(Chunks)

MongoDB는 샤딩된 데이터를 "청크(Chunk)" 또는 "범위(Range)"로 관리합니다.

  • MongoDB 5.2부터 기본 청크 크기는 128MB이며, 이전 버전(5.2 미만)에서는 64MB였습니다.
  • 각 청크는 샤드 키를 기준으로 하한(포함)과 상한(미포함)을 가지며, 연속된 샤드 키 값을 포함합니다.
  • MongoDB 6.1부터 자동 청크 분할(Auto-Splitting)이 비활성화되었으며, 청크가 다른 샤드로 이동할 때만 분할이 수행됩니다.

참고: MongoDB 6.1 이전에는 청크 크기가 최대 크기를 초과하면 자동 분할(Auto-Splitter)에 의해 청크가 분할되었습니다.

밸런서(Balancer)와 균등 청크 분배(uniform chunk distribution)

MongoDB 6.0.3부터 샤딩된 클러스터에서 데이터 분배는 "청크 개수"가 아닌 "데이터 크기"를 기준으로 수행됩니다.

이를 위해 밸런서(Balancer)라는 백그라운드 프로세스가 각 샤드의 데이터 양을 추적하고, 청크를 샤드 간에 이동시킵니다.

특정 샤드가 설정된 마이그레이션 임계값(Migration Threshold)을 초과하면, 밸런서는 데이터를 고르게 분산하기 위해 샤드 간 청크 이동을 수행합니다. 이 과정은 존(Zone) 설정을 준수하면서 수행됩니다. 기본적으로 밸런싱 프로세스는 지속적으로 실행됩니다.

 

밸런서는 Config Server Replica Set(CSRS)의 Primary 노드에서 실행됩니다.

밸런서 작업은 애플리케이션과 사용자에게 투명하게 수행되지만, 실행 중에는 경미한 성능 저하가 발생할 수 있습니다.

이러한 성능 영향을 최소화하기 위해 밸런서는 다음과 같은 전략을 사용합니다:

  • 한 번에 하나의 마이그레이션만 수행
    하나의 샤드는 동시에 여러 개의 데이터 마이그레이션을 수행할 수 없습니다.
    MongoDB는 병렬 마이그레이션을 지원하지만,
    • 각 샤드는 특정 시점에 하나의 마이그레이션만 수행할 수 있습니다.
    • n개의 샤드가 있다면, MongoDB는 최대 n/2개의 동시 마이그레이션을 수행할 수 있습니다.
  • 데이터 불균형이 특정 임계값을 초과하면 밸런싱 실행
    가장 데이터가 많은 샤드와 가장 적은 샤드 간의 데이터 차이가 특정 임계값을 초과하면, 마이그레이션이 트리거됩니다.
    샤드 클러스터가 "균형 상태(Balanced)"로 간주되는 기준:
    • 샤드 간 데이터 크기 차이가 특정 컬렉션의 "설정된 범위 크기"의 3배 미만일 경우
      예를 들어, 기본 청크 크기(128MB)일 때, 두 샤드 간 데이터 크기 차이가 384MB 이상이면 밸런싱이 실행됩니다.

밸런싱 윈도우(Balancer Window) 설정 가능

운영 트래픽에 영향을 최소화하기 위해 밸런서가 실행되는 특정 시간대를 지정할 수 있습니다.
이를 "밸런싱 윈도우(Balancer Window)"라고 합니다.


청크 관리(Chunk administration)

일부 상황에서는 청크를 수동으로 관리해야 할 필요가 있습니다.

점보 청크(Jumbo chunks)

MongoDB에서 지정된 범위 크기를 초과하고 MongoDB가 자동으로 분할할 수 없는 청크는 점보 청크(jumbo)로 표시됩니다. MongoDB는 청크 분할과 균형 조정을 자동으로 관리하지만, 점보 청크를 관리하기 위해 수동 개입이 필요한 경우가 있습니다. 점보 플래그를 제거하는 가장 좋은 방법은 청크를 분할해보는 것입니다. 청크가 분할될 수 있으면 MongoDB는 청크 분할이 성공적으로 이루어진 후 점보 플래그를 제거합니다. 청크를 분할하려면 sh.splitAt() 또는 sh.splitFind() 메서드를 사용할 수 있습니다.

분할할 수 없는 청크(Indivisible chunks)

특정 경우에 MongoDB는 더 이상 점보 청크가 아닌 청크를 분할할 수 없습니다. 예를 들어, 단일 샤드 키 값을 가진 청크입니다. 이런 경우, 청크를 분할하여 플래그를 제거할 수 없습니다. 이런 경우에는 샤드 키를 수정(컬렉션을 다시 샤딩)하여 청크를 분할 가능하게 만들거나, 수동으로 플래그를 제거할 수 있습니다.

 

플래그를 수동으로 제거하려면, 관리 데이터베이스에서 clearJumboFlag 명령을 실행하고, 샤드 컬렉션의 네임스페이스와 다음 중 하나를 제공해야 합니다:

  • 점보 청크의 경계:
db.adminCommand({
  clearJumboFlag: "sample.customers",
  bounds: [{ "x": 5 }, { "x": 6 }]
})​
  • 점보 청크 내의 샤드 키와 값이 포함된 찾기 문서:
db.adminCommand({
  clearJumboFlag: "sample.customers",
  find: { "x": 5 }
})​

 

컬렉션에 해시된 샤드 키가 사용되는 경우, clearJumboFlag와 함께 find 필드를 사용하지 마세요. 해시된 샤드 키가 있는 컬렉션에는 bounds 필드를 사용하는 것이 더 적합합니다.

 

범위 미리 분할하기(Pre-splitting the ranges)

대부분의 경우, 샤드 클러스터는 수동 개입 없이 데이터 청크를 자동으로 생성, 분할 및 할당합니다. 그럼에도 불구하고 MongoDB가 충분한 청크를 생성하지 못하거나 필요한 처리량에 맞게 데이터를 배포할 수 없는 경우가 있습니다.

 

새로운 샤딩 클러스터에 대량의 데이터를 로드하려는 경우, 미리 분할을 통해 데이터를 처음부터 샤드 간에 고르게 분배할 수 있습니다. 이렇게 하면 하나의 샤드가 병목 현상이 되는 것을 방지할 수 있습니다.


주의:
현재 비어 있는 컬렉션에 대해서만 범위를 미리 분할하는 것이 좋습니다. 이미 데이터가 포함된 컬렉션에 대해 범위를 수동으로 분할하려고 하면 불규칙한 범위 경계와 크기가 발생할 수 있으며, 균형 조정 동작이 비효율적으로 작동하거나 전혀 작동하지 않을 수 있습니다.

 

빈 범위를 수동으로 분할하려면 split 명령을 사용할 수 있습니다. 이 명령은 샤드 클러스터에서 하나의 청크를 두 개의 별도 청크로 나눕니다. split 명령은  admin 데이터베이스에서 실행해야 합니다.

예를 들어 보겠습니다. myapp.products라는 컬렉션이 있고, 이 컬렉션을 네 가지 가격 범위로 미리 분할하려고 한다고 가정합니다. 샤드 키는 price 필드에 설정됩니다. 다음 코드를 사용하여 이를 달성할 수 있습니다.

// 지정된 분할 지점을 기준으로 컬렉션을 청크로 나눕니다.
var splitPoints = [20, 50, 100];
// 참고: 'split' 명령은 middle, find, 또는 bounds 옵션을 사용할 수 있습니다.
// 이 예제에서는 middle 옵션을 사용하여 분할 지점을 지정합니다.
for(var i = 0; i < splitPoints.length; i++) {
  db.adminCommand({
    split: "myapp.products",
    middle: {
      price: splitPoints[i]
    }
  });
}

 

이 코드는 데이터를 네 가지 가격 범위로 나눕니다:

  • 가격이 $20 미만인 제품
  • 가격이 $20에서 $50 사이인 제품
  • 가격이 $50에서 $100 사이인 제품
  • 가격이 $100 이상인 제품

MongoDB 6.0부터, 밸런서는 데이터 크기를 기반으로 데이터를 샤드에 분배합니다. 단순히 범위를 분할하는 것만으로는 데이터가 샤드 간에 고르게 분배되지 않을 수 있습니다. 따라서 균형 잡힌 분배를 보장하기 위해 청크를 수동으로 이동해야 합니다:

// 청크를 이동할 샤드 목록
var shards = ["shard0000", "shard0001", "shard0002", "shard0003"];
for (var i = 0; i < splitPoints.length; i++) {
  var lowerBound = { price: MinKey };
  if (i > 0) {
    lowerBound = { price: splitPoints[i-1] };
  }
  var upperBound = { price: MaxKey };
  if (i < splitPoints.length - 1) {
    upperBound = { price: splitPoints[i] };
  }
  // 청크를 원하는 샤드로 수동으로 이동
  db.adminCommand({
    moveChunk: "myapp.products",
    find: lowerBound,
    to: shards[i],
    bounds: [lowerBound, upperBound]
  });
}​

 

이 접근 방식은 데이터를 처음부터 샤드 간에 고르게 분배하는 데 도움이 됩니다.


샤딩된 데이터 쿼리(Querying sharded data)

MongoDB 샤딩 클러스터에서 데이터를 쿼리하는 방식은 단일 서버 배포나 복제 세트에서 데이터를 쿼리하는 방식과 다릅니다. 단일 서버나 복제 세트의 기본 서버에 연결하는 대신, mongos에 연결하여 쿼리 라우터 역할을 하며 어떤 샤드에서 데이터를 가져올지 결정합니다.
 
다음 섹션에서는 mongos 라우터의 작동 방식을 살펴봅니다.

몽고스 라우터 (The mongos router)

mongos 인스턴스는 MongoDB 클러스터에 대한 유일한 인터페이스이자 진입점입니다. 애플리케이션은 mongos에 연결하며, 샤드에 직접 연결하지 않습니다. mongos는 쿼리를 실행하고, 결과를 수집하여 애플리케이션에 전달합니다. mongos 프로세스는 영속적인 상태를 유지하지 않으며 일반적으로 시스템 리소스를 많이 사용하지 않습니다. 요청에 대한 프록시 역할을 합니다. 쿼리가 들어오면 mongos는 이를 검사하고, 어떤 샤드가 쿼리를 실행해야 할지 결정한 후, 모든 대상 샤드에서 커서를 생성합니다.

 

find (쿼리)

쿼리에 샤드 키 또는 샤드 키의 접두사가 포함되어 있으면, mongos는 타겟팅된 작업을 수행하여 데이터를 찾고 있는 샤드만 쿼리합니다.
예를 들어, 사용자 컬렉션의 복합 샤드 키가 _id, email, country일 경우 다음과 같은 쿼리가 있습니다:

javascript
db.user.find({ _id: 1 })
db.user.find({ _id: 1, "email": "packt@packt.com" })
db.user.find({ _id: 1, "email": "packt@packt.com", "country": "UK" })​
 

이 쿼리들은 샤드 키의 접두사(처음 두 쿼리처럼) 또는 전체 샤드 키를 포함합니다. 반면에 { email, country } 또는 { country }와 같은 쿼리는 올바른 샤드를 타겟팅할 수 없으며, 그 결과 브로드캐스트 작업이 발생합니다. 브로드캐스트 작업은 샤드 키나 샤드 키의 접두사가 포함되지 않은 작업으로, mongos가 모든 샤드를 쿼리하게 됩니다. 이는 또한 scatter-gather 작업 또는 팬아웃 쿼리로 알려져 있습니다.

 

sort(), limit(), skip()

결과를 정렬하려면 두 가지 방법이 있습니다:

  • 샤드 키를 정렬 기준으로 사용하는 경우, mongos는 쿼리할 샤드의 순서를 결정할 수 있어 효율적이고 타겟팅된 작업이 됩니다.
  • 샤드 키를 정렬 기준에 사용하지 않는 경우, 정렬 기준 없이 쿼리하는 것과 마찬가지로 팬아웃 쿼리가 됩니다. 샤드 키를 사용하지 않고 결과를 정렬하려면, 기본 샤드가 로컬에서 분산 병합 정렬을 실행한 후 정렬된 결과 세트를 mongos에 전달합니다.

쿼리에 대한 limit()는 각 개별 샤드에서 시행되며, 그 후 mongos에서 다시 시행됩니다. 여러 샤드에서 결과가 나올 수 있기 때문입니다. 반면, skip 연산자는 개별 샤드에 전달되지 않으며, mongos가 로컬에서 모든 결과를 가져온 후 이를 적용합니다.

 

skip()과 limit() 커서 메서드를 결합하면 mongos가 쿼리를 최적화하여 두 값을 개별 샤드에 전달합니다. 이는 페이지네이션과 같은 경우에 유용합니다.
정렬 없이 쿼리하고 결과가 여러 샤드에서 나오는 경우, mongos는 샤드들 간에 라운드 로빈 방식으로 결과를 반환합니다.

Update와 Delete (업데이트 및 삭제)

MongoDB 7.0부터는 문서 수정 작업(예: 업데이트 및 삭제)이 간소화되었습니다. 수정자의 find() 섹션에 샤드 키가 포함되어 있으면 mongos가 쿼리를 적절한 샤드로 라우팅할 수 있습니다. 그러나 find() 섹션에 샤드 키가 포함되지 않으면, 이전 버전에서와 같이 팬아웃 작업을 발생시키지 않습니다.

 

이제 updateOne(), deleteOne(), 또는 findAndmodify() 작업에서는 더 이상 샤드 키나 _id 값을 반드시 포함할 필요가 없습니다. 어떤 필드도 문서를 일치시키는 데 사용할 수 있으며, 이는 비샤드 컬렉션과 유사합니다. 그러나 이러한 작업에서 샤드 키를 사용하는 것이 여전히 더 효율적입니다. 이는 타겟팅된 쿼리를 가능하게 하기 때문입니다.

예를 들어, MongoDB 6.0에서는 updateOne()을 실행하려면 샤드 키를 반드시 전달해야 했습니다:

db.cities.updateOne({ "city": "New York City" }, { $set: { "population" : 8500000 }});​
 

위 예제에서 city는 샤드 키를 나타냅니다. 그러나 MongoDB 7.0에서는 필터에 샤드 키를 포함하지 않고 updateOne() 작업을 실행할 수 있습니다:

db.cities.updateOne({ "population" : 293200 },{ $set: { "areaSize" : 211 }});​
 


다음은 MongoDB 7.0에서 샤딩과 관련된 작업에 대한 설명을 요약한 표입니다:

 

작업(Operation) 작업(Operation)
insert() 샤드 키를 포함해야 함
update() 샤드 키를 포함할 수 있지만, 필수는 아님
샤드 키를 포함한 쿼리 타겟팅된 작업 
샤드 키를 포함하지 않은 쿼리 내부적으로 scatter-gather 작업
인덱스 정렬, 샤드 키를 포함한 쿼리 타겟팅된 작업 (Targeted operation)
인덱스 정렬, 샤드 키를 포함하지 않은 쿼리 분산 정렬 병합 (Distributed sort merge)
updateOne(), replaceOne(), deleteOne() 어떤 필드도 매칭할 수 있지만, 샤드 키를 사용할 경우 더 효율적

헤지드 리드 (Hedged Reads)

MongoDB 4.4부터, mongos 인스턴스는 기본  읽기 우선순위가 아닌 읽기*를 사용할 때 헤지드 리드를 지원할 수 있습니다. mongos 인스턴스는 각 샤드에 대해 복제 세트의 두 멤버로 읽기 작업을 지시하고, 각 샤드의 첫 번째 응답자에서 결과를 반환합니다.
헤지드 리드를 지원하는 작업에는 collStats, count, dataSize, dbStats, distinct, filemd5, find, listCollections, listIndexes 및 planCacheListFilters가 포함됩니다.

 

(* 역자주 - primary 읽기 설정에서는 헤지드 리드를 사용할 수 없고, primaryPreferred 읽기 설정에서도 프라이머리를 사용할 수 없는 경우에만 헤지드 리드가 적용됩니다. 헤지드 리드를 사용하면 mongos 인스턴스가 각 샤드의 두 복제본 세트 멤버로 읽기 작업을 라우팅하고, 가장 빠르게 응답하는 멤버의 결과를 반환합니다. MongoDB 8.0부터는 헤지드 리드가 더 이상 기본적으로 활성화되지 않으며, 명시적으로 설정해야 합니다.
따라서 secondaryPreferred, secondary, nearest 읽기 설정을 사용하고 헤지드 리드 옵션을 명시적으로 활성화하면 이 기능을 활용할 수 있습니다.
)


샤딩 방법 (Sharding Methods)

MongoDB는 데이터 분배를 관리하기 위해 일련의 helper 메서드를 제공합니다. 이 메서드들은 샤딩을 활성화하고, 데이터가 어떻게 분배되어야 하는지를 정의하며, 샤딩 상태를 모니터링하는 데 사용됩니다. 이들은 여러 머신에 걸쳐 데이터를 효율적으로 확장할 수 있도록 샤딩된 MongoDB 배포를 관리하는 데 필수적인 도구입니다. 다양한 mongosh 셸 helper 메서드는 다음과 같습니다:

  • sh.shardCollection(): MongoDB에서 샤딩을 설정하는 데 필수적입니다. 컬렉션이 샤딩된 후에는 MongoDB에서 이를 unshard하는 방법을 제공하지 않습니다. 그러나 필요에 따라 샤드 키를 나중에 변경할 수 있습니다.
  • db.collection.getShardDistribution(): 특정 샤딩된 컬렉션의 샤드 간 데이터 분배에 대한 자세한 분석을 제공합니다. 이 명령이 표시하는 출력 정보의 요약은 다음과 같습니다:
출력 타입 설명
<shard-x> String 샤드 이름을 포함
<host-x> String 호스트 이름을 포함
<size-x> Number 데이터 크기와 측정 단위 포함
<count-x> Number 샤드 내 문서 수
<number of chunks-x> Number 샤드 내 청크 수
<size-x>/<number of chunks-x> Calculated value 샤드의 청크당 데이터 크기(측정 단위 포함)
<count-x>/<number of chunks-x> Calculated value 샤드의 청크당 문서 수
<stats.size> Number 샤딩된 컬렉션의 데이터 총 크기(측정 단위 포함)
<stats.count> Number 샤딩된 컬렉션의 문서 총 수
<calc total chunks> Calculated value  모든 샤드에서의 청크 수
<estDataPercent-x> Calculated value 각 샤드의 데이터 크기가 컬렉션의 전체 데이터 크기에서 차지하는 비율
<estDocPercent-x> Calculated value 각 샤드의 문서 수가 컬렉션의 전체 문서 수에서 차지하는 비율을 나타냄

Table 2.2: Output information for db.collection.getShardDistribution()

 

  • sh.status()는 샤딩된 클러스터에 대한 정보를 제공합니다. 출력의 세부 수준은 verbose 매개변수를 통해 조정할 수 있으며, 고수준 개요 또는 자세한 보고서를 선택할 수 있습니다. 이 명령이 제공하는 정보에는 샤딩 버전, 각 샤드에 대한 세부 사항, 활성 mongos 인스턴스의 상태, 자동 분할 상태, 발란서 상태 및 데이터베이스 및 샤딩된 컬렉션에 대한 정보가 포함됩니다.
  • MongoDB 5.0부터는 sh.reshardCollection() 메서드를 사용하여 컬렉션의 샤드 키를 수정하고, 데이터를 클러스터 전체에서 어떻게 분배할지를 변경할 수 있습니다. 그러나 리샤딩 작업을 시작하기 전에 애플리케이션이 리샤딩 중에 2초의 쓰기 차단을 허용할 수 있는지 확인해야 하며, 이는 지연을 증가시킬 수 있습니다. 만약 이를 허용할 수 없다면 샤드 키를 조정하는 것을 고려해야 합니다. 또한 데이터베이스 서버는 다음과 같은 필수 자원을 보유해야 합니다:
    • 저장소: 각 샤드의 사용 가능한 저장소가 리샤딩할 컬렉션 크기의 최소 2.2배 이상이어야 합니다. 예를 들어, 1TB의 컬렉션의 경우, 각 샤드는 최소 2.2TB의 여유 저장소가 필요합니다.
      • 필요한 저장소 계산식: 각 샤드에 필요한 저장소 = (컬렉션 저장소 크기 * 2.2) / 컬렉션이 분배될 샤드 수
      • 예시 계산: (2TB * 2.2) / 4 샤드 = 1.1TB/샤드
    • I/O: I/O 용량은 50%를 초과하지 않아야 합니다.
    • CPU 사용량: CPU 사용량을 80% 이하로 유지해야 합니다.
  • sh.getBalancerState()는 발란서가 현재 활성 상태인지 여부를 나타내는 불리언 값을 반환합니다.
  • $shardedDataDistribution은 이 집계 단계에서 샤딩된 클러스터에서 데이터의 분포에 대한 정보를 반환합니다. 이를 통해 데이터가 각 샤드에 어떻게 분배되어 있는지에 대한 세부 사항을 확인할 수 있습니다.

MongoDB 7.0의 새로운 샤딩 클러스터 기능

MongoDB 7.0은 운영 및 개발자 사용 사례 모두에서 샤딩된 클러스터의 관리와 이해를 더욱 단순화합니다. 이 버전은 초기 및 향후 샤드 키 선택을 위한 최상의 결정을 내리는 데 도움이 되는 추가적인 통찰력을 제공합니다. 또한 MongoDB 7.0부터는 개발자가 샤딩된 클러스터와 비샤딩 클러스터에서 동일한 명령어 인터페이스를 경험할 수 있으며, 필요한 경우 성능 최적화를 위한 옵션도 제공합니다. MongoDB 7.0에 도입된 새로운 샤딩 기능을 살펴보겠습니다.

샤드 키 어드바이저 명령어

샤드 키를 선택하는 것은 복잡한 데이터 패턴과 트레이드오프가 있기 때문에 어려운 작업입니다. 그러나 MongoDB 7.0의 새로운 기능은 이 작업을 더 쉽게 만들어줍니다:

  • analyzeShardKey: 기존 데이터를 기준으로 후보 샤드 키를 평가할 수 있는 기능을 제공합니다. MongoDB 7.0의 샤드 키 분석은 샤드 키의 적합성을 평가할 수 있는 지표를 제공합니다. 이 지표에는 고유성(카디널리티), 빈도, 그리고 값이 지속적으로 증가하는지 또는 감소하는지(단조성)가 포함됩니다. 이 명령어는 복제 세트뿐만 아니라 샤딩된 클러스터에서도 실행할 수 있습니다.
  • configureQueryAnalyzer: 클러스터 내의 쿼리 라우팅 패턴에 대한 지표를 제공하여, 불균형 부하와 핫 샤드를 식별하는 데 도움을 줍니다. 이 명령어는 이전 구성과 새 구성을 설명하는 필드를 포함한 문서를 반환합니다. 이 두 명령어를 통해 초기 샤드 키를 확신을 가지고 설정하거나 실시간 리샤딩을 준비할 수 있으며, 선택한 샤드 키에 대한 신뢰를 보장하는 데 필요한 데이터를 제공합니다.
  • mergeAllChunksOnShard: 이 명령어는 특정 샤드가 소유한 주어진 컬렉션의 모든 병합 가능한 청크를 통합하거나 병합하는 기능을 제공합니다. 샤딩 유지보수 작업 중 성능 저하를 해결하는 데 도움이 되며, 특정 샤드에서 병합할 수 있는 모든 청크를 찾아 병합합니다. 이를 통해 단편화를 줄이고, 샤딩 유지보수 작업 중 쿼리해야 할 청크 수를 줄여 성능을 향상시킬 수 있습니다.

AutoMerger

AutoMerger는 특정 병합 가능성 요구 사항을 충족하는 청크를 자동으로 병합하는 기능입니다. 이 과정은 균형 작업의 일환으로 백그라운드에서 실행됩니다. AutoMerger는 발란서가 처음 활성화될 때 시작되며, 각 실행 후 설정된 간격(autoMergerIntervalSecs) 동안 일시 중지됩니다. 활성화된 경우, AutoMerger는 매번 간격마다 자동 병합을 수행합니다. 각 컬렉션에 대해 최소 지연(autoMergerThrottlingMS)을 설정하여 후속 병합 사이에 시간을 유지합니다. 발란싱 윈도우가 정의되면 AutoMerger는 해당 윈도우 내에서만 작동합니다. 두 개 이상의 인접한 청크가 동일한 컬렉션에 있을 경우, 다음 조건을 모두 충족하면 병합 가능합니다:

  • 병합이 가능한 청크들이 동일한 컬렉션에 속해야 하며,
  • 두 청크가 인접해야 하고,
  • 병합을 위한 특정 조건을 충족해야 합니다.
  • 청크들은 동일한 샤드에 속해야 합니다.
  • 점보 청크는 마이그레이션에 참여할 수 없습니다.
  • 청크는 트랜잭션이나 스냅샷 읽기를 방해하지 않고 안전하게 마이그레이션 기록을 삭제할 수 있어야 합니다. 즉, 마지막 마이그레이션이 minSnapshotHistoryWindowInSeconds와 transactionLifetimeLimitSeconds 값만큼 이전에 발생해야 합니다.

AutoMerger 동작 제어 방법

  • sh.startAutoMerger(): AutoMerger 시작
  • sh.stopAutoMerger(): AutoMerger 중지
  • sh.enableAutoMerger(): AutoMerger 활성화
  • sh.disableAutoMerger(): AutoMerger 비활성화

샤드 키 없이 명령어 지원

updateOne, deleteOne, findAndModify와 같은 명령어는 샤딩된 클러스터와 비샤딩 클러스터 모두에서 일관되게 사용되며, 필요 시 성능 최적화를 위한 옵션을 제공합니다.

요약

MongoDB에서의 복제는 여러 서버 간에 데이터를 동기화하여 데이터 가용성을 증가시키고, 장애 시 복구를 가능하게 합니다. 복제 세트 내에서 하나의 노드는 기본(primary) 노드로 지정되어 모든 쓰기 작업을 처리하고, 나머지 노드는 이 작업들을 복제하여 데이터를 동기화합니다. 이러한 구조는 장애 조치(failover) 및 복구를 위한 강력한 시스템을 제공합니다.

MongoDB에서의 샤딩은 데이터를 여러 서버(샤드)에 분할하고 배포하는 방법입니다. 각 샤드는 독립적인 복제 세트로 구성되며, 이들 샤드들이 모여 하나의 논리적 데이터베이스인 샤딩된 클러스터를 형성합니다. 이 접근법은 매우 큰 데이터셋과 높은 처리량을 필요로 하는 배포 환경을 지원하며, 확장성 문제를 해결하는 데 효과적입니다.

 

다음 장에서는 MongoDB 개발자 도구의 다양한 카테고리를 살펴보고, MongoDB Shell, MongoDB CLI, MongoDB Compass, 그리고 VS Code용 MongoDB를 사용하여 생산성을 높이는 방법에 대해 알아볼 것입니다.

'T. > Mastering MongoDB 7.0' 카테고리의 다른 글

5. CRUD Operations and Basic Queries  (0) 2025.03.10
4. Connecting to MongoDB  (0) 2025.03.10
3. Developer Tools  (0) 2025.03.10
1. Introduction to MongoDB  (2) 2025.03.10
0. 관련 자료와 용례  (0) 2025.03.10