MongoDB Replica Set 구축하기

Chanjoo Lee
17 min readMar 1, 2023

본 글은 2021년 12월 19일 이곳에 작성된 글입니다.

📌 읽기 전에

  • 본 글은 저희가 운영중인 MongoDB 서버를 Atlas로 마이그레이션하기 전, 동일한 환경을 구성하여 프로덕션 환경에서의 마이그레이션을 시뮬레이션(?)하기 위한 과정에서 Replica Set을 구축한 과정을 기록한 글입니다.
  • Replica Set을 간단히 구축해보고 싶거나, 구축 과정에 대한 이해를 하기에 적합한 수준으로 작성하였습니다.
  • Replica Set의 개념에 대해 알고싶으시다면 앞부분을, 직접 구축해보고 싶으신 분들은 뒷부분을 집중해주세요.

Replica Set?

  • Kubernetes에도 동일한 개념이 존재하지만 여기서는 MongoDB 관점에서의 개념을 설명하고자 합니다.
  • Replica(Replication)부터 설명하자면, DB의 데이터들을 여러 서버에 동기화(synchronization)하는 것을 의미합니다.. 여러 서버가 모두 동일한 데이터를 가짐에 따라 하나의 서버가 다운되더라도 제공하는 서비스에 문제가 생기지 않고 운영을 할 수 있다는 장점이 있습니다.. 각 서버에 데이터 복구/리포팅/백업 역할(용도)을 설정할 수도 있습니다.

Replica Set을 구축하는 이유?

서비스 운영에 MongoDB를 사용할 경우, Replica Set을 구축하지 않을 이유는 없어보입니다. 물론 구축과 실제 운영 알아야할 것들에 차이가 있지만, 후회하지 않는 선택이라고 생각합니다.

  • 데이터를 안전하게 보존하기 위해
  • 24시간 접근 가능한 데이터의 상태를 유지하기 위해
  • 서비스 운영시 다운타임(인덱스 적용, 백업 작업 등에 의해)을 없애기 위해
  • 기타 등등…

용어

(1) Primary node, Secondary node

모든 write 작업을 수행합니다. primary node에 해당 작업이 수행되면 oplog라는 것에 모든 작업 로그를 저장하는데, secondary node에서는 이 oplog를 보고 동일한 작업을 수행합니다. 쉽게 말해서 primary node에서 변화된 데이터를 복사하는 것이죠.

출처 : MongoDB 공식 문서

(2) Election

우리 말로는 ‘선거’, primary node가 이용 불가능한 상태에 빠졌을 때, secondary node들 중에서 primary node를 하나 정하는 과정을 의미합니다.

(3) Arbiter node

primary나 secondary node처럼 데이터를 가지진 않고 secondary node들 중에서 primary node를 선정하는 election 과정에 참여합니다. arbiter node가 primary node가 될 수는 없습니다.

(4) Heartbeat

Replica set 내의 모든 노드들은 정해진 초(second)마다 서로에게 heartbeat(일종의 ping)를 보냅니다. 귀엽지 않나요? 이러한 Heartbeat가 특정 초 동안 수신되지 않으면 다른 node들이 Election을 주섬주섬 준비합니다.

📌 구성

Replica Set은 동일한 데이터를 가진 여러 node(여기서는 서버)로 이루어져있으며, 선택적으로 하나의 Aribiter node를 포함시킬 수 있습니다. 데이터를 가진 node들 중에서는 반드시 하나의 primary node를 지정해줘야하며, 나머지 node들은 secondary node라고 칭합니다.

node들의 구성에 따라 대표적인 구성 방식을 소개하고자 하는데요, Replica Set을 구성하는 node의 개수는 최소 3개 이상입니다. 그 중에 대표적인 3개로 이루어진 경우는 다음과 같습니다.

(1) P-S-S (Primary + Secondary + Secondary)

  • 하나의 Primary와 두개의 Secondary node로 이루어진 구성입니다. Primary node에 문제가 생기더라도 Secondary node 2개나 그 자리를 대신할 수 있으니 든든(?)합니다. 높은 안정성( high availability )을 보장할 수 있습니다.

(2) P-S-A(Primary + Secondary + Arbiter)

  • Primary, Secondary, Arbiter node 각 1개씩으로 이루어진 구성입니다. P-S-S 구성과는 다르게 Arbiter node가 추가되었는데요, 이 node는 Primary나 Secondary와는 다르게 서버의 리소스는 많이 필요하지 않지만 데이터를 실제로 담고있지는 않기 때문에 상대적으로는 구성의 안정성이 낮습니다(그렇다고 안정성이 좋지 않은 것은 아닙니다).

📌 구축하기

Step #1: 각 인스턴스에 MongoDB 설치
Step #2: Replica Set 설정

이번에는 실제로 MongoDB 서버를 3개 구축하고 이들을 P-S-A Replica Set으로 설정하는 방법을 알아보겠습니다. 전체 과정은 MongoDB의 공식 문서 를 참고하였습니다.

필자의 경우 Replica Set으로 운영중인 서비스를 마이그레이션하기 전, 개발 환경에서 동일한 환경을 구축하여 테스트하는 용도로 진행하였습니다.

✅ Step #1 : 각 인스턴스에 MongoDB 설치

  • 본 글에서 ‘인스턴스’는 AWS의 EC2 인스턴스를 의미합니다.
  • EC2 인스턴스 생성과 초기 설정에 익숙한 독자를 기준으로 설명하였습니다.
  • 아래와 같은 과정을 총 3번 진행해주세요.

(1) EC2 인스턴스에 MongoDB를 설치하기(이 문서 참고)

  • Platform(x86_64, arm64 등)에 따른 MongoDB 버전을 잘 체크해야합니다.
  • 인스턴스 OS(ex. Ubuntu, Debian 등)도 지원되는 버전을 잘 확인해야합니다.
  • 설치 커맨드 예시(MongoDB 4 버전)
$wget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add -

$echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list

$sudo apt-get update

$sudo apt-get install -y mongodb-org

(2) 인스턴스의 27017번 포트를 열기

  • Security Group의 inbound rule을 수정해야합니다.

(3) Primary node로 설정할 인스턴스를 정하고, admin 유저의 비밀번호 설정하기

$mongo

> use admin
> db.createUser({"user": "admin", "pwd":"{비밀번호}", "roles": [{"role":"root", "db": "admin"}]}) # 예시 권한
  • Primary node에 비밀번호를 설정하지 않으면 불시에(?) 해킹을 당할 수 있으므로 반드시 진행해주세요.

✅ Step #2: Replica Set 설정

고려해야할 것

  • 공식 문서에도 나와있지만, 위의 Step#1 에서 생성한 인스턴스들은 ip보다는 host name(도메인 주소)로 접근하는 것을 권장하고있습니다. AWS의 경우 Route53을 통해 도메인을 설정할 수 있으니 먼저 진행해주세요.(ex. {primary/secondary/arbitery}.mongo.db})

(1) 각 인스턴스의 MongoDB 설정 파일(/etc/mongo.conf) 수정하기

  • Replica Set으로 설정할 모든 인스턴스에서 동일하게 수정해줘야합니다.
  • 설정값을 아래와 같이 수정해줍니다.

(수정1) net
: localhost와 해당 인스턴스의 host name으로 설정

(수정2) security
: Replica Set 내의 node들 간의 인증에 사용되는 키를 생성해줘야합니다. 만약 이 부분을 설정해주지 않으면 DBClientConnection failed to receive message from 127.0.0.1:27017 - HostUnreachable: Connection closed by peer 에러가 발생할 수 있습니다. 이 문서를 참고하여 키파일을 생성하고, 키파일의 경로를 추가해주세요.

(수정3) replication
: 원하는 replica set 이름을 설정해주세요.

  "/etc/mongod.conf" 43L, 637C        24,30         All
# mongod.conf

# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/

# Where and how to store data.
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
# engine:
# mmapv1:
# wiredTiger:

# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log

# 수정(1)
net:
port: 27017
bindIp: localhost,{해당 인스턴스의 host name}

# how the process runs
processManagement:
timeZoneInfo: /usr/share/zoneinfo

# 수정(2)
security:
authorization: "enabled"
clusterAuthMode: "keyFile"
keyFile: "{키 파일 경로}"

#operationProfiling:

# 수정(3)
replication:
replSetName: "{원하는 이름}"

#sharding:

## Enterprise-Only Options:

#auditLog:

#snmp: 37,36 All

(2) Primary node에서 replica set 생성하기

  • (1)에서 설정 파일을 잘 생성했다면, 이제는 각 인스턴스에서 mongod(mongo daemon)을 실행해줘야합니다.
// mongo daemon 실행
$sudo mongod --config /etc/mongod.conf --fork(백그라운드로 실행)
  • Primary node로 설정하고자하는 인스턴스의 mongo daemon에 접속합니다.
$mongo --port 27017 -u "admin" -p
Enter password: {Step#1의 (3)에서 설정한 비밀번호 입력}
  • Replica set 생성
> rs.initiate()

(3) Replica Set에 Secondary, Arbiter node 추가하기

  • Secondary node 인스턴스에 접속해서 추가하기
$mongo
> rs.add({host:"{Secondary node 도메인주소:27017}", priority:1})
  • Arbiter node 인스턴스에 접속해서 추가하기
$mongo
> rs.add({host:"{Arbitrer node 도메인주소:27017}", priority:1, arbiterOnly:true})

(4) Replica set 생성 결과 확인(Primary node 인스턴스에서)

$mongo --port 27017 -u "admin" -p
Enter password: {Step#1의 (3)에서 설정한 비밀번호 입력}
> rs.status()

{
"set" : "{Replica set 이름}",
"date" : ISODate("2021-11-23T15:29:07.810Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1637681296, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2021-11-23T08:51:26.230Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1637657486, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2021-11-23T08:51:26.231Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2021-11-23T08:51:26.314Z")
},
"members" : [
{
"_id" : 0,
"name" : "{Primary node 도메인 주소:27017}",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 23873,
"optime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-11-23T15:29:06Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1637657486, 2),
"electionDate" : ISODate("2021-11-23T08:51:26Z"),
"configVersion" : 3,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "{Arbiter node 도메인 주소:27017}",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 193,
"lastHeartbeat" : ISODate("2021-11-23T15:29:07.005Z"),
"lastHeartbeatRecv" : ISODate("2021-11-23T15:29:07.013Z"),
"pingMs" : NumberLong(1),
"lastHeartbeatMessage" : "",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : 3
},
{
"_id" : 2,
"name" : "{Secondary node 도메인 주소:27017}",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 160,
"optime" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1637681346, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2021-11-23T15:29:06Z"),
"optimeDurableDate" : ISODate("2021-11-23T15:29:06Z"),
"lastHeartbeat" : ISODate("2021-11-23T15:29:06.943Z"),
"lastHeartbeatRecv" : ISODate("2021-11-23T15:29:06.251Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "test.primary.mongo.db:27017",
"syncSourceHost" : "test.primary.mongo.db:27017",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 3
}
],
"ok" : 1,
"operationTime" : Timestamp(1637681346, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1637681346, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}

위와같이 보인다면 Replica Set 설정이 완료된 것입니다. 이제 Primary node의 주소를 가지고 어플리케이션 코드에서 적용시켜보면서 테스트해보자구요!

--

--