(3/5) AWS DMS + CDC 로 MSSQL 무중단 컷오버 — 풀로드 후 변경분 따라잡기
📚 MSSQL → AWS RDS 마이그레이션 시리즈 (전체 5편)
- 방법 비교 — 6가지 중에서 고르기
- Native Backup/Restore 실전 — 옵션 그룹부터 컷오버까지
- DMS + CDC 무중단 컷오버 — 풀로드 후 변경분 따라잡기 ← 지금 글
- 변경분 계속 쌓기 — NORECOVERY 체인과 CDC-only 결합
- 정합성 트러블슈팅 — 케이스별 8가지 함정
지난 글에서 Native Backup/Restore 로 풀카피 → 컷오버 순서를 정리했어요. 그런데 운영 DB 라 단 몇 분의 다운타임도 허용 안 되는 케이스가 있죠. 이번 글은 그런 상황에서 쓰는 AWS DMS + CDC 구성법이에요. 원본을 운영 중 그대로 두고 풀로드 → 변경분 실시간 따라잡기 → 컷오버 순서로 다운타임을 분 단위 이하로 줄이는 흐름을 정리합니다.
💡 이 글에서 다루는 것
- 원본 MSSQL 에서 CDC 활성화 (DB 레벨 + 테이블 레벨)
- DMS Replication Instance / Endpoints / Task 세팅
- Full Load + CDC 동시 모드 vs 분리 모드 차이
- 스키마/인덱스/IDENTITY 는 DMS 가 안 가져온다는 점과 대응
- CloudWatch 로 latency 추적해서 안전하게 컷오버하는 절차
- 마이그레이션 끝난 뒤 CDC/DMS 정리(cleanup)
1. 큰 그림 — 무엇이 어디서 도는가
[내부망 MSSQL]
│ (1) CDC 로 변경분 캡처 (트랜잭션 로그 기반)
│
▼
[DMS Replication Instance (VPC)]
│ (2) Full Load + 캡처된 변경분을 타깃에 적용
│
▼
[AWS RDS for SQL Server]
DMS 는 Replication Instance 라는 EC2 같은 워커를 하나 띄워두고, 거기서 소스의 트랜잭션 로그 를 읽어와 타깃에 흘려줘요. 원본은 CDC 만 켜두면 되고, DMS 가 직접 원본을 폴링하면서 변경을 잡아갑니다.
💡 풀로드 + CDC 를 같은 task 에서 켜면 풀로드 진행 중에도 변경을 캐시해두고, 풀로드 끝나는 순간부터 따라잡기를 시작해요. “다운타임 거의 0” 의 핵심.
2. 원본 MSSQL 준비 — CDC 활성화
DMS 가 MSSQL 의 변경분을 잡아가려면 원본에서 CDC 가 켜져 있어야 해요. SQL Server 의 CDC 는 트랜잭션 로그를 읽어서 변경을 별도 시스템 테이블에 기록하는 기능이에요.
2-1. DB 레벨 CDC 켜기
USE [MyDB];
EXEC sys.sp_cdc_enable_db;
-- 확인
SELECT name, is_cdc_enabled
FROM sys.databases
WHERE name = 'MyDB';
is_cdc_enabled = 1 이면 OK.
2-2. 테이블 레벨 CDC 켜기
CDC 는 테이블 단위로 한 번 더 켜줘야 해요. 마이그레이션 대상 테이블 전부에 대해 돌립니다.
USE [MyDB];
EXEC sys.sp_cdc_enable_table
@source_schema = N'dbo',
@source_name = N'Orders',
@role_name = NULL, -- 별도 role 안 쓰면 NULL
@supports_net_changes = 1;
테이블 많으면 동적 SQL 로 한 번에 돌려요.
DECLARE @sql NVARCHAR(MAX) = N'';
SELECT @sql = @sql +
N'EXEC sys.sp_cdc_enable_table @source_schema=N''' + s.name
+ N''', @source_name=N''' + t.name
+ N''', @role_name=NULL, @supports_net_changes=1;' + CHAR(13)
FROM sys.tables t
JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE s.name = 'dbo';
EXEC sp_executesql @sql;
2-3. SQL Server Agent 가 떠 있어야 함
CDC 캡처 잡(cdc.MyDB_capture) 은 SQL Server Agent 가 돌려요. Agent 가 죽어있으면 변경이 안 잡혀요.
EXEC msdb.dbo.sp_help_job @job_name = N'cdc.MyDB_capture';
current_execution_status 가 1 (idle 아닌 실행 중) 또는 정상 스케줄로 도는지 확인.
⚠️ RDS for SQL Server 소스 였다면 CDC 활성화 절차가 조금 달라요(
rds_cdc_enable_db). 이 글은 온프레미스/EC2 소스 기준.
3. AWS 측 사전 세팅 — Replication Instance / Endpoints
3-1. Replication Subnet Group + Instance
aws dms create-replication-subnet-group \
--replication-subnet-group-identifier dms-subnet-grp \
--replication-subnet-group-description "DMS subnet group" \
--subnet-ids subnet-aaaa subnet-bbbb
aws dms create-replication-instance \
--replication-instance-identifier dms-mssql-mig \
--replication-instance-class dms.r5.large \
--allocated-storage 100 \
--vpc-security-group-ids sg-xxxxxxxx \
--replication-subnet-group-identifier dms-subnet-grp \
--no-publicly-accessible \
--multi-az
인스턴스 사이즈는 풀로드 데이터량/병렬도/CDC 부하 따라 골라요. 보통 dms.r5.large 부터 시작해서 latency 보면서 키워요.
3-2. Source / Target Endpoints
Source (내부망 MSSQL) — S2S VPN 으로 접근.
aws dms create-endpoint \
--endpoint-identifier mssql-source \
--endpoint-type source \
--engine-name sqlserver \
--server-name 10.x.x.x \
--port 1433 \
--database-name MyDB \
--username dms_user \
--password '<DMS_USER_PASSWORD>'
Target (AWS RDS for SQL Server)
aws dms create-endpoint \
--endpoint-identifier rds-target \
--endpoint-type target \
--engine-name sqlserver \
--server-name my-mssql-rds.xxxx.ap-northeast-2.rds.amazonaws.com \
--port 1433 \
--database-name MyDB \
--username admin \
--password '<RDS_ADMIN_PASSWORD>'
3-3. Connection 테스트
엔드포인트를 만들면 반드시 한 번 테스트.
aws dms test-connection \
--replication-instance-arn arn:aws:dms:ap-northeast-2:123456789012:rep:DMS-MSSQL-MIG \
--endpoint-arn arn:aws:dms:ap-northeast-2:123456789012:endpoint:MSSQL-SOURCE
✅ 결과가 successful 이어야 다음 단계로 갈 수 있어요. 실패 사유는 콘솔의 Last failure message 에 떨어져요.
3-4. 소스 계정 권한
dms_user 에는 최소 권한만. 흔히 깨지는 권한 세트는 다음 세 가지에요.
USE master;
GRANT VIEW SERVER STATE TO dms_user;
USE MyDB;
EXEC sp_addrolemember 'db_owner', 'dms_user';
-- 또는 SELECT + VIEW DATABASE STATE + db_datareader 조합
DMS 가 CDC 함수(fn_cdc_get_all_changes_*) 를 호출하기 때문에 단순 db_datareader 만으론 부족해요. db_owner 가 가장 깔끔하지만, 보안상 줄이고 싶다면 EXECUTE 권한을 CDC 함수에 별도 부여하는 방식이 있어요.
4. 스키마는 따로 — DMS 가 안 챙기는 것들
🚨 여기서 가장 많이 헷갈려요. DMS 는 데이터 위주 로 옮기는 서비스라, 다음을 직접 챙겨야 해요.
| 항목 | DMS 가 옮겨주나? |
|---|---|
| 테이블 구조 (CREATE TABLE) | △ (기본 타입 변환만, 추천 X) |
| 인덱스 (clustered/nonclustered) | ❌ |
| 외래키 제약 | ❌ (풀로드 중 자동 disable 권장) |
| 트리거 / 저장 프로시저 / 함수 | ❌ |
| 시퀀스 / IDENTITY 시드 | ❌ |
| 사용자/로그인/권한 | ❌ |
일반적인 대응 패턴
- 원본에서
SqlPackage /Action:Extract로 DACPAC 만들기 (스키마만, 데이터 X) - 타깃 RDS 에
SqlPackage /Action:Publish로 스키마 먼저 적용 - 그 다음 DMS 로 데이터만 채우기
- DMS 끝난 후 외래키 / 트리거 / 시퀀스 시드 보정
또는 SSMS 의 “Generate Scripts” 로 스키마 + 보조 객체 만 추출해도 OK.
💡 풀로드 중에는 타깃의 외래키와 인덱스를 잠시 끄거나 비활성화 하는 게 빨라요. Task 설정의
Target metadata에서BatchApplyEnabled+ 인덱스/제약 처리를 조정할 수 있어요.
5. Migration Task 만들기
이제 본 task. Full Load + CDC 모드로 만들고, table mapping 으로 옮길 테이블을 지정해요.
5-1. Table mappings (table-mappings.json)
{
"rules": [
{
"rule-type": "selection",
"rule-id": "1",
"rule-name": "select-dbo",
"object-locator": {
"schema-name": "dbo",
"table-name": "%"
},
"rule-action": "include"
}
]
}
dbo 스키마의 모든 테이블을 선택. 일부만 옮길 거면 table-name 을 구체적으로.
5-2. Task settings (task-settings.json)
{
"TargetMetadata": {
"BatchApplyEnabled": true,
"ParallelLoadThreads": 8,
"ParallelLoadBufferSize": 500
},
"FullLoadSettings": {
"TargetTablePrepMode": "TRUNCATE_BEFORE_LOAD",
"MaxFullLoadSubTasks": 8,
"CommitRate": 10000
},
"Logging": {
"EnableLogging": true,
"LogComponents": [
{ "Id": "SOURCE_CAPTURE", "Severity": "LOGGER_SEVERITY_DEFAULT" },
{ "Id": "SOURCE_UNLOAD", "Severity": "LOGGER_SEVERITY_DEFAULT" },
{ "Id": "TARGET_APPLY", "Severity": "LOGGER_SEVERITY_DEFAULT" },
{ "Id": "TARGET_LOAD", "Severity": "LOGGER_SEVERITY_DEFAULT" }
]
},
"ChangeProcessingTuning": {
"BatchApplyPreserveTransaction": true,
"BatchApplyTimeoutMin": 1,
"BatchApplyTimeoutMax": 30,
"MinTransactionSize": 1000
}
}
| 항목 | 의미 |
|---|---|
BatchApplyEnabled |
변경을 배치로 묶어 타깃에 적용. CDC 단계에서 처리량 ↑ |
ParallelLoadThreads |
테이블당 병렬 스레드 |
MaxFullLoadSubTasks |
동시에 풀로드 돌릴 테이블 수 |
TargetTablePrepMode = TRUNCATE_BEFORE_LOAD |
풀로드 전 타깃 테이블 비우기. 단, 외래키 있으면 실패할 수 있어 사전 작업 필요 |
CommitRate |
풀로드 커밋 단위. 트랜잭션 로그 부담 조절 |
5-3. Task 생성
aws dms create-replication-task \
--replication-task-identifier mssql-fullload-cdc \
--source-endpoint-arn arn:aws:dms:...:MSSQL-SOURCE \
--target-endpoint-arn arn:aws:dms:...:RDS-TARGET \
--replication-instance-arn arn:aws:dms:...:DMS-MSSQL-MIG \
--migration-type full-load-and-cdc \
--table-mappings file://table-mappings.json \
--replication-task-settings file://task-settings.json
# 시작
aws dms start-replication-task \
--replication-task-arn arn:aws:dms:...:task:MSSQL-FULLLOAD-CDC \
--start-replication-task-type start-replication
💡
full-load-and-cdc가 핵심.full-load만이면 풀로드 끝난 시점부터의 변경이 누락돼요.
6. 진행 모니터링 — 무엇을 봐야 하는가
6-1. Task 통계
콘솔에서 Task 의 Table statistics 탭이 가장 직관적이에요. 테이블별로
Full load rows/TotalInserts / Updates / Deletes(CDC 단계)Load state(Table completed,Before load,Full load,Table error)
6-2. CloudWatch 핵심 지표
| 지표 | 의미 | 컷오버 기준 |
|---|---|---|
CDCLatencySource |
소스 트랜잭션 로그 → DMS 까지 지연 (초) | 1초 이내 안정 |
CDCLatencyTarget |
DMS → 타깃 반영까지 지연 (초) | 1~3초 이내 안정 |
CDCIncomingChanges |
캡처 중인 변경 수 | 누적 안 쌓이고 평탄 |
CDCChangesMemorySource |
메모리에 쌓인 변경 | 안정적으로 낮게 |
CDCChangesDiskSource |
디스크로 떨어진 변경 | 0 근처 유지 |
✅ 컷오버 직전 체크 — CDCLatencyTarget 이 연속 5~10분간 1~3초 이내 유지되면 따라잡기 안정 상태로 봐도 됩니다.
⚠️
CDCLatencyTarget이 점점 커지면 DMS 인스턴스 사양 / 타깃 디스크 IOPS / 타깃 인덱스가 병목. 이 상태로 컷오버하면 안 됩니다.
7. 컷오버 절차 — 다운타임 분 단위 이하로
여기서부터가 본 게임이에요. 절차는 한 줄도 빼먹지 마세요.
Step 1. 사전 정렬
- CDC latency 가 안정적으로 작음 (5~10분 연속)
- Table statistics 의 에러 0
- 타깃에 인덱스/외래키 보조 객체 사전 생성 완료 (또는 사전 비활성)
- 애플리케이션 측 새 접속 정보(RDS 엔드포인트) 준비 완료
Step 2. 원본 쓰기 차단
가장 안전한 방법은 애플리케이션 측에서 새 트랜잭션을 막는 거예요. DB 단에서 막으려면
-- 원본 MSSQL
ALTER DATABASE [MyDB] SET READ_ONLY WITH ROLLBACK IMMEDIATE;
또는 앱 계정의 권한을 일시적으로 회수.
Step 3. 마지막 변경분 따라잡기 대기
CDCLatencyTarget → 0 으로 수렴
CDCIncomingChanges → 0 으로 수렴
10~30초 정도면 보통 마지막 트랜잭션까지 다 따라잡아요.
Step 4. 검증
-- 양쪽 row count 비교
SELECT COUNT(*) FROM dbo.Orders; -- 원본
SELECT COUNT(*) FROM dbo.Orders; -- 타깃
-- 최신 데이터 비교
SELECT MAX(updated_at) FROM dbo.Orders;
핵심 테이블 몇 개만 빠르게 비교.
Step 5. 외래키/시퀀스/IDENTITY 복구
-- 외래키 다시 enable
ALTER TABLE dbo.Orders WITH CHECK CHECK CONSTRAINT ALL;
-- IDENTITY 시드 재설정 (원본의 최댓값으로)
DBCC CHECKIDENT('dbo.Orders', RESEED, <원본 MAX(id)>);
Step 6. 앱 connection string 전환
DNS 변경 or config 핫스왑. 새 쿼리는 RDS 로 흐름.
Step 7. Task 중지
aws dms stop-replication-task --replication-task-arn arn:aws:dms:...:task:MSSQL-FULLLOAD-CDC
🎉 컷오버 완료. 다운타임은 보통 1~3분 안에 끝나요.
8. 사후 정리 — 비싸게 두지 말기
DMS Replication Instance 는 시간당 요금이 꾸준히 나와요. 컷오버 끝나면 빠르게 정리.
# 1. Task 삭제
aws dms delete-replication-task --replication-task-arn ...
# 2. Endpoints 삭제
aws dms delete-endpoint --endpoint-arn arn:aws:dms:...:endpoint:MSSQL-SOURCE
aws dms delete-endpoint --endpoint-arn arn:aws:dms:...:endpoint:RDS-TARGET
# 3. Replication Instance 삭제
aws dms delete-replication-instance --replication-instance-arn arn:aws:dms:...:rep:DMS-MSSQL-MIG
원본 MSSQL 의 CDC 도 더 이상 필요 없으면 끄세요. 트랜잭션 로그 보존 부담이 줄어요.
USE [MyDB];
-- 테이블별 CDC 끄기
EXEC sys.sp_cdc_disable_table
@source_schema = N'dbo',
@source_name = N'Orders',
@capture_instance = N'all';
-- DB 레벨 CDC 끄기
EXEC sys.sp_cdc_disable_db;
9. 자주 깨지는 함정
저도 한 번씩 다 밟아본 함정들이에요.
- 🚨 CDC 안 켜고 task 시작 →
full-load-and-cdc가CDC source endpoint not configured에러로 죽음 - 🚨 SQL Server Agent 가 꺼져있음 → CDC 캡처 잡이 안 돌아 변경이 누적되다 폭주
- 🚨 타깃에 외래키가 켜진 채 풀로드 → 부모/자식 로드 순서 때문에 실패. 풀로드 전 disable
- 🚨 IDENTITY 시드 미보정 → 컷오버 후 새 INSERT 가 키 충돌
- 🚨 권한 부족 (
VIEW SERVER STATE, CDC 함수EXECUTE) → CDC 단계에서 silent 하게 latency 만 늘어남 - 🚨
CDCLatencyTarget이 점점 커짐 → DMS 인스턴스 / 타깃 IOPS / 인덱스 병목. 이 상태로 컷오버 절대 금지 - 🚨 트랜잭션 로그 폭증 → CDC 가 안 따라잡고 있거나, 풀백업/로그백업 주기가 너무 길어서. log_reuse_wait_desc 확인
10. Native Backup/Restore vs DMS + CDC — 다시 정리
이전 글과 함께 의사결정 표 한 장으로 정리해드릴게요.
| 기준 | Native Backup/Restore | DMS + CDC |
|---|---|---|
| 원본 그대로 보존(인덱스/제약/IDENTITY) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ (수동 보정 필요) |
| 다운타임 | 분~시간 단위 | 분 단위 이하 |
| 운영 부담/비용 | 낮음 (일회성) | 중간 (Task 가동 시간 비용) |
| 셋업 난이도 | 낮음 | 중간~높음 |
| 추천 시나리오 | “그대로 + 짧은 유지보수 창” | “절대 다운 불가 + 운영 중 이전” |
저희 사내 케이스 기준으로는
- 유지보수 창이 1~2시간 허락 → Native Backup/Restore 단독
- 유지보수 창이 거의 없음 → Native Backup/Restore 로 풀카피 후 DMS 의 CDC-only 모드 로 변경분만 흘려 컷오버
두 번째 패턴이 사실상 베스트에요. 풀로드 비용은 백업/복원으로 절약하고, CDC 만 DMS 로 처리해서 latency 안정화 후 컷오버.
일단 오늘은 여기까지…..
다음 글에서는 이 두 번째 패턴 — 백업/복원 + CDC-only DMS 의 결합을 단계별로 풀어볼게요.
← 이전 글: (2/5) Native Backup/Restore 실전 — 옵션 그룹부터 컷오버까지 | 다음 글 →: (4/5) 변경분 계속 쌓기 — NORECOVERY 체인과 CDC-only 결합