Skip to content
목록으로

DMS 운영 : 성능, 검증, 모니터링

AWS DMS2025년 8월 17일
DMS 운영 : 성능, 검증, 모니터링

저번 포스팅에서는, DMS 의 구조와 CDC 동작 방식을 공부했습니다. 이번에는 실제로 돌릴 때 마주치는 문제들입니다. 성능을 어떻게 끌어올리는지, 데이터가 제대로 갔는지 어떻게 확인하는지, 무엇을 보고 있어야 하는지, 그리고 Redshift 로 보낼 때 특히 주의할 점을 정리하겠습니다.


1. 기본 키가 없으면 곤란하다


DMS 에서 가장 먼저 부딪히는 제약입니다.

1.1. 왜 필요한가

CDC 는 트랜잭션 로그에서 "이 행이 이렇게 바뀌었다" 를 읽어 타깃에 재현합니다. 그런데 타깃에서 어느 행을 고쳐야 하는지 지목하려면 그 행을 식별할 수단이 있어야 합니다. 기본 키가 없으면 DMS 는 타깃에서 그 행을 찾지 못합니다. 그래서 다음 제약이 생깁니다.

  1. 전체 적재는 됩니다. 그냥 읽어서 넣으면 되기 때문입니다.
  2. CDC 의 UPDATE 와 DELETE 가 되지 않습니다. INSERT 만 반영됩니다.
  3. LOB 컬럼을 옮길 수 없습니다.
  4. 데이터 검증 기능을 쓸 수 없습니다.

1.2. 대응 방법

  1. 소스에 기본 키를 추가합니다. 가능하다면 가장 깔끔합니다.
  2. 고유 인덱스를 대신 쓰게 합니다. 일부 엔진 조합에서 지원됩니다.
  3. S3 나 Kinesis 를 타깃으로 삼습니다. 이쪽은 행을 고치는 것이 아니라 변경 이벤트를 추가만 하는 방식이라 기본 키가 없어도 동작합니다.

세 번째가 실무에서 자주 쓰이는 우회로입니다. 변경 이력을 S3 에 쌓아두고 그 이력으로 최종 상태를 만드는 처리를 뒤에서 따로 합니다.


2. 전체 적재를 빠르게 하기


2.1. 인덱스와 제약을 잠시 걷어낸다

가장 효과가 큰 방법입니다. 타깃에 인덱스와 외래 키가 걸려 있으면, 행을 넣을 때마다 인덱스를 갱신하고 제약을 검사합니다. 수억 행이면 이 비용이 전체 시간을 지배합니다. 그래서 순서를 이렇게 잡습니다.

  1. 타깃에 테이블만 만들어 둡니다. 인덱스와 외래 키는 만들지 않습니다.
  2. 트리거도 꺼둡니다.
  3. 전체 적재를 수행합니다.
  4. 끝난 뒤 인덱스와 제약을 생성합니다.

2.2. 큰 테이블을 나눠 읽는다

1편에서 본 병렬 로드 규칙입니다. 큰 테이블 하나가 전체 작업의 발목을 잡는 경우가 많은데 파티션이나 값 범위로 나누면 여러 스레드가 동시에 읽습니다. 전체 작업 시간은 가장 느린 테이블이 결정하므로, 거대한 테이블 한두 개만 나눠도 효과가 큽니다.

2.3. 작업을 쪼갠다

테이블 수백 개를 작업 하나에 넣으면 관리가 어렵습니다. 하나가 실패하면 전체를 다시 봐야 하고 재시작 단위도 커집니다.

  1. 큰 테이블은 전용 작업으로 분리합니다.
  2. 성격이 비슷한 테이블끼리 묶습니다.
  3. 다만 작업이 많아지면 소스 DB 의 연결 수와 로그 읽기 부하가 늘어나므로 무한정 쪼갤 수는 없습니다.

2.4. 인스턴스 크기

복제 인스턴스가 작으면 여기가 병목이 됩니다. 특히 CDC 단계는 메모리를 많이 씁니다. 아직 커밋되지 않은 트랜잭션을 메모리에 들고 있어야 하기 때문입니다. 전체 적재 때는 크게 잡아 빨리 끝내고 이후 CDC 단계에서는 줄이는 방식도 씁니다.


3. CDC 지연 다루기


3.1. 두 가지 지연

DMS 를 운영할 때 가장 자주 보는 지표입니다. 둘을 구분하는 것이 중요합니다.

  1. CDCLatencySource : 소스에서 변경이 일어난 시각과, DMS 가 그것을 읽어낸 시각의 차이입니다. 이 값이 크면 읽는 쪽이 느린 것입니다.
  2. CDCLatencyTarget : DMS 가 읽은 시각과, 타깃에 적용이 끝난 시각의 차이입니다. 이 값이 크면 쓰는 쪽이 느린 것입니다.

어느 쪽이 큰지에 따라 대응이 완전히 달라집니다.

3.2. 소스 지연이 클 때

  1. 소스 DB 에서 대량 변경이 한꺼번에 일어났습니다. 배치 작업 때문인 경우가 많습니다.
  2. Oracle 의 LogMiner 가 느립니다. Binary Reader 로 바꾸는 것을 검토합니다.
  3. 같은 소스를 읽는 DMS 작업이 너무 많습니다. 로그를 여러 번 읽는 부하가 겹칩니다.
  4. 소스 DB 자체가 바빠 로그 읽기가 밀립니다.

3.3. 타깃 지연이 클 때

  1. 타깃이 쓰기를 감당하지 못합니다. 인덱스가 많거나 인스턴스가 작습니다.
  2. 기본 키가 없거나 인덱스가 없어 UPDATE/DELETE 가 전체 스캔을 유발합니다. 타깃에 적절한 인덱스가 필요합니다.
  3. Redshift 타깃의 배치 설정이 맞지 않습니다. 4장에서 다루겠습니다.
  4. LOB 처리가 Full LOB 모드라 한 건씩 추가 조회가 발생합니다.

3.4. 배치 적용

CDC 를 한 건씩 적용하면 느립니다. 변경을 모아서 한 번에 적용하는 설정이 있습니다.

  1. BatchApplyEnabled : 배치 적용을 켭니다.
  2. BatchApplyTimeoutMax : 최대 얼마나 모을지.
  3. BatchApplyMemoryLimit : 얼마나 쌓이면 내보낼지.

처리량과 지연은 맞바꾸는 관계입니다. 오래 모을수록 처리량은 늘지만 지연은 커집니다. 분석용이라면 몇 분의 지연은 대체로 허용되므로 배치를 키우는 편이 유리합니다.


4. Redshift 를 타깃으로 쓸 때


Redshift 시리즈에서 공부한 내용이 그대로 이어집니다.

4.1. DMS 는 내부적으로 S3 를 거친다

Redshift 로 보낼 때 DMS 는 행을 직접 INSERT 하지 않습니다. S3 에 파일로 모았다가 COPY 로 적재합니다. Redshift 시리즈 3편에서 본 이유 그대로입니다. 컬럼 지향 저장소에 한 건씩 넣는 것은 극단적으로 비효율적이기 때문입니다. DMS 가 알아서 집합 단위로 바꿔주는 셈입니다.

그래서 DMS 용 S3 버킷과 권한이 필요하고 그 경로가 지연의 원인이 될 수도 있습니다.

4.2. UPDATE 와 DELETE 가 비싸다

Redshift 시리즈 3편에서 본 대로, Redshift 의 UPDATE 는 기존 행을 삭제 표시하고 새 행을 뒤에 붙이는 방식입니다. CDC 로 UPDATE 가 계속 들어오면 어떻게 될까요.

  1. 삭제 표시된 행이 계속 쌓여 디스크를 차지합니다.
  2. 새 행이 정렬 키 순서와 무관하게 뒤에 붙어 정렬이 깨집니다.
  3. 존 맵의 효과가 떨어져 조회가 점점 느려집니다.

그래서 DMS 로 Redshift 에 지속 복제를 걸어둔 테이블은 SVV_TABLE_INFO 의 unsorted 값을 주기적으로 확인해야 합니다. 자동 VACUUM 이 따라가지 못하면 조회 성능이 서서히 나빠집니다.

4.3. 다른 구성도 고려한다

이런 이유로 DMS 에서 Redshift 로 직접 보내는 대신, S3 를 타깃으로 두고 변경 이력을 쌓은 뒤 따로 병합하는 구성을 쓰기도 합니다.

  1. DMS 타깃을 S3 로 둡니다. 변경 이벤트가 파일로 쌓입니다.
  2. 주기적으로 그 이력을 읽어 스테이징 테이블에 COPY 합니다.
  3. Redshift 시리즈 3편에서 본 스테이징 방식의 UPSERT 로 한 번에 반영합니다.

복잡해지지만 UPDATE 의 빈도를 통제할 수 있다는 것이 큰 이점입니다. 실시간성이 덜 중요하고 데이터 양이 많다면 이쪽이 안정적입니다.

4.4. 타입 매핑을 확인한다

Redshift 시리즈 2편에서 본 대로 VARCHAR 길이를 크게 잡으면 메모리를 낭비합니다. DMS 가 자동 생성한 테이블은 여유 있게 잡히는 경향이 있으므로, 테이블을 직접 만들어두고 DMS 가 그것을 쓰게 하는 편이 낫습니다.

분산 키와 정렬 키도 DMS 는 정해주지 않습니다. 미리 설계해서 만들어두어야 합니다.


5. 데이터가 제대로 갔는지 확인하기


5.1. 데이터 검증

DMS 에는 검증 기능이 내장되어 있습니다. 켜두면 소스와 타깃의 행을 비교해 다른 것을 찾아냅니다.

  1. 소스와 타깃에서 데이터를 읽어 행 단위로 비교합니다.
  2. 결과는 awsdms_validation_failures_v1 같은 제어 테이블에 기록됩니다.
  3. CDC 중에도 계속 동작합니다.

주의할 점이 있습니다.

  1. 기본 키가 필요합니다.
  2. 부하가 늘어납니다. 소스와 타깃 양쪽에서 데이터를 추가로 읽습니다.
  3. 타입 변환으로 인한 차이가 불일치로 잡힐 수 있습니다. 부동소수점이나 시간대 처리가 다른 경우입니다.

5.2. 직접 확인해야 할 것

검증 기능을 켜지 않았거나 쓸 수 없다면, 최소한 이 정도는 확인합니다.

  1. 행 수 비교 : 가장 기본입니다.
  2. 숫자 컬럼의 합계 : 정밀도 손실을 잡아냅니다.
  3. 날짜의 최솟값과 최댓값 : 시간대 문제를 잡아냅니다.
  4. NULL 개수 : 변환 과정에서 NULL 이 되어버린 경우를 잡습니다.
  5. 문자열 길이의 최댓값 : LOB 이나 VARCHAR 잘림을 잡습니다.

다섯 번째가 특히 중요합니다. 1편에서 본 Limited LOB 모드의 잘림은 오류 없이 조용히 일어나기 때문입니다.


6. 모니터링


6.1. 봐야 할 지표

  1. CDCLatencySource / CDCLatencyTarget : 3장에서 본 두 지연
  2. FreeableMemory : 복제 인스턴스의 여유 메모리
  3. FreeStorageSpace : 스토리지가 차면 작업이 실패합니다
  4. CPUUtilization
  5. CDCIncomingChanges : 아직 적용되지 않고 쌓여 있는 변경 수. 이 값이 계속 늘면 따라가지 못하고 있다는 뜻입니다

6.2. 로그

작업 로그를 CloudWatch 로 내보낼 수 있습니다. 기본 수준에서는 오류만 남으므로, 문제를 파고들 때는 특정 구성 요소의 로그 수준을 높입니다. 다만 상세 로그를 켜두면 양이 급격히 늘어나므로 진단이 끝나면 되돌립니다.

6.3. 제어 테이블

DMS 는 타깃에 자체 관리 테이블을 만들 수 있습니다.

  1. awsdms_apply_exceptions : 적용에 실패한 행이 기록됩니다. 반드시 켜두어야 할 테이블입니다. 여기에 조용히 쌓이는 오류를 모르고 지나가면 데이터가 어긋납니다.
  2. awsdms_status : 작업 상태
  3. awsdms_history : 이력

6.4. 알림

작업이 실패하거나 멈추면 알아야 합니다. DMS 이벤트를 SNS 로 연결하고 지연 지표에 CloudWatch 경보를 걸어둡니다. "작업이 실패한 것" 보다 "작업은 돌고 있는데 지연이 계속 늘어나는 것" 이 더 늦게 발견됩니다. 후자에도 경보를 걸어두어야 합니다.


7. 운영 중에 생기는 일들


7.1. 스키마가 바뀔 때

소스에 컬럼이 추가되면 어떻게 될까요. 엔진 조합에 따라 다르지만 자동으로 따라가지 않는 경우가 많습니다. 그래서 스키마 변경은 계획이 필요합니다.

  1. 타깃에 컬럼을 먼저 추가합니다.
  2. 소스에 컬럼을 추가합니다.
  3. 필요하면 작업을 재시작해 매핑을 갱신합니다.

컬럼 삭제나 타입 변경은 더 조심스럽습니다. 작업을 멈추고 정리한 뒤 재개하는 편이 안전합니다.

7.2. 작업을 재시작해야 할 때

  1. 이어서 재개(Resume) : 멈춘 지점부터 이어갑니다. 소스 로그가 아직 남아 있어야 합니다.
  2. 처음부터 다시(Restart) : 전체 적재부터 다시 합니다. 시간이 오래 걸립니다.

1편에서 말한 로그 보존 기간이 여기서 중요해집니다. 보존이 짧으면 이어서 재개할 수 없어 전체를 다시 해야 합니다. 특정 시점부터 CDC 를 시작하도록 지정할 수도 있어서 상황에 따라 전체 재적재를 피할 수 있습니다.

7.3. 전환 시점 정하기

마이그레이션이라면 언젠가 애플리케이션을 새 데이터베이스로 옮기는 순간이 옵니다.

  1. CDC 지연이 0에 가까워질 때까지 기다립니다.
  2. 소스에 대한 쓰기를 멈춥니다.
  3. 남은 변경이 전부 적용되었는지 확인합니다.
  4. 데이터를 검증합니다.
  5. 애플리케이션의 접속 대상을 바꿉니다.

되돌릴 계획도 함께 세워두어야 합니다. 반대 방향 복제를 미리 구성해두면 문제가 생겼을 때 돌아갈 수 있습니다.


8. 체크리스트


두 편에서 나온 것들을 모았습니다.

시작 전

  1. 소스의 로그 설정을 켰는가 (binlog ROW, wal_level logical, 보충 로깅)
  2. 로그 보존 기간이 충분한가
  3. 기본 키가 없는 테이블을 파악했는가
  4. LOB 컬럼의 크기 분포를 확인했는가
  5. 타깃 테이블을 직접 만들었는가 (Redshift 라면 분산 키·정렬 키 포함)
  6. 인덱스와 제약을 전체 적재 후에 만들 계획인가

운영 중

  1. 두 가지 지연을 나눠 보고 있는가
  2. awsdms_apply_exceptions 를 확인하고 있는가
  3. 지연 증가에 대한 경보가 있는가
  4. Redshift 타깃이라면 unsorted 비율을 보고 있는가

검증

  1. 행 수, 합계, 최소·최대, NULL 개수, 최대 문자열 길이를 비교했는가

9. 조금 더 들어가보기 : 배치 적용과 트랜잭션 경계


운영 항목들을 봤는데 배치 적용이 타깃에서 실제로 어떤 작업이 되는지와 긴 구간의 복제가 왜 불안정해지는지를 한 층 더 내려가 보겠습니다.

9.1. 배치 적용은 무엇을 합치는가

CDC 를 한 건씩 적용하면 타깃에 SQL 이 한 문장씩 들어갑니다. 배치 적용을 켜면 일정 시간 또는 일정 양만큼 모았다가 한 번에 처리합니다. 그런데 그냥 이어 붙이는 것이 아닙니다. 같은 행에 대한 여러 변경을 하나로 접습니다.

주문 1234 : INSERT → UPDATE(접수) → UPDATE(체결) → UPDATE(정정)
           ↓
          최종 상태 한 번만 적용

같은 키에 대한 중간 상태는 타깃에 반영할 필요가 없습니다. 어차피 마지막 값만 남기 때문입니다. 이렇게 접으면 타깃에 가는 작업량이 크게 줄어듭니다. 여기서 중요한 전제가 나옵니다. 접으려면 행을 식별할 키가 있어야 합니다. 앞서 본 기본 키 이야기가 여기로 이어집니다. 키가 없으면 접을 수 없고 배치의 이득도 크게 줄어듭니다.

그리고 부작용도 있습니다. 중간 상태가 타깃에 나타나지 않습니다. 상태 변화 이력을 보려고 복제를 걸었다면, 배치를 크게 잡을수록 중간 단계가 사라집니다. 이력이 필요하면 타깃을 S3 나 스트림으로 두어 변경 이벤트를 그대로 쌓는 편이 맞습니다.

9.2. 왜 커밋이 나와야 적용할 수 있는가

앞서 긴 트랜잭션이 메모리를 부풀린다고 했는데 그 이유를 보겠습니다. 트랜잭션 로그에는 아직 커밋되지 않은 변경도 들어 있습니다. 롤백될 수도 있는 내용입니다. 그것을 먼저 타깃에 반영하면, 롤백됐을 때 되돌릴 방법이 없습니다.

그래서 복제 도구는 커밋을 볼 때까지 그 트랜잭션의 변경을 붙들고 있어야 합니다. 여기서 몇 가지가 따라옵니다.

  1. 트랜잭션이 길수록 메모리가 부풉니다. 수천만 행을 한 트랜잭션으로 갱신하는 배치가 있으면 그만큼 쌓입니다.
  2. 메모리를 넘으면 디스크로 넘깁니다. 그러면 느려집니다.
  3. 긴 트랜잭션 하나가 뒤의 모든 변경을 밀리게 합니다. 순서를 지켜야 하므로 그것이 커밋될 때까지 뒤가 나가지 못합니다.

소스에서 대량 갱신을 나눠 커밋하는 것이 복제 안정성에 직접 도움이 되는 이유입니다. 운영 DB 쪽 배치를 손보는 일이 복제 쪽 문제를 푸는 경우입니다.

9.3. 지연 지표가 거짓말을 할 때

지연이 0에 가까운데도 데이터가 안 오는 경우가 있습니다. 소스에 변경이 없으면 읽을 것이 없고 그러면 지연도 0입니다. 그런데 이것이 "정상적으로 한가한 것" 인지 "테이블 매핑이 잘못돼 아무것도 안 잡히는 것" 인지 지연 지표만으로는 구분되지 않습니다.

그래서 적용된 행 수를 함께 봐야 합니다. 지연 0 + 처리 건수 0 이 장시간 이어진다면 의심해야 합니다. 특히 새 작업을 만든 직후에 이 상태면 설정 문제일 가능성이 큽니다. 반대 경우도 있습니다. 지연이 0인데 랙이 쌓이는 상황입니다. 소스 로그를 읽는 지점은 따라가고 있는데 타깃 적용이 밀리면, 어느 지표를 보느냐에 따라 정반대로 읽힙니다. 앞서 두 지연을 나눠 보라고 한 이유가 이것입니다.

9.4. 네트워크가 끊겼다 붙을 때

복제 인스턴스와 소스·타깃 사이의 연결이 잠깐 끊기면 어떻게 될까요.

  1. 재연결합니다. 마지막으로 읽은 위치가 기록되어 있으므로 이어서 받습니다.
  2. 그런데 끊긴 시점의 진행 상황이 확정되지 않은 경우가 있습니다. 타깃에 적용은 됐는데 그 사실을 기록하기 전에 끊겼다면, 재개하면서 같은 구간을 다시 적용합니다.
  3. 그래서 중복이 생길 수 있습니다.

여기서 앞서 정리한 이야기가 다시 나옵니다. 최소 한 번 전달이고 중복은 타깃에서 흡수해야 합니다. 기본 키가 있으면 upsert 로 흡수되고 없으면 중복이 그대로 쌓입니다. 기본 키가 없는 테이블이 위험한 이유가 하나 더 있는 셈입니다. CDC 의 갱신과 삭제가 안 되는 것뿐 아니라, 재연결 시 중복을 막을 수단도 없습니다.

9.5. 대규모에서 부딪히는 것들

  1. 소스 로그 읽기 경합 : 같은 소스를 여러 작업이 읽으면 로그를 여러 번 훑습니다. 소스 DB 의 부하가 작업 수에 비례해 늘어납니다. 테이블을 나누더라도 로그는 하나라는 점을 염두에 두어야 합니다.
  2. 복제 인스턴스 스토리지 : 타깃이 밀리면 여기에 쌓입니다. 가득 차면 작업이 실패하므로 여유와 경보가 필요합니다.
  3. 타깃의 커밋 처리량 : 앞서 본 대로 웨어하우스는 커밋이 직렬화되므로, 작업을 잘게 나눠 병렬로 밀어도 타깃에서 줄을 섭니다.
  4. 테이블 수 : 작업 하나가 다루는 테이블이 수백 개면 전체 적재의 병렬 처리 수와 연결 수가 소스 DB 의 한도에 걸립니다.
  5. 장시간 운영 시의 스키마 변경 : 몇 달을 돌리다 보면 소스 스키마가 반드시 바뀝니다. 복제를 시작할 때 스키마 변경 절차를 함께 정해두지 않으면 나중에 반드시 문제가 됩니다.

마지막 항목이 실무에서 가장 자주 간과됩니다. 복제 구성은 한 번 만들면 끝나는 작업처럼 보이는데 소스가 살아 있는 시스템인 이상 계속 바뀌기 때문입니다.


결론


DMS 를 정리하면서 가장 크게 느낀 것은, 관리형 서비스라고 해서 신경 쓸 것이 적은 것은 아니라는 점이었습니다. 소스의 로그 설정, 기본 키 유무, LOB 크기 분포, 타깃의 인덱스까지 챙길 것이 많았습니다. DMS 가 대신해주는 것은 읽고 옮기고 재현하는 기계적인 부분이고 무엇을 어떻게 옮길지에 대한 판단은 여전히 사람의 몫이었습니다.

두 번째로 인상 깊었던 것은 두 가지 지연을 나눠 본다는 설계였습니다. "느리다" 는 하나의 증상인데 읽는 쪽이 느린 것과 쓰는 쪽이 느린 것은 원인도 대응도 완전히 다릅니다. 지표를 처음부터 둘로 갈라두었기 때문에 문제를 반으로 좁힐 수 있었습니다. 무엇을 측정할지 정하는 것이 곧 진단 능력을 정한다는 생각이 들었습니다.

세 번째는 Redshift 로 직접 보내는 것이 늘 최선은 아니라는 점이었습니다. CDC 의 UPDATE 가 Redshift 에서는 삭제 표시와 재삽입이 되고 그것이 쌓이면 정렬이 깨져 조회가 느려집니다. Redshift 시리즈에서 공부한 저장 구조를 알고 있어야 이 문제를 예상할 수 있었고 S3 를 거쳐 배치로 병합하는 우회로를 떠올릴 수 있었습니다. 두 시스템을 각각 아는 것과 이어 붙일 때 무엇이 문제가 되는지 아는 것은 다른 지식이었습니다.

마지막으로, 조용히 실패하는 것들이 가장 무섭다는 것을 다시 확인했습니다. Limited LOB 의 잘림도, awsdms_apply_exceptions 에 쌓이는 행도 오류를 내지 않습니다. 작업이 초록색이어도 데이터는 어긋나 있을 수 있다는 전제로 확인 절차를 만들어두어야 한다는 것을 배웠습니다.


공부 참고 자료


📑 1. DMS - 모범 사례

📑 2. DMS - 작업 설정

📑 3. DMS - 데이터 검증

📑 4. DMS - 모니터링

📑 5. DMS - 타깃으로 Redshift 사용

📑 6. DMS - 타깃으로 S3 사용

📑 7. DMS - 문제 해결

📑 8. DMS - LOB 지원 설정

Comments