
3단계: Change Stream · CDC · Resume Token 이해하기
이번 3단계는 MongoDB와 Kafka가 실제로 연결되는 핵심 구간입니다.
2단계에서 우리는 여기까지 봤습니다.
Application
↓
MongoDB Primary
↓
oplog
↓
Change Stream
↓
Source Connector
↓
Kafka
이번에는 이 흐름을 더 자세히 보겠습니다.
핵심은 3가지입니다.
CDC = 변경사항을 계속 따라가는 방식
Change Stream = MongoDB 변경 이벤트를 외부로 전달하는 인터페이스
Resume Token = 마지막으로 어디까지 읽었는지 기억하는 위치 정보
3.1 CDC란 무엇인가?
CDC는 Change Data Capture의 약자입니다.
쉽게 말하면:
📡 DB 전체를 계속 복사하는 것이 아니라,
“변경된 데이터만 찾아서 전달하는 방식”
입니다.
예를 들어 MongoDB에 10TB 데이터가 있다고 하겠습니다.
매번 전체 10TB를 DR로 복사하면 비효율적입니다.
MongoDB 10TB
│
├── 매번 10TB 전체 복사 ❌
│
└── 변경된 데이터만 전송 ✅
예:
10:00 INSERT user 100
10:01 UPDATE user 101
10:03 DELETE user 102
이 변경분만 계속 전달하면 됩니다.
이게 CDC입니다.
3.2 Full Copy와 CDC의 차이
두 개념을 비교하면 이해가 쉽습니다.
| 처리 대상 | 전체 데이터 | 변경된 데이터 |
| 데이터량 | 큼 | 상대적으로 작음 |
| 실행 방식 | 일괄 | 지속적 |
| 실시간성 | 낮음 | 높음 |
| DR 활용 | 초기 적재 | 이후 지속 동기화 |
즉 실제 DR에서는 보통:
① Initial Load
전체 데이터 복제
↓
② CDC 시작
이후 변경분 지속 반영
이렇게 조합합니다.
3.3 MongoDB Change Stream은 무엇인가?
MongoDB에서 CDC를 구현할 때 핵심 기능이 Change Stream입니다.
Change Stream은 MongoDB에서 발생한 변경 이벤트를 외부 프로그램이 구독할 수 있게 해줍니다.
예:
MongoDB
INSERT
UPDATE
DELETE
REPLACE
↓
Change Stream
↓
Kafka Source Connector
즉 Change Stream은:
“MongoDB에서 무슨 변경이 발생했는지 계속 알려주는 통로”
라고 이해하면 됩니다.
3.4 실제 변경 이벤트 예시
사용자 데이터가 있다고 하겠습니다.
{
"_id": 100,
"name": "Kim",
"status": "ACTIVE"
}
상태를 변경합니다.
db.users.updateOne(
{ _id: 100 },
{ $set: { status: "INACTIVE" } }
)
그러면 Change Stream에서는 개념적으로 다음과 같은 이벤트가 발생합니다.
{
"operationType": "update",
"documentKey": {
"_id": 100
},
"updateDescription": {
"updatedFields": {
"status": "INACTIVE"
}
}
}
즉:
MongoDB Document 변경
↓
Change Event 발생
↓
Connector가 소비
↓
Kafka Topic으로 전달
입니다.
3.5 INSERT는 어떻게 보일까?
예를 들어:
db.users.insertOne({
"_id": 101,
"name": "Lee",
"status": "ACTIVE"
})
Change Stream 이벤트는 개념적으로:
{
"operationType": "insert",
"documentKey": {
"_id": 101
},
"fullDocument": {
"_id": 101,
"name": "Lee",
"status": "ACTIVE"
}
}
입니다.
즉 Source Connector는 이 이벤트를 Kafka Record로 바꿔서 전달합니다.
3.6 DELETE는 어떻게 처리될까?
삭제도 마찬가지입니다.
db.users.deleteOne({
"_id": 101
})
Change Stream 이벤트:
{
"operationType": "delete",
"documentKey": {
"_id": 101
}
}
그리고 Kafka를 거쳐 DR MongoDB에서도 같은 문서 삭제가 수행되도록 Sink Connector를 설계할 수 있습니다.
즉 DR에서 중요한 것은 단순 INSERT만이 아닙니다.
INSERT
UPDATE
DELETE
REPLACE
를 모두 일관되게 처리해야 합니다.
3.7 oplog와 Change Stream 관계
여기서 다시 oplog를 보겠습니다.
MongoDB Primary
│
├── 실제 데이터
│
└── oplog
│
↓
Change Stream
│
↓
Source Connector
쉽게 구분하면:
| oplog | MongoDB 내부 replication 변경 기록 |
| Change Stream | 변경 이벤트를 외부에 제공 |
| Source Connector | 이벤트를 Kafka로 전달 |
즉 Source Connector는 MongoDB 내부 파일을 직접 열어 읽는 것이 아니라, Change Stream을 통해 이벤트를 받는 구조로 이해하면 됩니다.
3.8 여기서 가장 중요한 Resume Token 등장 🔥
Connector가 계속 Change Stream을 읽고 있다고 하겠습니다.
Event 100
Event 101
Event 102
Event 103
Event 104
그런데 Connector가 Event 103까지 처리한 상태에서 장애가 났습니다.
Event 100 ✅
Event 101 ✅
Event 102 ✅
Event 103 ✅
Connector 장애 💥
Event 104
Event 105
Event 106
복구했을 때 중요한 질문이 생깁니다.
“어디서부터 다시 읽어야 하지?”
여기서 사용하는 것이 Resume Token입니다.
3.9 Resume Token은 책갈피라고 생각하면 된다
책을 500페이지까지 읽었다고 생각해보겠습니다.
책갈피가 없다면:
책 처음부터 다시 읽기 😭
해야 할 수도 있습니다.
하지만 책갈피가 있으면:
500페이지 위치 기억
↓
501페이지부터 계속
읽을 수 있습니다.
Resume Token도 같은 개념입니다.
Change Stream
Event 100
Event 101
Event 102
Event 103 ← Resume Token 위치
Event 104
Event 105
Connector가 재시작하면:
Resume Token 확인
↓
마지막 위치부터 이어서 처리
합니다.
3.10 Resume Token은 Kafka Offset과 같은가?
아닙니다. ❌
이 부분은 매우 중요합니다.
MongoDB와 Kafka 모두 “위치” 개념이 있어서 헷갈리기 쉽습니다.
MongoDB
↓
Resume Token
Kafka
↓
Offset
둘은 서로 다른 영역입니다.
| 시스템 | MongoDB | Kafka |
| 의미 | Change Stream 위치 | Partition 내 Record 위치 |
| 사용 주체 | Source Connector | Kafka Consumer |
| 목적 | MongoDB 변경 이어읽기 | Kafka 메시지 이어읽기 |
즉:
MongoDB
Event A
Event B
Event C
↑
Resume Token
Kafka Partition
Offset 100
Offset 101
Offset 102
↑
Kafka Offset
입니다.
3.11 전체 파이프라인에는 위치 정보가 여러 개 있다
우리 DR 구조에서는 이 점이 매우 중요합니다.
MongoDB PROD
│
│ Resume Token
▼
Source Connector
│
▼
Kafka PROD
│
│ Kafka Offset
▼
MirrorMaker 2
│
▼
Kafka DR
│
│ Kafka Offset
▼
Sink Connector
│
▼
MongoDB DR
즉 장애 복구 시 하나만 보면 안 됩니다.
각 구간별 처리 위치를 봐야 합니다.
3.12 장애 시 실제로 어떻게 복구될까?
예를 들어 Source Connector가 다음 상태였다고 하겠습니다.
10:00 Event 100 처리
10:01 Event 101 처리
10:02 Event 102 처리
10:03 Event 103 처리
10:04 Connector 장애 💥
그동안 MongoDB에서는 계속 데이터가 변경됩니다.
10:05 Event 104
10:06 Event 105
10:07 Event 106
10:10에 Connector가 복구됩니다.
Connector 재시작
↓
Resume Token 확인
↓
Event 104부터 재개
↓
Kafka로 전송
즉 정상적이면 장애 시간 동안의 데이터도 따라잡을 수 있습니다.
3.13 그런데 중요한 조건이 있다: oplog가 남아 있어야 한다
여기서 실무적으로 아주 중요한 문제가 나옵니다.
MongoDB oplog는 무한히 저장되지 않습니다.
예를 들어 oplog가 최근 6시간 분량만 유지된다고 가정해보겠습니다.
현재 시간 18:00
oplog 보유 범위
12:00 ───────────────── 18:00
그런데 Connector가 10시간 장애났습니다.
Connector 마지막 처리
08:00
복구 시점:
18:00
문제는:
08:00 이벤트
│
└── 이미 oplog에서 사라짐 ❌
입니다.
이 경우 Resume Token이 있어도 해당 위치가 이미 유효하지 않을 수 있습니다.
3.14 그래서 oplog 크기 산정이 중요하다
실무에서는 oplog를 단순히 기본값으로 두기보다 최대 장애 허용시간을 고려해야 합니다.
예를 들어 목표를:
WAN 장애가 최대 12시간 발생해도 복구 가능
으로 잡았다고 하겠습니다.
그러면 최소한:
12시간 이상의 변경 이벤트
+
안전 여유
를 담을 수 있도록 oplog를 검토해야 합니다.
예:
최대 예상 장애 : 12시간
안전 계수 : ×2
권장 목표
≈ 24시간 이상 보관 가능
처럼 생각할 수 있습니다.
단, 실제 oplog 보존 시간은 쓰기 TPS와 변경 데이터량에 따라 달라집니다.
3.15 oplog는 “시간”이 아니라 사실 용량 기반이다
예를 들어 oplog가 20GB라고 하겠습니다.
쓰기량이 적으면:
20GB
→ 3일치 기록
이 될 수도 있습니다.
하지만 쓰기량이 많으면:
20GB
→ 2시간치 기록
밖에 안 될 수도 있습니다.
따라서 이렇게 봐야 합니다.
Oplog Retention
=
Oplog 크기
÷
시간당 변경 데이터 발생량
예를 들어:
Oplog 크기 100GB
시간당 변경량 5GB
100 ÷ 5
= 약 20시간
입니다.
3.16 예제로 계산해보자
운영 MongoDB의 변경량이:
하루 변경량 = 240GB
라면 시간당 평균:
240GB ÷ 24시간
= 10GB/hour
입니다.
최대 24시간 장애를 버티고 싶다면 최소:
10GB × 24시간
= 240GB
정도가 필요합니다.
여기에 피크와 안전 여유를 고려해:
240GB × 1.5
= 360GB
또는
240GB × 2
= 480GB
같은 식으로 검토합니다.
이것은 단순 예제이고 실제 환경에서는 쓰기 패턴과 압축/기록 특성을 측정해야 합니다.
3.17 Source Connector 장애와 MM2 장애는 다르다
중요합니다.
우리 전체 구조는:
MongoDB
↓
Source Connector
↓
Kafka PROD
↓
MM2
↓
Kafka DR
↓
Sink Connector
↓
MongoDB DR
입니다.
Source Connector 장애:
MongoDB
↓
❌ Source Connector
이 경우 MongoDB Change Stream 재개와 Resume Token이 중요합니다.
반면 MM2 장애:
Kafka PROD
↓
❌ MM2
이 경우에는 MongoDB Resume Token보다 Kafka에 이미 저장된 메시지와 MM2 복제 위치가 중요합니다.
즉 장애 위치에 따라 복구 기준이 다릅니다.
3.18 Kafka가 중간 버퍼 역할을 한다
Kafka를 사용하는 큰 장점 중 하나가 바로 이것입니다.
예를 들어 DR센터가 일시적으로 다운됐습니다.
MongoDB PROD
↓
Source Connector
↓
Kafka PROD
↓
MM2 ❌
그래도 Kafka PROD에 메시지가 남아 있다면:
Offset 100
Offset 101
Offset 102
Offset 103
Offset 104
Offset 105
MM2 복구 후 밀린 데이터를 따라잡을 수 있습니다.
즉 Kafka는 중간 버퍼이자 재처리 가능한 이벤트 로그 역할을 합니다.
3.19 Kafka Retention도 같이 봐야 한다
MongoDB oplog만 크게 잡으면 끝나는 것이 아닙니다.
Kafka에도 Retention이 있습니다.
예:
Kafka Topic Retention = 7일
이면 일정 기간 메시지를 보관합니다.
우리 DR 구조에서는 최소한 다음 두 가지 보존 시간이 중요합니다.
MongoDB oplog retention
+
Kafka topic retention
왜냐하면 장애 위치에 따라 필요한 데이터 소스가 다르기 때문입니다.
3.20 장애 시나리오 ① Source Connector 장애
MongoDB
↓
Source Connector ❌
↓
Kafka
필요한 것:
Resume Token
+
MongoDB oplog retention
입니다.
3.21 장애 시나리오 ② MM2 장애
Kafka PROD
↓
MM2 ❌
↓
Kafka DR
필요한 것:
Kafka PROD retention
+
MM2 offset/checkpoint 상태
입니다.
3.22 장애 시나리오 ③ Sink Connector 장애
Kafka DR
↓
Sink Connector ❌
↓
MongoDB DR
필요한 것:
Kafka DR retention
+
Sink Connector consumer offset
입니다.
따라서 DR 설계는 반드시 구간별 장애 모델로 접근해야 합니다.
3.23 중복 데이터가 들어갈 수도 있을까?
가능성을 고려해야 합니다.
분산 시스템에서는 장애 복구 과정에서 메시지가 다시 처리될 수 있습니다.
예:
Kafka Record 100 처리
MongoDB DR Write 성공 ✅
그런데
Connector가 Commit 전에 장애 💥
재시작
Record 100 다시 처리
이런 상황이 발생할 수 있습니다.
그래서 DR 설계에서는:
At-least-once
Idempotency
Upsert
Document Key
중복 처리
를 반드시 검토해야 합니다.
3.24 Idempotent란 무엇인가?
초보자 관점에서는:
같은 데이터를 두 번 처리해도 최종 결과가 같도록 만드는 것
입니다.
예를 들어:
_id = 100
status = ACTIVE
라는 상태를 두 번 적용해도:
1회 적용 → ACTIVE
2회 적용 → ACTIVE
최종 결과가 같으면 안전합니다.
반면:
balance = balance + 1000
같은 작업을 두 번 실행하면:
+1000
+1000
= +2000
이 되어 문제가 생길 수 있습니다.
그래서 Sink Connector의 Write Model 설계가 중요합니다.
3.25 순서(Ordering) 문제도 중요하다
Kafka에서는 같은 Partition 안에서는 순서를 보장할 수 있지만, 여러 Partition을 사용하면 전체 전역 순서가 단순하지 않습니다.
예를 들어:
user 100
Event A
status = ACTIVE
Event B
status = BLOCKED
정상 순서는:
A → B
여야 합니다.
그런데 분산 처리에서 잘못된 설계를 하면:
B → A
로 적용될 위험을 고려해야 합니다.
그래서 보통 같은 Document Key가 같은 Partition으로 가도록 Key 설계를 신중하게 해야 합니다.
이 부분은 나중에 Partition Key 설계 단계에서 자세히 다루겠습니다.
3.26 우리 구조에서 보는 실제 장애 복구 흐름
전체 그림으로 보면:
━━━━━━━━━━━━ PROD ━━━━━━━━━━━━
MongoDB Replica Set
│
│ Change Stream
│ Resume Token
▼
Source Connector
│
▼
Kafka PROD
│
│ Kafka Offset
▼
MirrorMaker 2
│
│ WAN
▼
━━━━━━━━━━━━ DR ━━━━━━━━━━━━━
Kafka DR
│
│ Consumer Offset
▼
Sink Connector
│
▼
MongoDB DR
장애가 발생하면:
① 어디에서 장애가 났는가?
② 마지막 정상 처리 위치는 어디인가?
③ 원본 이벤트가 아직 남아 있는가?
④ 재처리가 가능한가?
⑤ 중복 처리되어도 안전한가?
⑥ DR 데이터가 PROD와 동일한가?
를 확인해야 합니다.
3.27 운영에서 모니터링해야 할 항목
이 구조에서는 다음 지표들을 반드시 모니터링하는 것이 좋습니다.
| MongoDB | oplog window |
| MongoDB | Replica lag |
| Source Connector | Connector status |
| Source Connector | Task status |
| Kafka PROD | Producer rate |
| Kafka PROD | Topic retention |
| MM2 | Replication lag |
| Kafka DR | Consumer lag |
| Sink Connector | Consumer offset |
| MongoDB DR | Write 성공/실패 |
| 전체 | End-to-End replication lag |
가장 중요한 최종 지표는:
PROD MongoDB에서 발생한 변경이 DR MongoDB에 반영되기까지 몇 초 걸리는가?
입니다.
3.28 RPO와 연결해보자
예를 들어 목표 RPO가:
RPO ≤ 5분
이라고 하겠습니다.
그렇다면 DR MongoDB가 PROD보다 최대 5분 이상 뒤처지면 안 됩니다.
따라서:
Source Connector Lag
+
Kafka Queue Lag
+
MM2 Lag
+
Sink Connector Lag
의 합을 감시해야 합니다.
예:
Source 5초
Kafka 2초
MM2 10초
Sink 8초
────────────────
총 지연 25초
이면:
RPO 5분 기준
→ 현재 정상
입니다.
3.29 그런데 데이터 정합성은 Lag만 봐서는 안 된다
Lag가 0초라고 해서 반드시 모든 데이터가 정확하다는 뜻은 아닙니다.
예를 들어:
INSERT 100개
UPDATE 50개
DELETE 10개
중 일부가 Sink 오류로 빠졌다면:
Lag = 0
이어도 데이터가 다를 수 있습니다.
따라서 정기적으로:
Document Count
Checksum
Sample Validation
Key 비교
Timestamp 비교
같은 데이터 정합성 검증도 필요합니다.
3.30 Initial Sync와 CDC 연결 시점이 중요한 이유
앞에서 Initial Load와 CDC를 같이 사용한다고 했습니다.
예:
10:00 Initial Load 시작
12:00 Initial Load 종료
그런데 10:00~12:00 사이에도 운영 MongoDB에서는 변경이 계속 발생합니다.
10:30 UPDATE
11:00 INSERT
11:40 DELETE
이 변경분을 놓치면 안 됩니다.
그래서 실제 구축 시 핵심은:
Initial Snapshot 기준 시점
+
CDC 시작 위치
를 정확하게 연결하는 것입니다.
이 부분은 나중에 8단계 Initial Sync + CDC 연결에서 집중적으로 다룰 예정입니다.
3.31 이번 단계에서 꼭 기억할 장애 공식
이렇게 기억하면 좋습니다.
MongoDB → Source Connector 장애
= Resume Token + oplog
Kafka PROD → MM2 장애
= Kafka Retention + MM2 복구
Kafka DR → Sink Connector 장애
= Kafka Retention + Consumer Offset
그리고 최종적으로:
전체 DR 안정성
=
각 단계의 위치정보
+
충분한 보존기간
+
재처리 가능 구조
입니다.
✅ 3단계 핵심 요약
이번 단계에서 가장 중요한 개념은 5개입니다.
| CDC | 변경 데이터만 지속적으로 복제 |
| Change Stream | MongoDB 변경 이벤트 제공 |
| oplog | MongoDB 내부 변경 기록 |
| Resume Token | Change Stream 이어읽기 위치 |
| Kafka Offset | Kafka Record 처리 위치 |
그리고 전체 흐름은:
MongoDB Write
↓
oplog
↓
Change Stream
↓
Resume Token
↓
Source Connector
↓
Kafka Offset
↓
Kafka PROD
↓
MirrorMaker 2
↓
Kafka DR
↓
Sink Connector
↓
MongoDB DR
입니다.
🚀 다음 4단계: Kafka 구조를 제대로 이해하기
이제 MongoDB 쪽 출발점은 잡았습니다.
다음 4단계에서는 Kafka의 핵심인:
Broker
Topic
Partition
Offset
Replication Factor
Producer
Consumer
Consumer Group
를 MongoDB DR 관점으로만 쉽게 설명하겠습니다.
특히 다음 질문에 답하는 단계입니다.
왜 Topic이 필요한가?
왜 Partition을 나누는가?
Offset은 왜 필요한가?
Broker 3대면 데이터는 어떻게 분산되는가?
Replication Factor=3은 무슨 뜻인가?
MM2는 실제로 무엇을 복제하는가?
Source/Sink Connector는 Producer인가 Consumer인가?
여기까지 이해하면 5단계부터 실제 MongoDB Source Connector → Kafka Topic 설정이 훨씬 쉬워집니다.
'J-H-T > MongoDB' 카테고리의 다른 글
| [MongoDB & Kafka - 3.2단계] oplog 크기 · oplog Window · 장애시간 기준 용량 산정!! (0) | 2026.08.27 |
|---|---|
| [MongoDB & Kafka - 3.1단계] MongoDB oplog는 실제로 어떻게 생겼을까? (1) | 2026.08.26 |
| [MongoDB & Kafka - 2단계] MongoDB Replica Set · Primary · Secondary · oplog 이해! (0) | 2026.08.25 |
| [MongoDB & Kafka - 1단계] 전체 DR 구조 이해! (0) | 2026.08.25 |
| Kafka를 통한 MongoDB 데이터 스트리밍 싱크 구성 가이드 !! (0) | 2026.08.24 |
댓글