본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 27. · 54 Views
MongoDB 복제셋 완벽 가이드
MongoDB 복제셋의 아키텍처부터 장애 복구, 읽기 분산까지 초급 개발자가 알아야 할 모든 것을 담았습니다. 실무에서 바로 적용할 수 있는 예제와 함께 술술 읽히는 이북 스타일로 설명합니다.
목차
1. 복제셋 아키텍처
어느 날 김개발 씨가 운영하던 서비스의 데이터베이스 서버가 갑자기 다운되었습니다. 모든 사용자가 서비스를 이용할 수 없게 되었고, 복구하는 데 무려 2시간이나 걸렸습니다.
이후 선배 박시니어 씨가 "복제셋을 구성했어야지"라고 말했습니다.
**복제셋(Replica Set)**은 동일한 데이터를 가진 여러 MongoDB 서버의 그룹입니다. 마치 중요한 서류를 여러 금고에 나누어 보관하는 것과 같습니다.
하나의 금고가 잠겨도 다른 금고에서 서류를 꺼낼 수 있듯이, 복제셋은 서버 장애에도 서비스를 지속할 수 있게 해줍니다.
다음 코드를 살펴봅시다.
// MongoDB 복제셋 기본 구성 (3개 노드)
const replicaSetConfig = {
_id: "myReplicaSet",
members: [
{ _id: 0, host: "mongo1.example.com:27017", priority: 2 },
{ _id: 1, host: "mongo2.example.com:27017", priority: 1 },
{ _id: 2, host: "mongo3.example.com:27017", priority: 1 }
]
};
// 복제셋 연결 문자열
const uri = "mongodb://mongo1.example.com:27017,mongo2.example.com:27017,mongo3.example.com:27017/?replicaSet=myReplicaSet";
// Node.js에서 복제셋 연결
const client = new MongoClient(uri);
await client.connect();
김개발 씨는 스타트업에서 백엔드 개발을 담당하고 있습니다. 어느 날 새벽 3시, 갑자기 전화가 울렸습니다.
"서비스가 안 돼요!" 급하게 노트북을 열어보니 데이터베이스 서버가 먹통이 되어 있었습니다. 단일 서버로 운영하던 MongoDB가 하드웨어 장애로 멈춰버린 것입니다.
다음 날 출근한 박시니어 씨가 김개발 씨에게 물었습니다. "혹시 복제셋 구성은 해두셨어요?" 김개발 씨는 고개를 저었습니다.
그 개념 자체를 처음 들어본 것입니다. 그렇다면 복제셋이란 정확히 무엇일까요?
쉽게 비유하자면, 복제셋은 마치 은행의 금고 시스템과 같습니다. 은행은 고객의 귀중품을 단 하나의 금고에만 보관하지 않습니다.
여러 지점에 동일한 복사본을 만들어 두어, 한 지점에 문제가 생겨도 다른 지점에서 서비스를 제공할 수 있습니다. MongoDB 복제셋도 이와 동일한 원리로 작동합니다.
복제셋이 없던 시절에는 어떤 일이 벌어졌을까요? 단일 서버 구성에서는 서버가 다운되면 모든 서비스가 중단됩니다.
데이터 백업이 있더라도 복구에는 상당한 시간이 소요됩니다. 더 큰 문제는 복구 중에 발생하는 데이터 손실입니다.
마지막 백업 이후의 모든 데이터가 사라질 수 있습니다. 바로 이런 문제를 해결하기 위해 복제셋이 등장했습니다.
복제셋은 일반적으로 3개 이상의 노드로 구성됩니다. 왜 3개일까요?
이는 과반수 투표 시스템과 관련이 있습니다. 한 노드가 다운되어도 나머지 2개 노드가 과반수를 형성할 수 있기 때문입니다.
홀수 개의 노드를 사용하는 것이 권장되는 이유입니다. 위의 코드를 살펴보겠습니다.
_id 필드는 복제셋의 이름을 지정합니다. members 배열에는 복제셋을 구성할 각 서버의 정보가 담깁니다.
priority 값은 해당 노드가 Primary가 될 우선순위를 나타냅니다. 값이 높을수록 Primary로 선출될 가능성이 높아집니다.
연결 문자열을 보면 세 개의 호스트가 쉼표로 구분되어 있습니다. 애플리케이션은 이 중 하나라도 연결 가능하면 복제셋 전체에 접근할 수 있습니다.
하나의 서버가 다운되어도 나머지 서버로 자동 연결되는 것입니다. 실제 현업에서 복제셋은 거의 필수입니다.
24시간 서비스를 운영하는 쇼핑몰, 금융 서비스, 게임 서버 등 대부분의 프로덕션 환경에서 복제셋을 사용합니다. 데이터 손실은 비즈니스에 치명적인 영향을 미치기 때문입니다.
하지만 주의할 점도 있습니다. 복제셋 노드들은 서로 네트워크 통신이 가능해야 합니다.
방화벽 설정이나 네트워크 분리가 잘못되면 복제셋이 제대로 동작하지 않습니다. 또한 각 노드는 충분한 저장 공간을 확보해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 바로 복제셋 구성에 착수했습니다.
이제 새벽에 전화받을 일은 없어질 것입니다.
실전 팁
💡 - 복제셋은 최소 3개 노드로 구성하되, 홀수 개를 유지하세요
- 각 노드는 서로 다른 물리적 서버나 가용 영역에 배치하는 것이 좋습니다
2. Primary와 Secondary 역할
복제셋을 구성한 김개발 씨는 새로운 의문이 생겼습니다. "세 개의 서버가 있는데, 데이터를 어디에 써야 하죠?
세 곳에 다 써야 하나요?" 박시니어 씨가 웃으며 말했습니다. "그건 Primary와 Secondary의 역할을 알면 이해할 수 있어요."
복제셋에서 Primary는 모든 쓰기 작업을 담당하는 유일한 노드입니다. Secondary는 Primary의 데이터를 복제하여 동일한 상태를 유지합니다.
마치 회사에서 원본 문서는 본사에서만 작성하고, 지사들은 그 복사본을 보관하는 것과 같습니다.
다음 코드를 살펴봅시다.
// Primary에서 데이터 쓰기 (자동으로 Primary로 라우팅)
const db = client.db("myapp");
const users = db.collection("users");
// 모든 쓰기 작업은 Primary에서 수행됨
await users.insertOne({
name: "김개발",
email: "kim@example.com",
createdAt: new Date()
});
// 현재 복제셋 상태 확인
const admin = client.db("admin");
const status = await admin.command({ replSetGetStatus: 1 });
// 각 멤버의 역할 출력
status.members.forEach(member => {
console.log(`${member.name}: ${member.stateStr}`);
});
김개발 씨는 복제셋의 기본 구조를 이해했지만, 실제로 데이터가 어떻게 흘러가는지는 아직 명확하지 않았습니다. 세 개의 서버가 있는데, 사용자가 회원가입을 하면 어떤 서버에 데이터가 저장되는 걸까요?
박시니어 씨가 화이트보드에 그림을 그리며 설명을 시작했습니다. "복제셋에서 서버들은 모두 평등하지 않아요.
한 명의 대장과 여러 명의 부하가 있는 거죠." Primary는 복제셋의 대장입니다. 모든 쓰기 작업, 즉 데이터의 삽입, 수정, 삭제는 오직 Primary에서만 수행됩니다.
애플리케이션이 MongoDB에 데이터를 저장하면, 그 요청은 자동으로 Primary로 전달됩니다. 그렇다면 Secondary는 무엇을 할까요?
Secondary는 Primary의 **oplog(operation log)**를 지속적으로 읽어와 동일한 작업을 자신의 데이터에 적용합니다. 마치 회의록을 받아 똑같이 따라하는 것과 같습니다.
이렇게 해서 Secondary는 Primary와 동일한 데이터 상태를 유지합니다. 이 구조가 왜 중요할까요?
만약 모든 서버가 쓰기를 받는다면, 데이터 충돌이 발생할 수 있습니다. A 서버에서는 이름을 "김철수"로 바꾸고, B 서버에서는 "김영희"로 바꾸면 어떤 것이 맞는 걸까요?
단일 Primary 구조는 이런 충돌을 원천적으로 방지합니다. 위의 코드에서 insertOne 메서드를 호출하면, MongoDB 드라이버는 자동으로 Primary를 찾아 해당 요청을 전달합니다.
개발자가 직접 Primary를 지정할 필요가 없습니다. 드라이버가 알아서 처리해주는 것입니다.
replSetGetStatus 명령은 복제셋의 현재 상태를 보여줍니다. 각 멤버의 stateStr 필드를 확인하면 해당 노드가 PRIMARY인지 SECONDARY인지 알 수 있습니다.
운영 중에 이 명령으로 복제셋의 건강 상태를 모니터링할 수 있습니다. 실제 운영 환경에서 Secondary는 두 가지 중요한 역할을 합니다.
첫째, 읽기 작업을 분산하여 Primary의 부하를 줄일 수 있습니다. 둘째, Primary가 장애를 겪을 때 새로운 Primary로 승격될 준비를 합니다.
주의할 점이 있습니다. Secondary에서 읽기를 수행하면 약간의 복제 지연이 있을 수 있습니다.
Primary에서 방금 쓴 데이터가 Secondary에 아직 반영되지 않았을 수 있다는 뜻입니다. 실시간성이 중요한 작업은 Primary에서 읽어야 합니다.
김개발 씨는 이제 데이터의 흐름을 이해했습니다. "아, 그래서 연결 문자열에 여러 서버를 적어도 쓰기는 한 곳에서만 되는 거군요!"
실전 팁
💡 - replSetGetStatus 명령으로 정기적으로 복제셋 상태를 점검하세요
- Secondary 노드에 priority: 0을 설정하면 해당 노드는 절대 Primary가 되지 않습니다
3. 복제셋 초기화
이론을 충분히 배운 김개발 씨는 이제 직접 복제셋을 구성해보기로 했습니다. "그런데 세 개의 MongoDB 서버를 어떻게 하나의 복제셋으로 묶죠?" 박시니어 씨가 답했습니다.
"rs.initiate() 명령 하나면 됩니다."
복제셋 초기화는 독립적인 MongoDB 인스턴스들을 하나의 복제셋으로 묶는 과정입니다. rs.initiate() 명령에 설정 객체를 전달하면 MongoDB가 자동으로 노드 간 통신을 설정하고, Primary를 선출합니다.
마치 여러 사람을 한 팀으로 편성하고 팀장을 뽑는 것과 같습니다.
다음 코드를 살펴봅시다.
// mongosh에서 복제셋 초기화
rs.initiate({
_id: "myReplicaSet",
members: [
{ _id: 0, host: "mongo1.example.com:27017" },
{ _id: 1, host: "mongo2.example.com:27017" },
{ _id: 2, host: "mongo3.example.com:27017" }
]
});
// 초기화 결과 확인
rs.status();
// 새로운 멤버 추가
rs.add("mongo4.example.com:27017");
// 멤버 제거
rs.remove("mongo4.example.com:27017");
// 복제셋 설정 변경
rs.reconfig(newConfig);
김개발 씨는 테스트 환경에 세 개의 MongoDB 인스턴스를 준비했습니다. 각 인스턴스는 서로 다른 포트에서 실행 중입니다.
이제 이 세 개의 독립적인 서버를 하나의 복제셋으로 묶어야 합니다. 먼저 각 MongoDB 인스턴스를 시작할 때 --replSet 옵션을 지정해야 합니다.
이 옵션은 "나는 이 복제셋의 일원이 될 것이다"라고 선언하는 것과 같습니다. 모든 인스턴스에 동일한 복제셋 이름을 지정해야 합니다.
인스턴스들이 준비되면, 그중 하나에 접속하여 rs.initiate() 명령을 실행합니다. 이 명령은 복제셋의 청사진을 전달받아 실제 복제셋을 구성합니다.
설정 객체의 _id 필드는 복제셋의 이름입니다. 이 이름은 --replSet 옵션에서 지정한 이름과 일치해야 합니다.
members 배열에는 복제셋에 참여할 모든 노드의 정보가 담깁니다. rs.initiate()를 실행하면 MongoDB는 내부적으로 여러 작업을 수행합니다.
먼저 각 노드 간의 네트워크 연결을 확인합니다. 그 다음 **선거(election)**를 통해 Primary를 선출합니다.
priority 값이 가장 높고, 가장 최신 데이터를 가진 노드가 Primary가 됩니다. rs.status() 명령은 초기화가 성공했는지 확인하는 데 사용됩니다.
각 멤버의 상태, 마지막 heartbeat 시간, 복제 지연 등을 확인할 수 있습니다. 모든 멤버가 PRIMARY 또는 SECONDARY 상태이면 초기화가 성공한 것입니다.
운영 중에 노드를 추가하거나 제거해야 할 수도 있습니다. rs.add() 명령으로 새 멤버를 추가할 수 있습니다.
새 멤버는 처음에 STARTUP2 상태로 시작하여, Primary로부터 데이터를 복제한 후 SECONDARY가 됩니다. 반대로 rs.remove() 명령으로 멤버를 제거할 수 있습니다.
주의할 점은 현재 Primary를 직접 제거할 수 없다는 것입니다. Primary를 제거하려면 먼저 해당 노드를 강등시키거나, 다른 노드가 Primary로 선출되기를 기다려야 합니다.
rs.reconfig() 명령은 복제셋 설정 전체를 변경할 때 사용합니다. priority 값 변경, 투표권 조정 등 다양한 설정을 수정할 수 있습니다.
김개발 씨는 테스트 환경에서 rs.initiate()를 실행했습니다. 잠시 후 rs.status()를 확인하니 세 노드가 모두 정상적으로 연결되어 있었습니다.
"생각보다 간단하네요!" 주의할 점이 있습니다. 프로덕션 환경에서는 네트워크 설정을 꼼꼼히 확인해야 합니다.
각 노드가 서로의 호스트명을 정확히 해석할 수 있어야 합니다. DNS 설정이나 /etc/hosts 파일을 미리 점검하세요.
실전 팁
💡 - 초기화 전에 각 노드가 서로 ping 가능한지 확인하세요
- 프로덕션에서는 rs.initiate() 실행 전에 설정 파일을 별도로 백업해두세요
4. 자동 장애 복구
복제셋이 잘 작동하던 어느 날, 김개발 씨가 관리하는 서버 중 하나가 갑자기 다운되었습니다. 하지만 이번에는 새벽에 전화가 오지 않았습니다.
아침에 출근해서 확인해보니 서비스는 정상적으로 운영되고 있었습니다. 이것이 바로 **자동 장애 복구(Failover)**의 힘입니다.
Failover는 Primary 노드가 장애를 겪었을 때 Secondary 중 하나가 자동으로 새 Primary로 승격되는 과정입니다. 마치 팀장이 갑자기 병가를 내면 부팀장이 업무를 대신하는 것과 같습니다.
이 과정은 보통 10-12초 내에 완료되며, 애플리케이션은 짧은 지연 후 정상 동작을 재개합니다.
다음 코드를 살펴봅시다.
// Node.js 드라이버의 자동 재연결 설정
const client = new MongoClient(uri, {
// 서버 선택 타임아웃 (밀리초)
serverSelectionTimeoutMS: 5000,
// heartbeat 간격
heartbeatFrequencyMS: 10000,
// 재시도 쓰기 활성화
retryWrites: true,
// 재시도 읽기 활성화
retryReads: true
});
// 장애 발생 시 재시도 로직
async function insertWithRetry(doc, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await collection.insertOne(doc);
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(r => setTimeout(r, 1000 * (i + 1)));
}
}
}
김개발 씨의 회사에서 운영 중인 서비스는 하루 수만 건의 주문을 처리합니다. 만약 데이터베이스가 1분만 멈춰도 수백 건의 주문이 실패할 수 있습니다.
이런 상황에서 복제셋의 자동 장애 복구 기능이 빛을 발합니다. 복제셋의 각 노드는 서로 heartbeat를 주고받습니다.
기본적으로 2초마다 "나 살아있어?"라는 신호를 교환합니다. 만약 10초 동안 Primary로부터 응답이 없으면, Secondary들은 "Primary가 죽었구나"라고 판단합니다.
이때 **선거(election)**가 시작됩니다. 선거 과정은 민주주의와 비슷합니다.
각 Secondary는 자신의 데이터가 얼마나 최신인지, priority 값이 얼마인지 등을 기준으로 투표합니다. 과반수의 투표를 얻은 노드가 새로운 Primary가 됩니다.
이 과정은 보통 10-12초 내에 완료됩니다. 여기서 중요한 개념이 **과반수(majority)**입니다.
3개 노드로 구성된 복제셋에서는 2개 이상의 노드가 살아있어야 Primary를 선출할 수 있습니다. 만약 2개 노드가 동시에 다운되면, 남은 1개 노드는 Primary가 될 수 없습니다.
과반수를 형성할 수 없기 때문입니다. 위의 코드에서 retryWrites와 retryReads 옵션이 중요합니다.
이 옵션이 활성화되면, Failover 중에 발생한 실패 요청을 드라이버가 자동으로 재시도합니다. 애플리케이션 코드에서 별도의 재시도 로직을 구현하지 않아도 됩니다.
serverSelectionTimeoutMS는 새로운 Primary가 선출될 때까지 대기하는 최대 시간입니다. 이 시간 내에 Primary가 선출되지 않으면 에러가 발생합니다.
너무 짧게 설정하면 정상적인 Failover 중에도 에러가 발생할 수 있으니 적절한 값을 설정해야 합니다. 추가적인 안전장치로 insertWithRetry 같은 함수를 구현할 수 있습니다.
첫 번째 시도가 실패하면 1초 후 재시도, 두 번째도 실패하면 2초 후 재시도하는 식입니다. 이런 지수 백오프(exponential backoff) 전략은 장애 복구 중 시스템에 과부하를 주지 않으면서 안정적으로 복구할 수 있게 해줍니다.
실제 운영 환경에서는 Failover 이벤트를 모니터링하는 것이 중요합니다. 너무 자주 Failover가 발생한다면 네트워크 불안정이나 하드웨어 문제를 의심해봐야 합니다.
김개발 씨는 서버 장애 알림을 받고도 여유롭게 커피를 마실 수 있게 되었습니다. 복제셋이 알아서 처리해주기 때문입니다.
실전 팁
💡 - retryWrites와 retryReads 옵션을 항상 활성화하세요
- Failover 테스트를 정기적으로 수행하여 실제 장애 상황에 대비하세요
5. Read Preference 설정
서비스 사용자가 늘어나면서 Primary 서버의 CPU 사용률이 80%를 넘기 시작했습니다. 김개발 씨가 걱정하며 박시니어 씨에게 물었습니다.
"Secondary가 놀고 있는 것 같은데, 읽기 요청을 분산할 수 없을까요?" 박시니어 씨가 답했습니다. "Read Preference 설정을 알아볼 때가 됐네요."
Read Preference는 읽기 작업을 어느 노드에서 수행할지 결정하는 설정입니다. 기본값은 Primary에서만 읽지만, Secondary로 읽기를 분산하여 Primary의 부하를 줄일 수 있습니다.
마치 도서관에서 인기 있는 책은 여러 권 비치하여 여러 사서가 동시에 대출 서비스를 하는 것과 같습니다.
다음 코드를 살펴봅시다.
const { MongoClient, ReadPreference } = require("mongodb");
// 연결 시 기본 Read Preference 설정
const client = new MongoClient(uri, {
readPreference: ReadPreference.SECONDARY_PREFERRED
});
// 컬렉션 레벨에서 Read Preference 설정
const users = db.collection("users", {
readPreference: ReadPreference.SECONDARY
});
// 개별 쿼리에서 Read Preference 설정
const result = await db.collection("products")
.find({ category: "electronics" })
.readPreference(ReadPreference.NEAREST)
.toArray();
// 최대 허용 복제 지연 설정 (초 단위)
const freshData = await db.collection("orders")
.find({})
.readPreference(ReadPreference.SECONDARY, { maxStalenessSeconds: 90 })
.toArray();
김개발 씨의 서비스는 하루에 100만 건 이상의 읽기 요청을 처리합니다. 그중 대부분은 상품 목록 조회, 게시글 조회 같은 단순 읽기입니다.
이 모든 요청이 Primary 한 대로 집중되니 서버가 버거워하는 것은 당연합니다. Read Preference에는 다섯 가지 모드가 있습니다.
primary는 기본값입니다. 모든 읽기를 Primary에서 수행합니다.
가장 최신 데이터를 보장하지만, Primary에 부하가 집중됩니다. primaryPreferred는 Primary를 우선하되, Primary를 사용할 수 없으면 Secondary에서 읽습니다.
Failover 상황에서 읽기 가용성을 유지할 수 있습니다. secondary는 모든 읽기를 Secondary에서 수행합니다.
Primary의 부하를 크게 줄일 수 있지만, 약간의 데이터 지연을 감수해야 합니다. secondaryPreferred는 Secondary를 우선하되, Secondary를 사용할 수 없으면 Primary에서 읽습니다.
읽기 분산과 가용성을 모두 확보할 수 있어 많이 사용됩니다. nearest는 네트워크 지연이 가장 적은 노드에서 읽습니다.
지리적으로 분산된 복제셋에서 유용합니다. 위의 코드에서 세 가지 레벨의 설정 방법을 보여줍니다.
연결 레벨에서 설정하면 모든 읽기에 적용됩니다. 컬렉션 레벨에서 설정하면 해당 컬렉션의 읽기에만 적용됩니다.
개별 쿼리에서 설정하면 그 쿼리에만 적용됩니다. maxStalenessSeconds 옵션은 중요한 안전장치입니다.
이 값은 "최대 이 정도의 데이터 지연은 허용한다"는 뜻입니다. 90초로 설정하면, 90초 이상 뒤처진 Secondary에서는 읽지 않습니다.
너무 오래된 데이터를 읽는 것을 방지합니다. 실제 운영에서는 데이터의 특성에 따라 Read Preference를 다르게 설정합니다.
주문 내역처럼 실시간성이 중요한 데이터는 Primary에서 읽습니다. 상품 카탈로그처럼 약간의 지연이 허용되는 데이터는 Secondary에서 읽습니다.
주의할 점이 있습니다. "방금 저장한 데이터를 바로 조회"하는 패턴에서는 Secondary 읽기가 문제를 일으킬 수 있습니다.
데이터가 아직 Secondary에 복제되지 않았을 수 있기 때문입니다. 이런 경우에는 Primary에서 읽거나, 적절한 지연 후 조회해야 합니다.
김개발 씨는 상품 조회 API에 secondaryPreferred를 적용했습니다. Primary의 CPU 사용률이 80%에서 50%로 떨어졌습니다.
"이렇게 간단한 설정으로 이렇게 큰 효과가 있다니!"
실전 팁
💡 - 실시간성이 중요한 데이터는 primary, 조회가 많은 데이터는 secondaryPreferred를 사용하세요
- maxStalenessSeconds는 최소 90초 이상으로 설정해야 합니다 (MongoDB 제한)
6. 복제 지연 모니터링
어느 날 고객센터에서 이상한 민원이 들어왔습니다. "방금 주문했는데 주문 내역에 안 보여요." 김개발 씨가 확인해보니 데이터베이스에는 정상적으로 저장되어 있었습니다.
박시니어 씨가 말했습니다. "복제 지연이 발생한 것 같네요.
모니터링을 설정해야겠어요."
**복제 지연(Replication Lag)**은 Primary의 데이터가 Secondary에 복제되기까지 걸리는 시간입니다. 정상적인 상황에서는 1초 미만이지만, 네트워크 문제나 부하 증가로 지연이 커질 수 있습니다.
이 지연을 모니터링하면 문제를 조기에 발견하고 대응할 수 있습니다.
다음 코드를 살펴봅시다.
// rs.status()로 복제 지연 확인
const status = await adminDb.command({ replSetGetStatus: 1 });
status.members.forEach(member => {
if (member.stateStr === "SECONDARY") {
// Primary의 optime과 비교하여 지연 계산
const lagSeconds = member.optimeDate
? (new Date() - member.optimeDate) / 1000
: null;
console.log(`${member.name}: ${lagSeconds}초 지연`);
}
});
// rs.printSecondaryReplicationInfo() 동등 명령
const replInfo = await adminDb.command({
replSetGetStatus: 1,
initialSync: 1
});
// 복제 지연 알림 설정 (예: 10초 초과 시)
function checkReplicationLag(maxLagSeconds = 10) {
if (lagSeconds > maxLagSeconds) {
sendAlert(`복제 지연 경고: ${member.name}이 ${lagSeconds}초 지연`);
}
}
김개발 씨가 겪은 문제의 원인은 복제 지연이었습니다. 고객이 주문을 완료한 직후 주문 내역 페이지로 이동했는데, 해당 페이지가 Secondary에서 데이터를 읽고 있었던 것입니다.
주문 데이터가 아직 Secondary에 복제되지 않아 "주문 없음"으로 표시된 것입니다. 복제 지연은 왜 발생할까요?
가장 흔한 원인은 네트워크 지연입니다. Primary와 Secondary 사이의 네트워크가 불안정하면 oplog 전송이 지연됩니다.
특히 지리적으로 먼 데이터센터에 Secondary를 배치한 경우 이런 문제가 자주 발생합니다. 두 번째 원인은 Secondary의 처리 능력 부족입니다.
Secondary가 oplog를 적용하는 속도보다 Primary에서 쓰기가 빠르면 지연이 누적됩니다. 디스크 I/O가 느린 서버에서 자주 발생합니다.
세 번째 원인은 인덱스 빌드입니다. 대용량 컬렉션에 인덱스를 생성하면 Secondary에서도 동일한 인덱스를 빌드해야 합니다.
이 과정에서 다른 복제 작업이 지연될 수 있습니다. 위의 코드에서 replSetGetStatus 명령은 각 멤버의 상세 정보를 반환합니다.
optimeDate 필드는 해당 노드가 마지막으로 적용한 oplog의 타임스탬프입니다. 현재 시간과 비교하면 복제 지연을 계산할 수 있습니다.
실제 운영에서는 이 값을 주기적으로 수집하여 모니터링 시스템에 전송합니다. Grafana, Datadog 같은 도구를 사용하면 복제 지연의 추이를 그래프로 시각화할 수 있습니다.
알림 임계값을 설정하는 것도 중요합니다. 일반적으로 복제 지연이 10초를 넘으면 주의가 필요합니다.
60초를 넘으면 심각한 문제로 간주해야 합니다. Read Preference로 Secondary 읽기를 사용 중이라면 더욱 철저한 모니터링이 필요합니다.
복제 지연이 지속되면 어떤 문제가 생길까요? 첫째, Secondary에서 읽은 데이터가 최신이 아닐 수 있습니다.
둘째, Failover 시 데이터 손실이 발생할 수 있습니다. Primary에만 있고 Secondary에 복제되지 않은 데이터는 Primary 장애 시 유실됩니다.
김개발 씨는 복제 지연 모니터링 대시보드를 구축했습니다. 지연이 5초를 넘으면 슬랙 알림이 오도록 설정했습니다.
"이제 문제가 생기기 전에 미리 알 수 있겠네요!" 문제 해결 방법도 알아두어야 합니다. 네트워크 문제라면 네트워크 대역폭을 늘리거나 Secondary를 더 가까운 위치로 이동합니다.
처리 능력 부족이라면 Secondary의 하드웨어를 업그레이드합니다. 인덱스 빌드가 원인이라면 트래픽이 적은 시간에 인덱스를 생성합니다.
실전 팁
💡 - 복제 지연 모니터링을 운영 대시보드의 핵심 지표로 포함하세요
- 지연이 지속되면 네트워크, 디스크 I/O, 쓰기 부하를 순서대로 점검하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
모델 Failover 및 프로바이더 관리 완벽 가이드
AI 서비스 개발 시 필수적인 모델 장애 대응과 다중 프로바이더 관리 전략을 다룹니다. Anthropic과 OpenAI를 통합하고, 안정적인 failover 시스템을 구축하는 방법을 실무 코드와 함께 설명합니다.
Cron과 Webhooks 완벽 가이드
Node.js 환경에서 자동화의 핵심인 Cron 작업과 Webhooks를 활용하는 방법을 다룹니다. 정기적인 작업 스케줄링부터 외부 서비스 연동까지, 실무에서 바로 적용할 수 있는 자동화 기법을 배워봅니다.
보안 모델 및 DM Pairing 완벽 가이드
Discord 봇의 DM 보안 정책과 페어링 시스템을 체계적으로 학습합니다. dmPolicy 설정부터 allowlist 관리, 페어링 코드 구현까지 안전한 봇 운영의 모든 것을 다룹니다.
Media Pipeline 완벽 가이드
실무에서 자주 사용하는 미디어 파일 처리 파이프라인을 처음부터 끝까지 배웁니다. 이미지 리사이징, 오디오 변환, 임시 파일 관리까지 Node.js로 구현하는 방법을 초급 개발자도 이해할 수 있도록 쉽게 설명합니다.
Slack 통합 완벽 가이드 Bolt로 시작하는 기업용 메신저 봇 개발
Slack Bolt 프레임워크를 활용하여 기업용 메신저 봇을 개발하는 방법을 초급자도 이해할 수 있도록 단계별로 설명합니다. 이벤트 구독, 모달 인터랙션, 실전 배포까지 실무 활용 사례와 함께 다룹니다.