AWS DMS를 통한 10억 레코드 DB 무중단 마이그레이션

konu·2025년 2월 15일
0

DevOps

목록 보기
4/4
post-thumbnail

(Amazon이 쫀득하게 말아주는 마이그레이션)

 

 

0. 배경

우리 DB가 많이 아파요

내가 프로젝트에 합류한 시점과 맞물려 MAU 지표가 급격하게 증가했다.
이에 따라 RDS 인스턴스의 CPU 사용량도 함께 급증하면서 종종 100%를 찍기도 했었다.

그래서 인덱스를 추가하거나, 읽기 전용 인스턴스를 활용하는 등 여러 방법을 시도해서 급한 불은 껐지만,
내부에서는 CPU 스파이크를 자동으로 대처할 수 있도록 DB 환경을 Multi-AZ 클러스터로 이전하기로 결정했다.

 

Multi-AZ?

Multi-AZmulti availability zone의 준말로,
가용 가능한 데이터베이스 인스턴스가 2개 이상의 장소에 산재되어 있음을 뜻한다.

따라서, 다음과 같은 상황에서도 빠른 failover가 가능하다.

  • 자연재해 등으로 인해 임의의 zone 내 데이터베이스 센터에 물리적인 충격이 가해졌을 때
  • 임의의 인스턴스에 장애가 발생했을 때

 

Multi-AZ는 위와 같이 인스턴스를 생성하는 콘솔 화면에서 간단하게 설정하여 지정할 수 있다.
그리고 사진과 같이 Clusterinstance로 옵션이 나뉜다.

현재는 인스턴스 환경을 가지고 있는데,
인스턴스 환경은 그림으로 나타낼 경우 다음과 같다.

 

이를 클러스터 환경으로 변환하려고 하는데, 인스턴스와 클러스터는 다음과 같이 비교할 수 있다.

 

 

DMS?

그리고 클러스터 환경으로의 안정적인 DB 마이그레이션을 진행하기 위해
AWS의 DMS(Database Migration Service)를 이용하기로 결정했다.

 

왜 DMS?

이번 마이그레이션에서 가장 중요한 포인트 중 하나는
서비스가 중단되지 않아야 한다는 점이었다.

그래서 마이그레이션 도중에 소스 데이터베이스에 쌓이는 데이터에 대한 업데이트가 손실 없이 진행되기를 원했다.
이에 따라 DMS에서 제공하는 CDC(Change Data Capture) 기능을 활용하기 위해 DMS를 마이그레이션 에이전트로 선정했다.

 

DMS란?

AWS에서 지원하는 소스 DB와 타겟 DB를 엔드포인트로 연결하여 데이터의 마이그레이션을 돕는 도구다.

덤프 뜨는 것과 다를 수 없겠다는 생각이 들 수 있겠지만,

  • CDC(Capture Data Change) 기능을 통해 마이그레이션 도중 소스 DB로의 지속적인 데이터 유입을 손실없이 이관할 수 있다.
  • 타겟 & 소스 DB 간 엔진이 다르더라도 호환시켜줄 수 있다.
  • mapping rule을 통해 특정 테이블 혹은 컬럼을 마이그레이션에서 포함하거나 제외할 수 있다.

 

DMS를 활용한 추상적인 마이그레이션 과정

full load 과정을 거쳐 비어 있는 타겟 DB에 소스 DB의 내용을 처음부터 옮겨 담는다.

 

full load가 완료되면, CDC를 통해 수정 내용을 지속적으로 반영한다.

 

웹 서버에서 바라보는 엔드포인트를 변경한다.
그리고 CDC를 통해 엔드포인트 변경 과정에서 소스 DB에 남아있는 변경사항도 모두 타겟 DB에 반영한다.

 

소스-타겟 간 데이터 비교 검증에 통과하면,
더이상 필요하지 않은 리소스를 모두 제거하여 마이그레이션을 종료한다.

 

 

1. 과정

1) 타겟 DB 생성

마이그레이션 받을 데이터베이스를 먼저 생성해야 한다.
(DB 생성에 관한 설명은 생략한다)

우리 팀은 클러스터 환경으로의 데이터 마이그레이션이 목표였기 때문에 클러스터 환경을 활용한다는 점을 제외하고는 소스 DB와 동일한 스펙으로 생성했다.

 

여기서 잠깐! 얼음!!

소스와 타겟 DB 사이에는 동일한 스키마가 적용되어 있어야 한다.

왜?

DMS는 아래 사진과 같이 database migration task 생성 시
target preparation mode 옵션을 통해 3가지 모드를 제공하고 있다.

이 중에서 drop tables on target 모드는 자동으로 소스 데이터베이스의 스키마까지 옮기지만,
인덱스와 외래키 제약 조건 등을 누락하게 되므로 추천하지 않는다.

따라서, do nothing 혹은 truncate 모드를 선택한 후 스키마를 덤프하는 것을 추천한다.
그리고 꼭 덤프 이후 diff 명령어로 스키마 사이에 차이가 없는지 확인해보자!

 

2) 마이그레이션 준비 🔫

<1> 네트워크 설정 🛜

DMS 인스턴스가 생성될 서브넷 그룹을 설정해야 한다.
RDS 인스턴스가 속한 VPC를 선택하여 원활하게 연결되도록 하자.

 

<2> DMS 인스턴스 생성 🐣

settings

이름을 필수로 설정해야 한다.

instance configuration

  • instance class
    스펙을 높게 잡아 빠르게 진행하는 것을 추천한다.
    스펙이 낮아 메모리가 넉넉치 않은 경우 LOB 컬럼 이관에 실패할 수 있기 때문이다.

  • engine version
    최신 버전을 추천한다.
    다만, 소스 & 타겟 DB 엔진 버전과 호환되는지 확인하고 적용해야 한다.

  • high availability
    단순 마이그레이션 작업에는 single 옵션으로 충분하다.

 

storage

스토리지 용량이 마이그레이션 성능에 영향을 미치기 때문에 높게 잡을수록 유리하다.

connectivity and security

  • VPC
    RDS 인스턴스와 동일하게 선택하자.

  • replication subnet group
    위의 네트워크 설정 항목에서 생성한 것을 선택하자.

(이하 다른 설정은 건들지 않았다)

 

<3> 엔드포인트 생성 📪

주의 ⚠️

엔드포인트는 소스 & 타겟 모두 생성해야 한다.

과정

위 사진과 같이 해당하는 데이터베이스 엔진을 선택한다.
그리고 psql로 DB 연결하듯 host, port, username 및 password를 제공해야 한다.

DB 연결에 필요한 정보를 모두 기입했다면, test endpoint connection에서 연결 여부를 확인할 수 있다.

타겟 DB의 foreign key 제약 조건 무시

타겟 데이터베이스가 MySQL일 경우 foreign key 제약 조건을 무시하도록 설정해야 한다.
DMS는 테이블 간 참조에 맞춰 테이블 마이그레이션 순서를 맞춰주지 않기 때문이다.

 

<4> DB 마이그레이션 태스크 생성 🏋️

task configuration

  • task identifier
    태스크 이름을 지정한다.

  • replication instance
    위에서 생성한 인스턴스를 지정하자.

  • source & target database endpoint
    위에서 생성한 엔드포인트를 지정하자.

  • migration type
    migrate and replicate을 선택했다.
    지속적으로 소스 DB에 쌓이는 업데이트 내용을 반영하려면 위 옵션을 선택해야 한다.

    참고로, migrate는 DB 덤프처럼 특정 시점의 소스 DB의 내용만 반영하고,
    replicate는 특정 시점 이후에 소스 DB에 쌓인 새 업데이트 내용만 반영한다.

task settings

  • custom CDC stop mode for source transactions
    CDC를 멈추는 시간 트리거를 미리 둘 수 있는 기능이다.
    우리는 마이그레이션이 완료되었다고 판단되면 수동으로 중단할 것이기 때문에 선택하지 않았다.

  • create recovery table on target DB
    마이그레이션에 실패했을 때 복구 가능한 컨트롤 테이블을 생성해주는 기능이다.
    불필요하다고 판단해 선택하지 않다.

  • target table preparation mode
    위의 타겟 DB 생성 항목에서 언급했듯, do nothing 혹은 truncate 모드를 추천한다.

  • stop task after full load completes
    full load 이후 CDC 시작 전에 한 번 끊어가기 위한 기능이다.
    불필요하다고 판단해 선택하지 않았다.

  • ✨✨✨ LOB column settings ✨✨✨
    소스 데이터베이스에 따라 매우 중요한 기능이다.
    우리 프로젝트에서는 크리티컬한 항목이기 때문에 하단 이슈 항목에서 따로 다룬다.

  • data validation
    full load 이후 자동으로 소스와 타겟 DB 간 데이터를 검증해주는 옵션이다.

    참고로, 검증은 명백한 PK 간 값의 비교를 통해 이뤄진다.
    그래서 복합 키를 가지는 중간 테이블은 검증에서 제외된다.

  • task logs
    CloudWatch에서 이관 작업의 세부 진행 내역을 출력해줍니다.
    디버깅에 매우 중요한 역할을 하기 때문에 꼭 선택해주자.

  • maximum number of tables to load in parallel
    동시에 n개 테이블을 한꺼번에 로드할 때 n을 지정한다.

    테이블은 1개씩 전송하는 것이 디버깅에도 유리하고 속도도 빠르기 때문에,
    테이블 1개마다 태스크를 생성한다면 1로 두는 것이 적절하다.

  • transaction consistency timeout
    이관 작업 직전 DB 내에서 진행중인 트랜잭션을 기다릴 timeout을 설정한다.
    이관 작업에 필요한 replication_slot은 lock이 걸린 테이블에 대해 접근하지 못하기 때문에 timeout이 필요하다.

  • commit rate during full load
    커밋의 속도를 지정한다.
    그리고 LOB 관련하여 중요한 역할을 하는데, 이는 이슈 항목에서 다룬다.

table mappings

마이그레이션에 포함할 스키마, 테이블을 결정할 수 있다.
참고로, 위 사진의 %는 모든 스키마, 테이블을 포함하는 와일드카드다.

postgresql 기준으로 public 스키마만 포함하여 불필요한 스키마는 제외하자.
그리고 테이블 크기가 큰 경우에는 여기서 1개 테이블만 명시하여 따로 마이그레이션 할 수 있다.

  • transformation rules
    소스 DB 데이터를 타겟 DB로 옮기면서 스키마, 테이블, 컬럼 등을 조작할 수 있다.

(마지막으로, premigration assessment는 사용하지 않았다)

 

 

3) 마이그레이션 시작 🏃

마이그레이션을 시작하면 곧바로 task status가 변경된다.
위 사진은 full load를 마치고 CDC를 진행하고 있는 상태다.

table statistics

각 테이블별로 아래 항목들을 확인할 수 있다.

  • 마이그레이션에 성공/실패 했는지
  • 마이그레이션에 얼마나 걸렸는지
  • 마이그레이션 이후 검증 결과 통과/실패 했는지

 

 

2. 이슈

하단에 프로젝트 진행중 겪은 여러 이슈들을 다룰 예정인데,
대부분의 이슈가 postgresql과 관련이 있기 때문에 다른 엔진을 사용중이라면 넘어가도 좋다.

1) LOB (Large Binary Object)

LOB는 DMS에서 지정하는 타입이다.
VARCHAR와 같이 작은 데이터가 아닌, 가변적이거나 큰 데이터를 담기 위해 정의되었다.

ORM

프로젝트에서 사용하는 ORM prisma를 통해 지정한 String 타입은 postgresql에서 text 타입으로 매핑된다.
그리고 text 타입은 1GB까지 확장 가능하기 때문에, DMS에서 LOB로 분류된다.

따라서 LOB가 없을 것이라고 단정짓고 배제할 경우
마이그레이션에 실패하게 될 것이므로 ORM의 매핑 전략을 꼭 확인하시고 진행하자!

Limited LOB Mode

DMS는 LOB 컬럼을 null로 세팅하고 레코드를 옮긴 후 나중에 채워넣는 방식으로 전송한다.
따라서, non-nullable인 PK가 LOB 타입인 경우 마이그레이션에 실패할 수 있다.

예외는 있다.
동일 엔진끼리 && limited LOB mode로 마이그레이션할 경우 전체 레코드를 한꺼번에 전송한다.

 

2) 복제 인스턴스 클래스 지정

배경

복제 인스턴스 생성시에 클래스(스펙)를 지정해야 한다.
그리고 이 클래스는 마이그레이션에 사용되는 메모리와 직접적인 관련이 있다.

이슈

Limited LOB mode를 선택했을 때에는 최대 LOB 사이즈를 미리 산정해야 한다.
그런데 이 값을 적절하게 선택하지 않은 경우 마이그레이션이 실패한다.

너무 크게 산정한 경우(최대값은 102400KB) 복제 인스턴스의 메모리 부족으로 실패하고,
너무 적으면 LOB 데이터가 짤린 채 마이그레이션되고 만다.

대신 이에 대한 적절한 공식이 있다.
LOB 최댓값 * commit rate * LOB 컬럼 수만큼 메모리가 필요하다.

따라서 이 세 값을 곱해보고,
commit rate를 줄이거나 인스턴스 스펙을 늘려야 한다.

 

3) 소스 DB의 logical_replication 파라미터

배경

소스 DB 엔진이 postgresql인데 CDC 기능을 이용하기 위해서는
rds.logical_replication 파라미터의 값을 1로 변경해야 한다.
(참조)

이슈

RDS 인스턴스에 rds.logical_replication과 같은 정적 파라미터를 적용하기 위해서는
재부팅이 강제된다.

그런데 현재 서비스중인 인스턴스를 재부팅할 경우, 그만큼 서비스의 장애 시간이 늘어나게 된다.
다행히도, Multi-AZ 옵션이 적용되어 있는 인스턴스는 이 문제를 최소화할 수 있다.

프로젝트에서 적용했던 Multi-AZ instance DB 옵션은 primary에 대해 standby 인스턴스가 존재한다.
standby 인스턴스는 primary의 데이터를 동기적으로 복제하고 있다가, failover 때 활용된다.

따라서, RDS 인스턴스를 재부팅할 때 reboot with failover 옵션을 선택해주면 된다.
참고로, 필자가 downtime을 측정했을 때는 최소 0.x초 ~ 3초 정도 소요되었다.

 

4) 타겟 DB의 session_replication_role 파라미터

배경

DMS에서 여러 테이블을 한꺼번에 마이그레이션하는 경우
테이블 간 순서를 보장해주지 않는다.

이슈

따라서 fk 제약 조건을 무시하도록 해야 하는데,
postgresql에서는 session_replication_role 파라미터를 replica로 변경해야 한다.

 

5) replication slot 스토리지 소모

배경

postgresql에서 논리적 복제를 실행할 경우 복제 슬롯을 생성하게 된다.
이 복제 슬롯은 타겟 DB가 죽더라도 복제를 원활히 재개하기 위해 필요하다.

레퍼런스 참조

이슈

이런저런 이유로 마이그레이션이 실패했고, 아무 생각없이 failed 상태를 두고 퇴근한 적이 있다.

 

그리고 다음날 출근 중 테스트 DB가 죽었다는 무수한 슬랙 요청이..!
(그나마 테스트 DB로 테스트하던 도중이라 다행이었다)

내 3개 복제 슬롯은 총 60GB의 스토리지를 하루아침에 소모시켜버렸다.
서비스 재개를 위해 스토리지를 우선 늘려놓고 쿼리를 통해 복제 슬롯을 제거하여 문제를 해결했다.

 

(그래서?)

 

3. 결론

이번 프로젝트는 긴 시간이 주어졌고, 처음 써보는 리소스인데다 국내 레퍼런스가 많지 않아 시행착오를 많이 겪었다.
그리고 DB 엔진을 많이 타는 작업인데, 하필 레퍼런스가 MySQL인 바람에 ...

그래도 그 수많은 억까를 이겨내고 (이슈 항목에서보다 1.5배 많은 사소한 이슈를 포함하여)
겨우겨우 마이그레이션을 성공시키고 말았다...

마이그레이션 이후에도 수많은 테스트를 거치고 (필요하시면 댓글 남겨주세요)
기존 상용 DB의 스냅샷을 남겨두고...

마지막으로 DMS 성능에 대한 언급하자면,
12억개 레코드 가진 테이블을 dms.r6i.2xlarge 클래스 기준 9시간에 옮겨준다.

그리고 소스 DB에는 별다른 영향을 주지 않기 때문에
더더욱 실시간 서비스 중인 DB 인스턴스에 무중단 마이그레이션을 진행하기 적절한 방식인 것 같다.

profile
日日是好日

0개의 댓글