MySQL lossless semi-sync replication
MySQL은 ORACLE 과는 HighAvailibility를 충족시키는 방법이 다릅니다.
하나의 스토리지에 데이터를 이중화,삼중화해서 저장하고 이를 여러 인스턴스가 공유하는 ORACLE의 RAC와는 달리
MySQL은 Master-Slave의 개념으로 Master의 데이터를 Slave 서버로 복제하고 MHA나 MMM 같은 솔루션을 통해 Master가 down 되었을 때 실시간으로 복제해둔 Slave를 Master로 승격시키는 방식입니다.
이 방식에는 한가지 문제점이 있는데요.
바로 Slave가 Master의 데이터를 모두 복제하지 못한채로 Master로 승격이 되는 경우입니다.
이 문제점을 해결하기 위해서는 단순하게는 Sync 방식으로 복제를 해오면 되겠지만 Slave에도 반영이 되어야 Master의 트랜잭션도 처리가 된다는 점에서 성능 이슈가 발생할 수 있습니다.
그래서 MySQL은 5.5버전부터 Semi-sync라는 방식을 지원하고 5.7버전에는 lossless semi-sync라는 거창한 이름의 복제방식이 나왔는데요.
이번 글에서는 이전의 semi-sync와 lossless semi-sync방식의 차이점에 대해 알아보겠습니다.
기본 설정
- sync_binlog = 1 => binlog commit 단계에서 바로 file로 Fsync 처리
- innodb_flush_log_at_trx_commit = 1 =>engine commit 단계에서 redo log file로 Flush 처리
- innodb_support_xa = 1 => 트랜잭션 commit 이 engine prepare - binlog commit - engine commit 으로 2PC commit 으로 수행되게 하여 binlog 와 redo log의 sync를 manage함
semi-sync 5.7 이전 (AFTER_COMMIT)
- User transaction commit
- Engine prepare
- Binlog flush
- Binlog commit
- Engine commit
- Binlog dump thread send event with ACK Request
- semi-sync wait (AFTER_COMMIT)
- User Commit OK
문제점
- 7번 Slave가 data를 받았다는 응답을 기다리는 도중 Master가 down 된다면?
- Master에서는 이미 Engine commit 까지 된 상황이기 때문에 새로 승격될 Slave에는 데이터가 없지만 Old Master에는 데이터가 존재하여 정합성이 깨짐
- 5번 engine commit 이 된 시점부터 다른 세션에서는 변경된 데이터를 읽을 수 있음 SLAVE로의 복제가 실패하면 이 트랜잭션은 Master에서 수동으로 rollback 처리해야함
lossless semi-sync 5.7 이후 (AFTER_SYNC)
- User transaction commit
- Engine prepare
- Binlog flush
- Binlog commit
- Binlog dump thread send event with ACK Request
- loss-less semisync wait (AFTER_SYNC)
- Engine commit
- User Commit OK
- 6번 Slave의 응답을 기다리는 도중 Master가 down 되어도 Master에서 engine commit이 처리되지 않았으므로 유실되는 데이터는 없음
5.7 이전 버전(AFTER_COMMIT) 데이터 유실 시나리오 테스트
- AFTER_COMMIT 확인
mysql> show global variables like 'rpl_semi_sync_master_wait_point';
+---------------------------------+--------------+
| Variable_name | Value |
+---------------------------------+--------------+
| rpl_semi_sync_master_wait_point | AFTER_COMMIT |
+---------------------------------+--------------+
1 row in set (0.01 sec)
- insert 작업 전 binary log 파일 사이즈 확인
### master
-rw-r----- 1 root root 195 Mar 15 11:37 master-bin.000035
### slave
-rw-r----- 1 root root 315 Mar 15 11:37 slave-relay.000021
- insert 수행
./sysbench --mysql-user=sbtest --mysql-password='qhdks123' --mysql-db=sbtest --mysql-host=localhost --mysql-port=3306 --mysql-socket=/engn001/mysql/data/mysql.sock --threads=10 --time=300 --report-interval=10 /engn001/sysbench-master/share/sysbench/oltp_insert.lua run > sysbench.out &
- 수행 도중 Master kill
[root@8429d3d8f01b data]# kill -9 298
[root@8429d3d8f01b data]#
[1]+ Killed /engn001/mysql/bin/mysqld --defaults-file=/engn001/mysql/my.cnf
- insert 작업 후 binary log 파일 사이즈 확인
### master
-rw-r----- 1 root root 3573155 Mar 15 11:38 master-bin.000035
### slave
-rw-r----- 1 root root 3572728 Mar 15 11:38 slave-relay.000021
=> 맨 처음 120bytes 이상으로 binary log / relay log 간 사이즈 차이 확인
- 데이터 확인
### master
mysql> select max(id) from sbtest1;
+---------+
| max(id) |
+---------+
| 13000 |
+---------+
1 row in set (0.00 sec)
### slave
mysql> select max(id) from sbtest1;
+---------+
| max(id) |
+---------+
| 12999 |
+---------+
1 row in set (0.00 sec)
=> 데이터 유실 발생
5.7 이후 (AFTER_SYNC) 테스트
- AFTER_SYNC 확인
mysql> show global variables like 'rpl_semi_sync_master_wait_point';
+---------------------------------+--------------+
| Variable_name | Value |
+---------------------------------+--------------+
| rpl_semi_sync_master_wait_point | AFTER_SYNC |
+---------------------------------+--------------+
1 row in set (0.01 sec)
- insert 작업 전 binary log 파일 사이즈 확인
### master
-rw-r----- 1 root root 195 Mar 15 11:26 master-bin.000033
### slave
-rw-r----- 1 root root 315 Mar 15 11:26 slave-relay.000017
- insert 수행
./sysbench --mysql-user=sbtest --mysql-password='qhdks123' --mysql-db=sbtest --mysql-host=localhost --mysql-port=3306 --mysql-socket=/engn001/mysql/data/mysql.sock --tables=5 --threads=10 --time=300 --report-interval=10 /engn001/sysbench-master/share/sysbench/oltp_insert.lua run > sysbench.out &
- 수행 도중 Master kill
[root@8429d3d8f01b data]# kill -9 296
[root@8429d3d8f01b data]#
[1]+ Killed /engn001/mysql/bin/mysqld --defaults-file=/engn001/mysql/my.cnf
- insert 작업 후 binary log 파일 사이즈 확인
### master
-rw-r----- 1 root root 3515364 Mar 15 11:38 master-bin.000033
### slave
-rw-r----- 1 root root 3515484 Mar 15 11:38 slave-relay.000017
=> 초기 binary / relay log file 120bytes 만큼만 차이남
- 데이터 확인
### master
mysql> select max(id) from sbtest1;
+---------+
| max(id) |
+---------+
| 10840 |
+---------+
1 row in set (0.00 sec)
### slave
mysql> select max(id) from sbtest1;
+---------+
| max(id) |
+---------+
| 10840 |
+---------+
1 row in set (0.00 sec)
결론
AFTER_COMMIT 방식을 사용하는 5.7 이전의 semi-sync는 Slave로부터 ACK를 받기도 전에 engine commit을 처리하기 때문에 도중에 Master가 crash 되면 Slave data 유실 되는 케이스가 종종 발생합니다.
이를 AFTER_SYNC 방식의 lossless semi-sync 로 변경하면 Slave로 부터 ACK를 받은 후 engine commit이 발생하기 때문에 Slave의 data 유실이 발생하지 않습니다.