지난 DRBD를 이용한 MySQL HA 구성 관련 글과 AWS Aurora 스토리지 관련 글에서 MySQL 리플리케이션의 경우 비동기 복제 방식이기 때문에 데이터의 일관성을 보장하지는 않는다는 내용을 많이 언급했습니다. 하지만 MySQL 5.7 버전에 추가된 Loss-Less Semi-sync 리플리케이션과 HA 도구인 MHA(Master High Availability)를 함께 사용하는 경우 동기식 복제와 거의 유사한 데이터 일관성 보장이 가능합니다. MHA 구성 방법과 장애조치 테스트 내용 공유를 위해 글을 작성하였습니다.
MySQL Semi-sync 리플리케이션
MHA를 구성하기 전에 먼저 MySQL Semi-sync 리플리케이션에 대해 알아야할 것 같습니다.
1) 기본 MySQL 리플리케이션
MySQL 리플리케이션은 바이너리 로그 전송을 통한 비동기식 복제이기 때문에 마스터 서버에서 commit 된 쿼리가 슬레이브에서 정상적으로 수행되었는지 확인을 하지 않습니다. 때문에 쓰기 속도는 동기식 복제에 비해 빠르나, 슬레이브로 해당 쿼리가 정상적으로 commit 되었는지 보장이 되지 않습니다.
마스터(쿼리 commit) -> 마스터(바이너리 로그 전송, 사용자에게 쿼리 결과 출력) -> 슬레이브(릴레이 로그 수신) -> 슬레이브(릴레이 로그를 통해 쿼리 commit)
2) Semi-sync 리플리케이션(AFTER_COMMIT)
Semi-sync 리플리케이션은 mysql 5.5 버전 이상에서 사용가능한 플러그인이며, 기존 리플리케이션과 다르게 마스터에서 쿼리를 commit 후 바이너리 로그를 슬레이브로 전송 시 마스터는 슬레이브가 해당 릴레이 로그(리플리케이션을 통해 전달받은 로그)를 받았다는 것을 Ack 을 회신함으로써 확인합니다.
마스터(쿼리 commit) -> 마스터(바이너리 로그 전송, Ack 수신 대기) -> 슬레이브(릴레이 로그 수신, 마스터에게 Ack 전송) -> 마스터(Ack 확인, 사용자에게 쿼리 결과 출력)
위의 방식을 AFTER_COMMIT(마스터에서 커밋 후 슬레이브로 로그 전송)이라고 합니다. 다만, AFTER_COMMIT 방식의 경우 마스터가 쿼리 commit 후 슬레이브로 릴레이 로그를 전송하기 전 타이밍에 장애가 발생한다면, 데이터의 일관성을 보장하기 힘든 단점이 있습니다.
마스터(쿼리 commit) -> 마스터(바이너리 로그 전송, Ack 수신 대기) ---(마스터 장애 발생)---> 슬레이브(릴레이 로그 수신 X)
-> 마스터에는 데이터가 업데이트 되었으나, 슬레이브에는 해당 데이터가 없음 -> 데이터 불일치 발생
3) Semi-sync 리플리케이션(AFTER_SYNC)
여기서 한 단계 발전한 방식이 MySQL 5.7 버전 이상부터 사용 가능한 Loss-Less 리플리케이션입니다. 동일하게 Semi-sync 플러그인을 사용하되, rpl_semi_sync_master_wait_point"이라는 옵션 값을 통해 설정 가능합니다. 이 방식은 AFTER_COMMIT 방식 중 마스터에서 쿼리가 commit 되는 타이밍이 다른 방식으로, 마스터는 슬레이브로부터 릴레이 로그를 정상적으로 수신했음을 Ack을 통해 확인한 이후 commit을 수행합니다.
마스터(쿼리 수행, commit은 X) -> 마스터(바이너리 로그 전송, Ack 수신 대기) -> 슬레이브(릴레이 로그 수신, 마스터에게 Ack 전송) -> 마스터(Ack 확인, 쿼리 commit) -> 마스터(사용자에게 쿼리 결과 출력)
위의 방식을 AFTER_SYNC(슬레이브에서 릴레이로그 수신을 확인하고 commit)이라고 합니다. AFTER_SYNC 방식에서는 슬레이브가 릴레이 로그를 슬레이브 DB엔진에 commit 하는 것을 보장하지는 않지만, 적어도 릴레이 로그를 정상적으로 수신했다는 것은 보장할 수 있습니다.
또한 위의 AFTER_COMMIT 방식과는 다르게 슬레이브에서 릴레이 로그를 수신하지 않으면 commit이 발생하지 않기 때문에 장애 발생 시에도 동일한 데이터를 유지할 수 있습니다.(슬레이브 DB엔진에 commit은 보장 X)
마스터(쿼리 수행, commit은 X) -> 마스터(바이너리 로그 전송, Ack 수신 대기) ---(마스터 장애 발생)---> 슬레이브(릴레이 로그 수신 X)
-> 마스터/슬레이브 모두 commit을 하지 않았기 때문에 일관성 유지
하지만 Semi-sync 방식 또한 동기식 복제와 유사하게 슬레이브로부터의 응답이 지연되는 경우 쓰기 속도가 저하된다는 단점이 발생합니다. 때문에 Semi-sync 방식은 슬레이브가 다수일 때, 1개의 슬레이브로부터 정상적인 Ack 응답이 발생하면 트랜잭션을 완료처리합니다. 즉, 일부 슬레이브의 네트워크 지연으로 지연시간이 발생하는 경우에도 빠른 응답을 하는 슬레이브가 1개라도 있으면 쓰기 지연시간은 줄어듭니다. 이것은 곧 장애 발생 시 최소 1개의 슬레이브는 최신의 릴레이 로그를 수신했음을 담보하는 근거가 됩니다.
MHA(Master High Availability)
MHA는 이러한 Loss-Less 리플리케이션의 장점을 활용한 MySQL HA 도구로,
perl 언어를 기반으로 한 스크립트입니다.
MHA에서는 노드와 매니저가 존재합니다. 리플리케이션으로 연결된 마스터와 다수 슬레이브는 노드라고 부르며, 매니저는 노드의 헬스체크와 장애조치를 수행합니다. 매니저는 별도의 서버로 구성할수도, 슬레이브 노드 중 하나의 서버에 설치할 수도 있습니다.
MHA는 장애조치 시 슬레이브 노드 중 가장 최신의 릴레이 로그를 가지고 있는 슬레이브를 선정하고, 마스터 서버가 접속 가능한 상태(MySQL만 죽은 상태)라면 마스터 서버의 바이너리 로그를 가져와 로그 간 비교를 수행하여 로그를 복구하며, 가장 최신의 로그를 가진 슬레이브를 마스터 역할로 승계시키고 누락된 로그를 복구합니다. 이를 통해 비동기 복제이지만 동기 복제와 같이 데이터의 일관성을 보장합니다.
마스터-마스터 구조이며 별도의 agent를 실행하는 MMM과는 다르게 MHA는 agent를 실행하지 않으며, 마스터-슬레이브로 구성됩니다.
MHA 설치 환경
테스트 환경은 아래와 같이 매니저 서버를 별도로 구성하였으며, 평상시에는 마스터 서버가 MySQL 서비스용 아이피를 사용하고, 장애 발생 시에만 슬레이브 서버가 서비스용 아이피를 사용할 수 있도록 하였습니다. MySQL은 5.7 버전으로 마스터/슬레이브 서버에 이미 설치된 것을 가정하여 진행합니다.
# 마스터 서버(MySQL 5.7)
enp0s8 -> 상태체크 및 SSH 접속용, 192.168.2.30
enp0s9 -> mysql 서비스용, 192.168.2.100
# 슬레이브 서버(MySQL 5.7)
enp0s8 -> 상태체크 및 SSH 접속용, 192.168.2.40
enp0s9(down) -> mysql 서비스용, 192.168.2.100
# 매니저 서버
enp0s8 -> 단일 인터페이스
MHA 설치
# 마스터/슬레이브 서버
yum -y install perl-CPAN perl-DBD-MySQL perl-Module-Install git
git clone https://github.com/yoshinorim/mha4mysql-node
cd mha4mysql-node
perl Makefile.PL
make && make install
# 매니저 서버
yum -y install perl-CPAN perl-DBD-MySQL perl-Module-Install perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager git
git clone https://github.com/yoshinorim/mha4mysql-node
cd mha4mysql-node
perl Makefile.PL
make && make install
cd ~
git clone https://github.com/yoshinorim/mha4mysql-manager
cd mha4mysql-manager
perl Makefile.PL
make && make install
MHA 설정
설정 항목에 따라 노드(마스터/슬레이브 서버)에서 설정해야하는 내용과 매니저 서버에서 설정해야하는 내용이 섞여 있어 각 항목마다 어떤 서버에서 설정해야하는 내용인지 기재하였습니다.
# mha 계정 생성(마스터/슬레이브 모두)
grant all privileges on *.* to 'mha'@'%' identified by 'mhapassword';
# 매니저 서버 기본 설정 파일 작성(매니저 서버만)
mkdir /var/log/masterha
vim /etc/masterha-default.cnf
[server default]
user=mha
password=mhapassword # 위의 단계에서 생성한 계정과 비밀번호입니다.
manager_workdir=/var/log/masterha
manager_log=/var/log/masterha/MHA.log
remote_workdir=/var/log/masterha
master_ip_failover_script=/root/mha4mysql-manager/samples/scripts/master_ip_failover
master_ip_online_change_script=/root/mha4mysql-manager/samples/scripts/master_ip_online_change
[server1]
hostname=192.168.2.30 # 마스터 서버의 정보
master_binlog_dir=/var/lib/mysql
candidate_master=1
[server2]
hostname=192.168.2.40 # 슬레이브 서버의 정보
master_binlog_dir=/var/lib/mysql
candidate_master=1
# 서비스 아이피 인계하기 위한 스크립트 작성(매니저 서버)
vim /root/mha4mysql-manager/samples/scripts/change_virtual_ip_master_to_slave.sh
ssh root@192.168.2.40 /sbin/ifup enp0s9
ssh root@192.168.2.30 /sbin/ifdown enp0s9
저장 후 닫기
vim /root/mha4mysql-manager/samples/scripts/change_virtual_ip_slave_to_master.sh
ssh root@192.168.2.40 /sbin/ifdown enp0s9
ssh root@192.168.2.30 /sbin/ifup enp0s9
저장 후 닫기
chmod 755 /root/mha4mysql-manager/samples/scripts/change_virtual_ip_master_to_slave.sh
chmod 755 /root/mha4mysql-manager/samples/scripts/change_virtual_ip_slave_to_master.sh
vim /root/mha4mysql-manager/samples/scripts/master_ip_failover
86번 라인 "## Creating an app user on the new master" 라인 밑에 있는 4줄 주석
92번 라인 "## Update master ip on the catalog database, etc" 밑에 "FIXME_xxx;" 주석처리하고 아래 내용 붙여넣기
## Update master ip on the catalog database, etc -> 기존 내용
if($new_master_ip eq "192.168.2.30"){
system("/bin/sh /root/mha4mysql-manager/samples/scripts/change_virtual_ip_slave_to_master.sh");
}
elsif($new_master_ip eq "192.168.2.40"){
system("/bin/sh /root/mha4mysql-manager/samples/scripts/change_virtual_ip_master_to_slave.sh");
}
$exit_code = 0; -> 기존 내용
저장 후 닫기
vim /root/mha4mysql-manager/samples/scripts/master_ip_online_change
152번 라인 "FIXME_xxx_drop_app_user($orig_master_handler);" 주석
244번 라인 "## Creating an app user on the new master" 밑에 있는 4줄 주석
250번 라인 "## Update master ip on the catalog database, etc" 밑에 아래 내용 붙여 넣기
if($new_master_ip eq "192.168.2.30"){
system("/bin/sh /root/mha4mysql-manager/samples/scripts/change_virtual_ip_slave_to_master.sh");
}
elsif($new_master_ip eq "192.168.2.40"){
system("/bin/sh /root/mha4mysql-manager/samples/scripts/change_virtual_ip_master_to_slave.sh");
}
저장 후 닫기
SSH 접속 설정
노드는 서로 접속이 가능해야하며, 매니저는 각각으로 접속 가능해야합니다.
# 매니저
ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N ""
ssh-copy-id root@192.168.2.30
ssh-copy-id root@192.168.2.40
# 마스터
ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N ""
ssh-copy-id root@192.168.2.40
# 슬레이브
ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N ""
ssh-copy-id root@192.168.2.30
# 확인(매니저 서버에서)
masterha_check_ssh --conf=/etc/masterha-default.cnf
확인을 위한 명령어를 매니저 서버에서 수행하면 아래와 같이 결과가 출력됩니다. 에러가 발생하는 경우 SSH 접속 설정을 다시 확인해봅니다.
MySQL Semi-sync 플러그인 설치
마스터/슬레이브 서버의 역할은 장애 조치 수행 시 맞바뀔 수 있기 때문에 마스터 플러그인과 슬레이브 플러그인을 모든 마스터/슬레이브 서버에 설치합니다.
# 플러그인 확인
show plugins;
# 마스터 플러그인 설치
install plugin rpl_semi_sync_master soname 'semisync_master.so';
# 마스터 플러그인 활성화
set global rpl_semi_sync_master_enabled=1;
# 슬레이브 플러그인 설치
install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
# 슬레이브 플러그인 활성화
set global rpl_semi_sync_slave_enabled=1;
# (이미 리플리케이션이 진행 중인 경우) 스레드 재시작 필요
stop slave io_thread;
start slave io_thread;
show slave status\G
# 설정값 확인
show global variables like '%semi_sync%';
# 상태 확인
show status like '%semi_sync%';
설정값 및 상태 확인의 결과는 아래와 같이 보여집니다.
MySQL 리플리케이션 설정
# 마스터 서버
vim /etc/my.cnf
log-bin=/var/lib/mysql/bin.log
binlog_cache_size=2M
max_binlog_size=512M
expire_logs_days=7
server-id=1
저장 후 닫기
MySQL 데몬 재시작 후 mysql 접속
grant replication slave on *.* to 'rep'@'192.168.2.%' identified by 'password';
(만약 패스워드 보안정책 때문에 에러가 난다면, 비밀번호를 복잡하게 변경하거나 "SET GLOBAL validate_password_policy=LOW;" 수행하여 보안정책 레벨 낮추고 다시 실행)
flush tables with read lock;
show master status;
bash쉘에서
mysqldump -uroot -p --all-databases > dump.sql
다시 mysql 접속하여
unlock tables;
# 슬레이브 서버
vim /etc/my.cnf
log-bin=/var/lib/mysql/bin.log
binlog_cache_size=2M
max_binlog_size=512M
expire_logs_days=7
server-id=2
저장 후 닫기
MySQL 데몬 재시작 후 mysql 접속
grant replication slave on *.* to 'rep'@'192.168.2.%' identified by 'password';
(마스터/슬레이브 관계는 장애조치 시 바뀔 수 있어 슬레이브에서도 허용 설정해줍니다)
change master to master_host='192.168.2.30', master_user='rep', master_password='password', master_port=3306, master_log_file='bin.xxxxxx', master_log_pos=xxxxxx;
(여기서 master_log_file의 값과 master_log_pos 값은 마스터 서버에서 show master status; 명령을 통해 확인한 값으로 변경합니다)
start slave;
show slave status\G
# 리플리케이션 확인(매니저 서버에서 수행)
masterha_check_repl --conf=/etc/masterha-default.cnf
리플리케이션 확인 명령어 수행 시 아래와 같이 출력이 나타납니다.
(중략)
MHA 매니저 실행
nohup masterha_manager --conf=/etc/masterha-default.cnf &
# 실행 확인
ps -ef |grep masterha
masterha_check_status --conf=/etc/masterha-default.cnf
tail -f /var/log/masterha/MHA.log
장애조치 테스트
이제 모든 구성은 완료되었으니, "tail -f /var/log/masterha/MHA.log" 명령어로 로그를 팔로우하며 마스터 서버 장애 발생 시 어떻게 장애조치가 이뤄지는지 확인합니다. 현재는 원격지에서 마스터 서버의 MySQL 서비스 아이피인 192.168.2.100 으로 정상적으로 접속되는 상태입니다.
마스터 서버의 MySQL 데몬을 다운시킵니다.
매니저 서버의 로그(/var/log/masterha/MHA.log)를 확인합니다. 로그상에서 마스터 서버가 다운되었음이 확인됩니다.
이후 MHA 매니저는 모니터링 스크립트를 종료하며, 자동으로 장애조치 단계로 넘어갑니다.
이후 출력되는 로그의 양이 많아 장애 조치의 단계 별 어떤 작업이 이뤄지는지 간단히 정리했습니다.
Phase 1: Configuration Check Phase -> 현재 살아있는 슬레이브 서버를 이용하여 마스터 서버가 죽었는지 다시 한번 체크합니다. 또한 현재 살아있는 슬레이브 서버를 체크합니다.
Phase 2: Dead Master Shutdown Phase -> (마스터 서버로 SSH 접속이 가능한 경우) 마스터의 MySQL로 접속이 불가하기 때문에 마스터로 SSH 접속하여 MySQL 데몬을 내립니다.
Phase 3: Master Recovery Phase -> 슬레이브 서버의 릴레이 로그의 POS(포지션)을 확인하여 가장 최신의 릴레이 로그를 가진 서버를 새 마스터로 선정하며,
기존 마스터로부터 가져온 바이너리 로그와 슬레이브 서버들의 릴레이 로그를 비교하여 최신의 데이터를 새 마스터에 적용합니다.
Phase 3.1: Getting Latest Slaves Phase
Phase 3.2: Saving Dead Master's Binlog Phase
Phase 3.3: Determining New Master Phase
Phase 3.4: New Master Diff Log Generation Phase
Phase 3.5: Master Log Apply Phase
Phase 4: Slaves Recovery Phase -> Phase 3의 작업과 동일하게 마스터 서버의 바이너리 로그와 슬레이브 서버의 릴레이 로그를 비교하여 데이터를 최신의 상태로 적용합니다.
Phase 4.1: Starting Parallel Slave Diff Log Generation Phase
Phase 4.2: Starting Parallel Slave Log Apply Phase
Phase 5: New master cleanup phase -> 새 마스터 서버의 slave 정보를 리셋합니다.
로그의 마지막으로 장애조치 리포트가 보여집니다. 마스터 서버의 MySQL 데몬 다운 후 장애 조치까지 이뤄진 소요시간은 약 26초입니다.
새 마스터로 선정된 슬레이브 서버의 네트워크 인터페이스 확인 시 아래와 같이 MySQL 서비스 아이피인 192.168.2.100이 정상적으로 올라왔습니다.
또한 원격지에서 접속 테스트 시 정상적으로 새로운 마스터 서버로 접속됩니다.
장애조치 완료 후 원복 작업(기존 마스터 서버 복구 및 마스터 인계)
이제 기존 마스터 서버가 복구된 경우를 가정하고 기존 마스터서버로 데이터 동기화 및 마스터 역할을 인계하는 작업을 진행합니다. 매니저 서버에서 로그를 확인하면 장애조치가 이뤄진 시점의 새 마스터 서버의 바이너리 로그 파일 및 POS(포지션) 정보를 포함한 슬레이브 설정 명령어가 확인됩니다.
# 매니저 서버에서 수행
grep "All other slaves should start replication from here" /var/log/masterha/MHA.log
# 출력 결과
Thu Sep 17 13:37:16 2020 - [info] All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='192.168.2.40',
MASTER_PORT=3306, MASTER_LOG_FILE='bin.000001', MASTER_LOG_POS=451, MASTER_USER='rep', MASTER_PASSWORD='xxx';
-> 여기서 "MASTER_PASSWORD='xxx';" 부분만 실제 리플리케이션 비밀번호로 치환
확인한 명령어를 이용하여 기존 마스터 서버(장애로 죽었다 살아난 서버)에서 새 마스터 서버(기존에 슬레이브 서버였던)로 리플리케이션을 설정합니다.
정리해보면 기존 마스터였던 서버는 현재 슬레이브 서버가 되었으며, 기존 슬레이브 서버였던 서버는 현재 마스터 서버로 리플리케이션 설정된 상태가 됩니다. 이제 매니저 서버에서 현재 슬레이브 서버(기존 마스터 서버)를 마스터 역할로 변경하는 작업을 수행합니다.
# 매니저 서버에서 수행
rm -f /var/log/masterha/masterha-default.failover.complete
masterha_master_switch --master_state=alive --conf=/etc/masterha-default.cnf
장애조치를 수행할 때와 유사하게 슬레이브 서버가 마스터 서버의 역할로 변경됩니다. 출력되는 결과는 아래와 같습니다.
(중략)
기존 마스터 서버가 마스터 역할로 변경 완료되었습니다. 출력 중 아래와 같이 슬레이브 서버에 설정할 리플리케이션 명령어 라인이 포함되어 있으니, 해당 라인을 복사하여 마스터 패스워드 부분만 올바른 패스워드로 수정하여 슬레이브 역할로 돌아간 슬레이브 서버에 적용하면 리플리케이션 설정 완료됩니다.
마지막으로 매니저 서버에서 다시 모니터링 스크립트를 실행하고, 상태를 확인하면 장애조치 원복도 완료됩니다.
# MHA 실행
nohup masterha_manager --conf=/etc/masterha-default.cnf &
# 상태 확인
masterha_check_status --conf=/etc/masterha-default.cnf
결론
MySQL MHA는 리플리케이션의 단점인 데이터의 일관성 문제를 보완한 Loss-Less Semi-sync 리플리케이션을 효과적으로 활용한 HA 도구입니다.
MHA가 동기식 복제만큼 완전한 데이터의 일관성을 보장하지는 않을 수 있으나, 쓰기 속도 측면에서 타협점을 찾는다면 MHA는 충분히 효과적인 방법이 될 수 있을 것으로 생각됩니다.
NHN에서 2018년 진행한 온라인 컨퍼런스의 자료를 참고하면 Toast RDS에 MHA와 DNS(CNAME을 이용한 연결 방식)을 적용하여 HA 구성을 하였다는 내용을 확인할 수 있습니다.
(https://www.slideshare.net/NHNFORWARD/mysql-nhn-forward-2018)
'MySQL' 카테고리의 다른 글
이미지를 DB에 BLOB 데이터타입으로 저장 (1) | 2023.03.12 |
---|