사용법과 함께 작성해본 좌충우돌 AWS DMS 사용기 - feat. RDS 통합 이야기

사용법과 함께 작성해본 좌충우돌 AWS DMS 사용기 - feat. RDS 통합 이야기

안녕하세요, 뱅크샐러드 Core Infra 팀의 DevOps Engineer 이재환 입니다. AWS DMS를 사용하면서 겪었던 경험들을 기반으로 기본적인 사용법에 대해 공유해보고자 합니다.

RDS 비용 최적화 이야기 (서론)

요즘 비용 최적화에 다들 관심많으시죠?
저희 뱅크샐러드에서도 최근 비용 최적화에 대해 관심을 갖고 많은 부분에 대한 최적화를 진행하고 있습니다.
먹이감을 찾는 맹수들처럼 인프라 구석구석을 뒤지며 최적화 할 수 있는 자원을 찾고 있었는데요. 그 때 눈에 들어온게 RDS 클러스터들이었습니다.

money_emoji

뱅크샐러드는 Kubernetes 기반의 MSA 아키텍처로 운영되고 있습니다.
Micro 단위의 서비스 분리로 상호 서비스 간의 장애 영향도를 최소화 한다는 MSA 아키텍처의 장점을 적극 활용하기위해 각 서비스마다 RDS 클러스터 1개씩을 갖고 있었습니다.
하지만 이런 구조는 요즘 커뮤니티에서 MSA 아키텍처의 단점으로 많이 이야기되고 있는것처럼 엄청난 자원 비효율성을 갖게 됩니다.
실제로 RDS 클러스터 자원량을 살펴보았을 때, 데이터베이스를 구동하기 위한 최소 생성 스팩인 t3.small로 생성 하였지만 정작 사용량은 10%를 넘지 않는 경우가 많이 발견되었습니다.
‘이 정도 사용량이라면 작은거 5개 쓰는것보다는 큰거 1개를 나눠서 사용하는게 효율적이지 않은가?’ 라는 생각에 까지 도달하게 되었습니다.
하지만 웃어넘기기에는 꽤 괜찮은 아이디어였고, 뱅크샐러드의 환경에 맞게끔 현실화해서 안정성과 리소스 효율성 두 마리 토끼를 다 잡기 위한 설계가 시작되었습니다.

뱅크샐러드의 전체 서비스를 크게 curation, 건강, 자산, 가계부, 인증 등 몇가지 큼직한 업무 도메인으로 나눌 수 있었습니다.
세부적으로는 각각의 도메인 별로 5~15개씩 서비스 분류가 가능했고 각 업무 도메인 단위로 작은 여러개의 데이터베이스 클러스터를 하나의 큰 데이터베이스 클러스터로 통합할 수 있겠다는 판단이 되었습니다.
이렇게 50여개의 RDS 클러스터를 7개의 업무 도메인 클러스터링을 통해 자원 효율성을 챙기면서도 서비스의 장애가 다른 업무 도메인으로 전파되는 것은 보호 할 수 있는 제법 그럴듯한 계획이 완성되었습니다.

계획이 완성되었으니 실천을 해야겠죠?
데이터베이스를 통합하기 위한 업무 도구 부터 선택해야했습니다. 도구 선택에는 몇가지 전제 조건이 있었는데요.

첫째. 클러스터링을 위해 N:1 로 통합이 되어야 한다.
둘째. 최소한의 서비스 중단으로 TB 단위의 데이터베이스도 이관 되어야 한다.
셋째. 실시간 변경되는 데이터까지 1개의 데이터 유실 없이 완벽하게 이관되어야 한다.

이 모든 전제 조건을 충족할 수 있는게 바로 DMS 였습니다. Source와 Target 엔드포인트를 생성하여 N:1 통합이 가능하였으며, Full Dump와 CDC 기능을 모두 지원하기 때문에 서비스 운영중인 주간에도 통합 작업을 진행할 수 있습니다.

DMS? 그게 뭔가요?

AWS DMS (Database Migration Service) 는 이름 그대로 데이터베이스를 마이그레이션 해주는 AWS 서비스 입니다.

아래서 설명드리겠지만 DMS는 Source와 Target 각각의 엔드포인트를 생성하기 때문에 각 엔드포인트가 같은 Cloud환경이 아니어도 됩니다.
즉, On-Premise 환경의 데이터베이스에서 Cloud로 이관 혹은 그 반대로 이관할 때 용이하게 사용될 수 있습니다.
또한 각 엔드포인트가 꼭 관계형 데이터베이스여야만 하는게 아니라 데이터 웨어하우스, NoSQL 데이터베이스 등 다양한 데이터 저장소를 마이그레이션 할 수 있는 서비스입니다.

DMS 사용 우여곡절 이야기

PoC 할 때는 간단했는데 모든게 그렇듯 실제 사용할 때는 중간중간 우여곡절이 많았습니다.
그 중 몇가지 대표적인 것만 간단히 공유드려볼까 합니다.

첫번째. 생각보다 높았던 부하

DMS도 결국은 Source로 부터 데이터를 읽어서 Target으로 데이터를 쓰는 방식이다보니, 실제 운영중인 데이터베이스에 생각보다 많은 부하를 주었습니다.
실제 운영에 필요한 부하와 DMS를 사용하기 위한 부하가 합쳐지면서 CPU가 100%까지 치솟아 오르며 서비스 장애로 이어졌고, 식겁하며 DMS작업을 중단하게 되었습니다.
DMS의 부하를 과소평가한 스스로를 반성하며, 작업대상이 되는 Source와 Target의 RDS 인스턴스에 대해 평소 Peak 사용량의 2배 이상의 부하를 견딜 수 있도록 높은 인스턴스 클래스로 스팩업 후 작업을 재개하였습니다.
여러분은 대용량 데이터베이스 이관 작업을 진행하신다면 꼭.꼭. 과하다 싶을 정도로 여유롭게 스팩업 한 다음 작업하시기를 추천드립니다.

rds_alert

두번째. binary log 파일 보존 기한

지속적 복제 기능을 활용할 때 MySQL을 Source로 하는 경우 binary log 파일의 보존 기한 안에 Full Load가 완료되어야 한다는 전제 조건이 있습니다.
저희가 사용하던 Aurora MySQL 5.7 버전의 binary log 파일 최대 보존기간은 최대 168시간 즉, 7일이기 때문에 TB단위의 대용량 데이터베이스를 옮기는데 7일 안에 완료되지 않으면 정상적인 작업 완료가 불가능한 이슈가 발생했는데요
이를 해결하고자 Source와 Target의 데이터베이스 인스턴스 클래스 외에도 데이터 이관을 담당하는 DMS의 인스턴스 클래스도 고사양으로 변경하여 네트워크 속도를 상향시켰습니다. (네트워크 보장 속도를 위해 최소 8xlarge의 스팩을 권장합니다.)
또한 아래에서도 설명드리겠지만 데이터 이관 방식 중 안전한 Full LOB Mode 만을 고집하지 않고 Limited LOB Mode 사용으로 데이터 이관 자체의 속도를 끌어올려 7일 안에 Full Load가 완료될 수 있도록 노력하였습니다.

세번째. 데이터 검증

아무리 DMS가 완벽하다해도 확인은 꼼꼼히 해봐야겠죠? 이관 작업 자체는 서비스 운영 중에 무중단으로 진행하였지만, 꼼꼼한 검수작업을 위해서는 트래픽을 완전 차단하고 모든 데이터가 잘 옮겨졌는지 데이터 검증 과정이 필요했습니다.

  1. 사용자가 비교적 적을 야간에 서비스 점검을 진행하여 모든 트래픽을 차단 (데이터 변경 방지)
  2. Source와 Target의 전체 Row Count 비교
  3. 1,000개 단위로 Row를 Sampling하고 Source와 Target에 대한 Row Diff x 만족할 때 까지 반복
  4. 서비스의 Connection을 Target 데이터베이스로 변경한 후 Unit 테스트 진행
  5. 전체 QA 및 통합 테스트 진행

1개의 데이터 유실도 용납하지 않기 위해 위와 같은 검증 방법으로 겹겹으로 데이터 정합성 체크를 진행하였습니다.

마이데이터의 대표주자 뱅크샐러드는 여러분의 데이터를 아주아주 소중하고 중요하게 다루고 있습니다. 믿고 사용해주세요! ^^b

최종적으로 DMS 덕분에 RDS통합 작업은 데이터 유실 없이 잘 마무리 되었습니다. RDS 클러스터 사용 비용도 작업 전 대비 1/3 로 감소하면서 많은 비용절약 효과를 얻을 수 있었고, 계획했던대로 다른 업무 도메인 단위로의 장애 전파 차단 효과도 지켜낼 수 있었습니다.

clap_tada_emoji

이와 같은 우여곡절과 삽질들을 뱅크샐러드 내부에만 공유하기는 아쉬워서 이러한 경험들을 기반으로 가장 기본적인 DMS 사용법에 대해 공유 드려볼까 합니다.

DMS 사용법

그럼 이제 본격적으로 DMS 사용법에 대해서 소개드리도록 하겠습니다. 물론 Case By Case 로 너무 다양한 사용방법이 존재하겠지만 초심자 눈높이에 맞추어 가장 기본적인 사용 방법을 설명 드리도록 하겠습니다.

더 많은 사용법은 AWS 공식 DMS 워크샵 을 참고 부탁드립니다.

DMS 아키텍처

dms_architecture

DMS의 구조는 위와 같습니다. 데이터를 복제할 인스턴스를 생성하고 Source와 Target 데이터베이스를 연결해주는 엔드포인트를 생성합니다. 그리고 복제 인스턴스를 통해 Replication Task를 실행하여 데이터 이관 작업을 진행해줍니다.

DMS 네트워크 세팅

dms_subnet

DMS도 결국은 AWS의 컴퓨팅 리소스를 활용해서 데이터를 이관해주는 서비스이기 때문에 인스턴스가 생성될 서브넷 그룹이 필요합니다. AWS DMS > Certificates > Subnet groups 메뉴를 통해 생성할 수 있습니다. Source와 Target 데이터베이스 모두에 접근할 수 있는 VPC 와 서브넷을 선택해주시면 됩니다.

DMS 인스턴스 생성

인스턴스 세팅

dms_instance_settings

네트워크 생성이 완료되었다면 복제 작업을 실행할 인스턴스를 만들어주어야 합니다. Migrate data > Replication Instances 메뉴를 통해 생성할 수 있습니다. Settings에서는 간단하게 인스턴스 이름을 입력받습니다.

인스턴스 설정

dms_instance_configuration

configuration에서는 인스턴스의 사양을 선택할 수 있습니다.

  • Instance class : 인스턴스 스팩을 정의 합니다. 경우에 따라 다르겠지만 데이터베이스 이관 작업은 고스펙으로 빠른 시간안에 끝내는게 좋다고 합니다. 인스턴스 성능에 따라 작업시간이 천차만별로 달라질 수 있으니 상황에 맞는 스팩을 선택하도록 합니다. 뱅크샐러드의 경우에는 r5.8xlarge 스팩을 사용하여 단기간 내 작업이 처리될 수 있도록 했습니다.
  • Engine version : 당연히 최신 버전을 사용하는게 좋겠죠?
  • High Availability : 장기간 고가용성을 확보해야 하는 작업이라면 Multi-AZ 를 사용하는게 좋겠으나, 일회성 데이터베이스 통합 작업에는 Single-AZ 에서도 충분히 잘 동작해 주었습니다.
  • Allocated storage (GiB) : 스토리지 용량이 작업 속도에 영향을 준다고 합니다. 넉넉하게 준비해주도록 합니다. 저희 작업때는 1TB를 사용했었습니다.

인스턴스 연결

dms_instance_connectivity

Connectivity and security 에서는 인스턴스의 네트워크 설정을 해준다고 보면 됩니다. 위 DMS 네트워크 세팅에서 사용했던 VPC 와 생성한 Subnet group을 선택해주고 Public accessible 은 Off 처리하였습니다. 이하 다른 설정들은 건들지 않았으며, 사용하시는 환경에 맞추어 세팅하시면 될 것 같습니다.

DMS 엔드포인트 생성

dms_endpoint_type

위에서도 언급되었듯이 DMS는 Source와 Target을 각각 생성해주어야 합니다.

Source 엔드포인트 생성

dms_source_endpoint_configuration

이제 엔드포인트 상세 설정을 진행 해보겠습니다.

  • Endpoint identifier : 알아보기 쉬운 이름으로 엔드포인트 이름을 지정합니다.
  • Source engine : Source가 되는 대상의 엔진을 선택해주세요.
  • Access to endpoint database : 별도로 Secret Manager 를 사용하는 경우가 아니라면 Provide access information manually 를 선택해서 수동 입력 해줍니다.
  • Server name : RDS의 호스트 주소를 입력합니다. 단, MySQL인 경우 binary log 파일을 읽을 수 있도록 RW가 가능한 주소를 입력해주셔야 합니다.
  • Port : RDS의 포트번호를 입력합니다. MySQL 기본 포트 설정으로 쓰고 있다면 3306이겠죠?
  • Admin계정으로 User name, Password 를 채워줍니다.

dms_endpoint_test

엔드포인트 정보를 입력하고나면 하단에 엔드포인트가 잘 설정되었는지 테스트해볼 수 있습니다.
테스트 할 복제 인스턴스와 VPC 를 선택하고 Run test 를 눌러서 엔드포인트 설정이 잘 되었는지 확인해보시길 바랍니다.

Target 엔드포인트 생성

dms_target_endpoint_configuration

Target 엔드포인트 생성도 Source 엔드포인트 때와 대동소이합니다.
다만, 주의해주셔야할 점이 있다면 Target 이다보니 데이터 쓰기를 할 수 있도록 RW가 가능한 호스트, 계정을 사용해야됩니다.
또 팁을 드리자면 FOREIGN_KEY 제약조건을 무시하고 복제 작업을 진행 할 수 있도록 Extra connection attributesinitstmt=SET FOREIGN_KEY_CHECKS=0 를 추가 해주시는게 좋습니다. 해당 옵션을 사용하면 FOREIGN_KEY 제약조건을 무시하여 데이터를 옮겨주기 때문에 사용자 입장에서는 아무것도 신경쓸게 없어집니다.

복제 테스크 생성

엔드포인트까지 생성했다면 이제 모든 사전 준비는 끝났습니다. 복제 테스크를 만들러 가보시죠 Migrate data > Database migration tasks 를 통해 복제 테스크를 생성할 수 있습니다.

Task Configuration

dms_task_configuration

테스크 이름과 사용할 복제 인스턴스 각각의 엔드포인트를 선택해줍니다. 그리고 Migration type 을 3가지를 선택할 수 있는데 진행하고자 하는 적당한 타입을 선택해줍니다.

  • Migrate existing data : 데이터 Full Dump 만 진행
  • Migrate existing data and replicate ongoing changes : 데이터 Full Dump 진행 후 지속적인 복제까지 진행
  • replicate ongoing changes : 현 시점으로 부터 지속적인 복제만 진행

이 때, 만약 Source 데이터베이스가 MySQL 이고 지속적인 복제를 사용하시려면 binary logging이 활성화 되어 있어야 하며, 보존 기한을 확인하셔야 합니다. MySQL의 경우 지속적인 복제 기능을 할 때 binary log를 활용하기 때문에 충분한 보존 기한이 확보되어야 합니다.

  • binary logging 활성화 확인
show variables like 'log_bin';
  • 보존 기간 확인
CALL mysql.rds_show_configuration;
  • 보존 기간 설정
call mysql.rds_set_configuration('binlog retention hours', 168);

Task Settings

dms_task_setting_1 dms_task_setting_2

JSON 포맷으로 정형화되게 생성할 수도 있지만 Wizard로 손쉽게 만들어 보도록 합니다. 여러가지 옵션을 지정할 수 있지만 가장 기본적인 것들만 안내드리겠습니다.

  • Target table prepargation mode : DMS 는 Auto Increment, FK 등의 제약조건은 자동으로 생성해주지 않기 때문에 Target 에도 Source 와 동일한 스키마 구조를 미리 생성해 두고 Do nothing 혹은 Truncate 를 사용하실 것을 추천드립니다.
  • LOB column settings : LOB 데이터를 어떻게 처리할 것인지를 선택해야 합니다. 상황에 따라 적절한 방법을 선택하시면 되겠습니다.
    • Don't include LOB columns : 이름 그대로 LOB 컬럼을 옮기지 않는 방식
    • Full LOB mode : LOB 데이터 자체를 통째로 옮기기 때문에 느리지만 안전한 방식
    • Limited LOB mode : 지정한 크기로 LOB 데이터를 잘라서 옮겨주기 때문에 상대적으로 빠르지만 크기를 잘못 지정할 경우 LOB 데이터가 잘릴 수 있는 방식

만약 Limited LOB mode 를 사용하셔야되는 경우라면 아래 쿼리를 사용해서 LOB 데이터의 최대 길이를 꼭 확인하고 잘 설정해주시길 바랍니다.

SELECT (MAX(LENGTH(${LOB_COLUMN}))/1024) AS "size in KB" FROM ${TABLE};
  • Validation : Source 와 Target의 데이터 상태를 검증해줍니다. 심리적 안정을 위해 Turn on 해주시는 것을 권장 드립니다.
  • Task logs : 테스크가 실패하면 왜 실패했는지 로그를 확인해야됨으로 활성화 해주실 것을 권장 드립니다.

Table mappings

dms_task_table_mappings

테스크에서 어떤 스키마와 테이블을 옮길 것인지 지정해줍니다.

스키마의 경우 전체 스키마가 복제되면 권한 문제 등이 발생할 수 있으니 필요한 스키마만 지정하도록 합니다.
테이블의 경우도 스키마와 마찬가지로 % 와 같이 전체를 지정할 수 있고 특정 테이블만을 지정할 수도 있습니다. 특정 테이블을 지정해서 각 테이블마다 테스크를 생성해주는 것이 테스크 재시작 등 관리하기 편했고 DMS 실행 시 범위가 좁아져 조금 더 빠른 속도로 진행되었기에 가급적이면 % 대신 특정 테이블을 지정해서 사용해주시길 권장 드립니다.

이 때, Source filters 를 이용해서 특정 컬럼만 선택하거나 제외하게끔 설정할 수 있고 Transformation rules 를 이용해서 스키마, 테이블, 컬럼을 변경하여 Target 에 적재할 수 있습니다.
이번 예시의 경우 단순히 복제만 하는 것이기 때문에 사용하지 않지만 Souce에서 Target으로 옮겨갈때 변경되는 사항이 있다면 유용하게 사용할 수 있습니다.

Task Start

dms_task_start

이제 모든 설정을 마쳤습니다. 최종적으로 생성과 동시에 실행할 것인지 나중에 실행할 것인지를 선택하고 테스크를 생성해주도록 합니다.

DMS 테스크 진행

dms_task_running

테스크가 Ready 상태를 거쳐 정상적으로 Running 중인것을 확인할 수 있습니다.
제가 DMS를 사용하면서 가장 좋았던 점은 개인적으로 진척률이 표시되어 완료 시기를 예측할 수 있다는 것입니다.

dms_task_completed

곧이어 테스크가 정상적으로 완료되었고 지속적 복제가 진행중임을 확인 할 수 있습니다.

dms_task_statistics

테스크 생성 당시 validate on 을 했을 경우 테스크 상세 내용에서 테이블 검증 상태도 확인할 수 있습니다.

dms_table_diff

실제 테이블 현황을 비교해볼까요?
테스트로 만들은 테이블이라 민망할 정도로 데이터가 빈약한 상태지만 잘 복제되었음을 확인할 수 있습니다.

에러 케이스

이 글을 읽어주시는 분들의 저와 똑같은 삽질을 방지하고자 제가 경험했던 에러 케이스를 조금 공유 드립니다.

FOREIGN_KEY 제약 조건

[TARGET_LOAD ]E: RetCode: SQL_ERROR SqlState: HY000 NativeError: 1452 Message: [MySQL][ODBC 8.0(w) Driver][mysqld-8.0.23]Cannot add or update a child row: a foreign key constraint fails (`table`.`column`, CONSTRAINT `fk` FOREIGN KEY (`column`) REFERENCES `column) [1022502] (ar_odbc_stmt.c:2738)

DMS가 FK를 고려해서 이관하지는 않는듯 합니다. 그런것까지 바라면 욕심이겠죠..?
위에서 말씀드린 팁 중에서 Target 엔드포인트 생성할 때 Extra connection attributesinitstmt=SET FOREIGN_KEY_CHECKS=0 를 추가 해서 FK 체크를 안하도록 설정하여 해결할 수 있었습니다.
해당 옵션을 사용하면 사용자 입장에서는 별도로 Target 데이터베이스의 FOREIGN_KEY를 조작해주거나 하지 않아도 DMS가 제약조건 체크를 안하기때문에 자연스럽게 데이터 이관 작업을 진행할 수 있습니다.

LOB 데이터 NOT NULL 제약 조건

RetCode: SQL_ERROR SqlState: HY000 NativeError: 3140 Message: [MySQL][ODBC 8.0(w) Driver][mysqld-8.0.23]Invalid JSON text: "The document is empty." at position 0 in value for column 'table.column'. [1022502] (ar_odbc_stmt.c:2738)

에러 내용을 의역해보자면.. NOT NULL 제약 조건을 갖고 있는 JSON 타입의 컬럼이 비어있다고 합니다..
Source 에서 아무리 is null 조회를 해도 실제 null 인 row 는 존재하지 않았습니다.
찾고 찾다가 결국은 AWS 문서에서 답을 찾을 수 있었습니다.

dms_error_notnull

즉, LOB 데이터는 나중에 옮기려고 해당 컬럼을 비워두기 때문에 Target에 Not Null 제약 조건이 걸려있다면 row가 insert 될 수 없어서 발생한 에러였습니다.
이를 해결하기 위해 저는 우선 Target을 Nullable 조건으로 변경하여 DMS 작업을 완료하고 추 후에 다시 Not Null 제약조건을 걸어주는 방식으로 해결했습니다.

글을 마치며..

DMS 덕분에 목표했던 RDS 통합 작업을 성공적으로 마무리하며 많은 클러스터 유지 비용을 절약하면서 활용 효율성을 높일 수 있었고, DMS의 기능을 활용해서 테이블의 크기가 너무 커서 그동안 미뤄왔던 컬럼 수정, Drop 등의 Alter 작업 또한 잘 마무리할 수 있었습니다. 관련하여 많은 도움을 주고 같이 고생해주신 뱅크샐러드 가족분들, AWS 관계자분들 모두에게 감사함을 전달드립니다.

DMS가 갖고 있는 가장 기초적인 사용법을 소개해드렸는데요. DMS가 갖고 있는 모든 것을 소개해드리기엔 너무 기능이 많아서 극히 일부 밖에 소개해드리지 못해 아쉽습니다. 그래도 이 글이 DMS에 입문하시는데 조금이라도 도움이 되었으면 좋겠습니다.
긴 글 읽어주셔서 감사합니다.

보다 빠르게 뱅크샐러드에 도달하는 방법 🚀

지원하기

Featured Posts

post preview
post preview
post preview
post preview
post preview

Related Posts