본문 바로가기

Technology

성장하는 서비스의 DB 성능 개선, 어떻게 할까?

안녕하세요. 스티비팀 서버 개발자 이학진 입니다. 저희는 최근 서비스에서 사용 중이던 MySQL DB를 RDS로 이관하는 작업을 진행하였습니다. 무엇 때문에 이관을 결정하게 되었는지와 어떻게 이관을 진행하였는지에 대해 글을 써보도록 하겠습니다.


배경

stibee.com은 작년 11월에 정식 오픈한 새내기 이메일마케팅 서비스 입니다. 사실 오픈 초기부터 얼마전까지만 해도 AWS EC2의 m4.large 인스턴스 하나로 운영되던 서비스였습니다.(사실 웹+API 서버 1대, 메일발송 서버 1대) 그리고 이 싱글 인스턴스에 무려 6개의 서버, MySQL 1개, Kafka™ 1개, Redis 1개가 돌고 있었습니다. 그럼에도 불구하고 CPU 사용률은 20%를 넘지 않았습니다.

 

하지만 최근 사용자도 점점 늘어났고, 네이버에서 메일 수신정책을 변경하면서 메일발송 서버에 대한 요청이 급증했습니다.

 

스티비에서 네이버로 대량메일을 발송했을 때 해당 메일의 본문 링크를 자동검사하는 것을 발견했는데요, 따라서 네이버로부터 비정상적으로 많은 요청이 들어오고 있었습니다. (어떤 기준으로 이런 검사를 하는 것인지 정확한 정책은 아직 모릅니다. 담당자분 이 글을 보신다면 연락주세요. 친하게 지냈으면 합니다 )

 

이내 곧 이메일을 마구 쏘아대던 스티비 서버는 네이버의 분노를 감당하지 못했습니다. (사실 네이버의 요청은 무난하게 처리하였으나, 요청 후 발생되는 이벤트 후처리에 DB를 과하게 사용하여…) 결국 CPU가 비명을 질렀습니다. 

 

일단 네이버의 IP를 달고오는 패킷을 drop함으로써 문제를 해결하였지만, 이로 인해 이제 "우리도 때가 왔다!'라는 판단을 하게 되었습니다.

 

그리하여 앞으로 대박날 스티비를 대비하여 스케일아웃이 가능하도록 각각의 서비스를 분리하는 작업을 진행하게 되었습니다. (사실 지금도 조금씩 떼어내고 있습니다. )


준비

앞서 RDS로 이관한다고 말씀드렸는데요. RDS는 AWS에서 제공하는 관계형 데이터베이스 서비스(링크)입니다. RDS로 이용 가능한 DB는 Amazon Aurora, PostgreSQL, MySQL, MariaDB, Oracle, Microsoft SQL Server 등이 있습니다.

 

이 중 저희는 아마존에서 MySQL과 호환되며 최대 5배까지 성능을 낼 수 있다는 Aurora DB를 사용하기로 결정하였습니다. (결정권자=1인=저)

 

AWS의 모든 서비스가 그러하듯이 RDS를 생성하는 방법은 매우 간단합니다. RDS 콘솔로 진입하신 뒤, 메인 대쉬보드의 “Launch a DB Instance” 를 클릭면 됩니다.



그러면 아래와 같이 사용 가능한 DB 목록들이 나타나는데 이중 기본값인 Aurora DB를 선택하고 “Select” 버튼을 누르시면 됩니다.


 


그리고 회사의 자금사정(?)에 따라 인스턴스 타입을 결정하시면 됩니다.


인스턴스 타입의 선택은 서비스의 특성에 따라 다르므로, 다양한 성능 테스트를 해보시기 바랍니다. 저희는 당시 EC2 인스턴스에서 DB가 같이 돌고 있었던 지라, EC2와 비슷한 사양이되 그 중 제일 저렴한 사양인 r3.large를 골랐습니다.


사양 선택에 있어 너무 고심할 필요는 없습니다. AWS에서는 클릭 한번으로 언제든 사양을 변경할 수 있으니까요~


사양을 변경하기 위해서는 인스턴스의 재부팅이 필요하므로 실서비스에 물리기 전에 결정하시길 권장합니다.



여기서 한 가지 주의할 점, Aurora DB는 기본적으로 클러스터로 구성됩니다. 클러스터란 쉽게 말해 여러 Aurora DB들을 하나로 묶어서 마치 하나의 DB처럼 사용 가능하게 해주는 역할을 합니다. (이로 인한 부하 분산, Fail-Over, 고가용성 등의 장점이 있습니다.)


앞서 저희는 하나의 인스턴스를 런칭 시키고자 하였으나, 설정페이지의 “Multi-AZ Deployment”의 기본설정을 보시면 “Create Replica In Difference Zone”으로 되어 있습니다. 이는 동일한 인스턴스를 다른 AZ에 추가적으로 둠으로써 고가용성을 보장하고 부하분산을 통해 성능향상을 해주겠다는 아마존 형님의 배려이십니다. (하지만 돈은 두배? ^^) 그래서 스티비는 “No”... (나중에 추가 할 수 있습니다.)


그 다음엔 DB Instance를 식별할 수 있게 이름을 부여하고 Root 역할을 할 Master Username과 패스워드를 설정해 줍니다. 그리고 “Next Step”


자! 이제 마지막 설정 페이지 입니다. 모든 옵션에 대해서 언급하고 넘어가기에는 설명이 너무 길어지니, 이것 정도는 알아야 한다는 옵션에 대해서만 간략히 설명하도록 하겠습니다.

  • VPC Security Group(s): DB Instance의 인/아웃바운드 트래픽 규칙을 정할 수 있습니다. 여기에서는 이미 생성된 Security Group을 사용할지, 아니면 새로 생성할 지를 정할 수 있으며, 선택된 Security Group의 세부 설정은 EC2 콘솔 페이지의 Security Group에서 할 수 있습니다.
  • Auto Minor Version Upgrade: Aurora DB의 마이너 패치가 나왔을 때, 자동으로 업그레이드를 할지 여부입니다. Yes로 하면 두세 달에 한번 업그레이드가 된다고 합니다. 주의하실 점은 업그레이드 시점에 DB는 사용 불가…(꺅!)
  • Maintenance Window: 자동으로 마이너 업그레이드를 한다면, 사용자의 이용이 가장 적은 시간대로 패치 시간을 적용할 수 있습니다.


모든 설정을 마치고 “Launch DB Instance”를 클릭하면 잠시 후 Aurora DB가 뙇!!하고 모습을 드러내시니, 이로써 이관에 대한 기본적인 준비를 마쳤습니다.

런칭된 오호라(?) DB

 

이관

AWS는 이관 방법에 대해 많은 정보를 문서로 제공합니다.

MySQL DB 인스턴스에서 데이터 가져오기 및 내보내기(링크)


운영 중인 DB를 중단하지 않고 RDS로 이관하는 방법도 있지만 이는 이관 후 데이터의 정합성을 검증해야하고 운영 DB의 부하발생 등의 이유로 해당 방법은 사용하지 않았습니다. 비록 점검 공지를 띄우고 이관 종료까지 서비스를 사용할 수 없지만 엔지니어에게 부담없는 방법을 택했습니다.

먼저 대략적인 이관 시간을 파악하고 스티비 활성 사용자가 가장 적은 시간대를 선택하여 이관을 진행하였습니다.


이관 절차는 아래와 같았습니다.

1. 자정(0시)을 기점으로 점검 페이지 교체

2. DB를 사용하는 모든 서비스 종료

3. MySQLdump를 이용한 DB백업

4. 백업된 DB 파일을 EC2로 복사

5. EC2에서 MySQL 명령어로 RDS 접속

6. source 명령어를 통한 백업파일 import

7. 명령어 실행 종료 후, 서비스 구동 및 점검 페이지 교체


이관을 진행함에 있어 EC2를 사용하는 이유는 다운타임을 최소화 하기 위해서 입니다. EC2와 RDS를 같은 VPC 내에 위치 시킴으로써 네트워크 속도가 가장 빠르기 때문입니다. (혹여나 그렇지 않을 경우 RDS가 속해 있는 VPC에 EC2를 생성해 주시기 바랍니다.)


아래는 3번부터 사용된 명령어 입니다.

DB 덤프 뜨기

$ mysqldump -u db_user -p --databases db_name --single-transaction --compress --order-by-primary > backup.sql


#필요에 따라서 압축

$ tar -zcvf backup.tar.gz backup.sql 


#EC2로 덤프 파일 복사

$ scp -r -i <key pair>.pem backup.sql.gz ec2-user@<EC2 DNS>:/<target_directory>/backup.sql.gz

#덤프 파일 복사는 편하신 방법으로 진행하시면 됩니다.

#MySQL 서버가 EC2에서 돌고 있었다면 생략


#EC2에서 RDS mysql 접속

$ mysql -h <host_name> -P 3306 -u <db_master_user> -p

#host_name은 RDS 콘솔 페이지에서 확인 할 수 있는 Cluster Endpoint 입니다.


#DB 및 User 생성

$ create database db_user;

$ grant all privileges on db_user.* to 'db_user'@'localhost' identified by 'db_password' with grant option;

$ grant all privileges on db_user.* to 'db_user'@'%' identified by 'db_password' with grant option;

$ flush privileges;


#이관 시작

$ source path/backup.sql



여기까지가 제가 진행한 이관절차의 끝 입니다. 그런데 과연 이렇게 간단하게 끝났을까요? 물론 아닙니다. 문제점 한두 개 정도는 나와줘야 제맛 아니겠습니까? (사실 악 악 하면서 심장이 쫄깃해졌습니다.)

 

문제 발생 및 해결

사실 지금까지 기술한 내용들은 AWS의 기술 문서에 매우 자세히 나와있어서 RDS를 사용하고자 하신 분들은 굳이 이 글을 읽지 않으셔도 무방하셨을 거 같습니다. 하지만 저 문서들을 보고 진행했음에도 불구하고 문제가 발생하더군요. 저희 서비스만의 문제였는지 모르겠지만 발생된 문제와 해결법을 공유해 보도록 하겠습니다.


1) Timezone

토종 한국 서비스라 Timezone이 한국 표준시였습니다. 그런데 기본 옵션으로 RDS 인스턴스를 생성하시면 국제 표준시로 설정이 되어 시간값이 다르게 나왔습니다.

DB Cluster Parameter Group의 time_zone 값을 Asia/Seoul로 변경한다.
기본 옵션값은 변경되지 않으므로 새로 생성한 뒤 변경하고, 인스턴스에 적용한다.


2) Import 중 ‘MySQL server has gone away’ 에러

MySQL의 옵션중 클라이언트가 한번에 전송 할 수 있는 패킷량의 제한이 있습니다. 이관 테이블 중 Long Text 타입의 컬럼이 있었고 해당 컬럼의 데이터를 옮기지 못해 발생된 에러 입니다. (이와 같은 이유로 아마존 기술 문서에 기술된 dump와 동시에 import하는 방식을 사용했을 때도 최대 1GB 밖에 전송할 수 없었습니다.)

DB Parameter Group의 max_allowed_packet의 값을 늘린다.
기본 옵션값은 변경되지 않으므로 새로 생성한 뒤 변경하고, 인스턴스에 적용한다.


3) 이모지 적용 안됨 

스티비는 메일 제목과 본문에 이모지 사용을 적극 권장하고 있습니다. 하지만 이관 후 작성된 메일의 이모지가 깨지는 현상이 발생 되었습니다. 이미 아시는 분들은 딱하고 감이 오시겠지만 네, CharacterSet 문제입니다. 그리고 이모지는 utf8mb4에서 지원됩니다.

DB Cluster Parameter Group의 character 옵션들을 바꾼다.
character_set_client = utf8mb4
character_set_connection = utf8mb4
character_set_database = utf8mb4
character_set_filesystem = utf8mb4
character_set_results = utf8mb4
character_set_server = utf8mb4
collation_connection = utf8mb4_unicode_ci


여기까지 스티비의 이관기를 마칩니다. 감사합니다.