2014년 2월 12일 수요일

서비스 점검 없이 mysql의 Table Schema 변경하기

Overview

카카오에서는 SNS 서비스 특성상 점검을 가질 수 없어야 함에도 불구하고 서비스 초기에 현재 사용 중인 Mysql DB의 경우 상용DB(Oracle, MySql 등)과 달리 Schema를 online중에 추가 및 변경이 불가능하기 때문에  Percona에서 제공하는 툴을 검토하게 되었으며 현재 트래픽이 작은 시간을 이용하여 서비스 운영 중에 스키마를 변경하고 있습니다.

Concept

Online Schema Change는 트리거를 활용하여 데이터를 복제하는 구조입니다.
먼저 변경할 스키마가 적용된 임시 테이블(TableA_new)을 생성하고, 트리거로 변경 내용을 "Replace Into" 구문으로 실시간으로 적용합니다. 
그리고 지정한 Chunk Size씩 PK 혹은 Unique Key 기준으로 데이터를 끊어서, 원본 테이블(현재 서비스 중인) 데이터를 일괄 신규 테이블(TableA_new)로 복사합니다.
데이터 복사가 끝나면 기존 테이블과 신규 테이블 이름을 변경하여 최종적으로 온라인 스키마 변경이 완료됩니다.

Installation
MySQL 설치 표준대로 DB를 설치하면 기본적으로 함께 설치되나, 그렇지 않은 경우에는 하단과 같이 별도로 유틸리티를 설치합니다.
rpm -ivh MariaDB-5.5.24-centos5-x86_64-compat.rpm
rpm -ivh perl-DBI-1.52-2.el5.x86_64.rpm
rpm -ivh perl-DBD-MySQL-4.017-1.x86_64.rpm
rpm -ivh perl-TermReadKey-2.30-3.el5.rf.x86_64.rpm
rpm -ivh percona-toolkit-2.1.3-2.noarch.rpm
rpm -ivh perl-TermReadKey-2.30-3.el5.rf.x86_64.rpm
유틸리티 설치가 정상적으로 완료되면, 일부 소스 수정이 필요합니다. 지나치게 많은 데이터를 한꺼번에 수정함에 따라 발생하는 DB부하를 사전에 방지하기 위해, 각 Bucket Data 복제 시마다 일시적으로 Sleep을 주는 부분입니다.
vi /usr/bin/pt-online-schema-change
 ## 5838 라인 : usleep 추가
use Time::HiRes qw(time sleep usleep);
   
## 7636 라인 : usleep(50000); 추가
sub exec_nibble {
    ## Changed by toto
    ## Sleep micro seconds : 1 second = 1000000 micro second.
    usleep(50000);

Usage

아래와 같이 변경할 Alter 구문과 타겟 데이터베이스명, 타겟 테이블명을 넣고 실행을 합니다.
pt-online-schema-change --alter "변경할 Alter 정보" D=데이터베이스,t=테이블명 \
--no-drop-old-table \
--no-drop-new-table \
--chunk-size=500 \
--chunk-size-limit=600 \
--defaults-file=/etc/my.cnf \
--host=127.0.0.1 \
--port=3306 \
--user=root \
--ask-pass \
--progress=time,30 \
--max-load="Threads_running=100" \
--critical-load="Threads_running=1000" \
--chunk-index=PRIMARY \
--retries=20 \
--charset=UTF8 \
--execute
다음은 예제입니다. --alter 파라메터에는 실제 스키마 적용할 부분만 아래와 같이 넣습니다.
pt-online-schema-change --alter "add key ix_empid(emp)" D=HR,t=emp \
--no-drop-old-table \
--no-drop-new-table \
--chunk-size=500 \
--chunk-size-limit=600 \
--defaults-file=/etc/my.cnf \
--host=127.0.0.1 \
--port=3306 \
--user=root \
--ask-pass \
--progress=time,30 \
--max-load="Threads_running=100" \
--critical-load="Threads_running=1000" \
--chunk-index=PRIMARY \
--retries=20 \
--charset=UTF8 \
--execute
반드시 --charset=UTF8 옵션을 추가하도록 합니다. Perl에서 캐릭터셋 지정 없이 DBI를 통하여 DB 연결 시 Latin1을 기본 캐릭터셋으로 접근하게 됩니다.
스키마에 영문만 포함된 경우라면 큰 문제가 없겠지만, 코멘트에 영문 외의 문자가 오게되면 코멘트 자체가 깨질 수 있습니다. 또한 테이블 칼럼 타입이 enum이고, 이에 대한 속성이 한글인 경우 문제가 발생할 수 있습니다.
--charset 파라메터를 주면 하단과 같이 "SET NAMES" 명령어로 원하는 캐릭터셋을 명시적으로 지정합니다.
if ( my ($charset) = $cxn_string =~ m/charset=([\w]+)/ ) {
$sql = qq{/*!40101 SET NAMES "$charset"*/};
PTDEBUG && _d($dbh, ':', $sql);
eval { $dbh->do($sql) };
if ( $EVAL_ERROR ) {
die "Error setting NAMES to $charset: $EVAL_ERROR";
}
PTDEBUG && _d('Enabling charset for STDOUT');
if ( $charset eq 'utf8' ) {
binmode(STDOUT, ':utf8')
or die "Can't binmode(STDOUT, ':utf8'): $OS_ERROR";
}
else {
binmode(STDOUT) or die "Can't binmode(STDOUT): $OS_ERROR";
}
}

Attention

Online Schema Change 툴을 사용하여 서비스 운영 도중 스키마 변경이 가능하나, 변경 도중 쿼리량이 급증하여 서비스에 지장이 생길수 있기 때문에서비스가 활성화된 상태에서는 되도록이면 수행하지 않도록 합니다. 
현재는 Binary Log Format이 Statement이기 때문에, Binlog 파일 사이즈가 급격하게 늘어나지 않지만, Row 포멧으로 지정된 경우에는 로그 사이즈가 급격하게 늘어날 수 있습니다.
대용량 테이블 변경 시에는 DB 상태 뿐만 아니라 Binlog 파일 사이즈 또한 모니터링이 필요합니다.
또, 1500이상의 DML 유입 상태에 툴을 실행하기 되면 Dead lock 이슈가 발생할 가능성이 높아 이점 유의해야 합니다.