소스 서버에서 빌셍힌 이벤트(트랜잭션)은 바이너리 로그에 기록되고, 레플리카 서버는 주기적으로 바이너리 로그 덤프 쓰레드를 사용하여 이벤트를 요청하고, 전송받는다. 즉, 소스 서버에서 레플리카 서버로 데이터를 복제하는데에 소요 시간이 댜소 발생한다. 그럼 궁금증이 하나 생긴다. 과연 복제하는 동안에는 두 서버의 데이터가 다를텐데, 이로 인해 문제가 발생하지는 않을까?
비동기 복제 방식 (Asynchronous replication)
특징
위와 같은 문제점은 비동기 복제 방식에서 발생할 수 있다. 비동기 복제 방식이라면 레플리카로 이벤트가 잘 전달 되었는지, 실제로 이벤트 복제가 잘 이루어졌는지 알 수 없고, 보장하지도 않는다. 비동기 방식은 소스 서버에서 쓰기 연산이 수행된 후, 이벤트를 레플리카 서버에서 전송하기만 하고 자기는 곧 바로 커밋하는 셈이다. 즉, 소스 서버에서 발생한 트랜잭션은 레플리카에 동기화되었는지와 별개로 동작하고, 커밋된다.
MySQL 레플리케이션은 기본적으로 비동기 복제 방식으로 동작한다. 따라서 별다른 설정이 없다면 서버간의 데이터 누락, 정합성 문제가 발생할 수 있다. 그런데, 이렇게 소스 서버가 레플리카 서버에 이벤트 동기화 여부를 보장하지 않는다는 특징은 장점도 있고, 단점도 있다.
장점
어떻게 보면 장점이 되기도 한다. 비동기 처리의 특성상 성능이 더 빨라지게 된다. 소스 서버 입장에서는 각 트랜잭션 작업을 수행할 때 레플리카 서버로 전송하는 부분을 기다리지 않고 수행되기 때문에, 트랙잭션 처리 속도가 빨라진다. 또한 레플리카 서버에 문제가 발생하더라도 소스 서버가 영향을 받지 않고 독립적으로 동작할 수 있다는 점이다.
단점
소스 서버에 장애가 발생시 레플리카 서버로 이벤트가 제대로 전달되지 않을 수 있다. 즉, 복제 과정에서 데이터 누락이 발생할 수 있다. 만약 소스 서버에서 장애가 발생하여 FailOver를 위해 레플리카를 소스 서버로 승격시킨다면, 레플리카에서 누락된 데이터를 일일이 수작업으로 찾고 반영해줘야한다.
반동기 복제 방식 (Semi-synchronous replication)
비공기 복제 방식보다 데이터 동기화 문제를 더 견고하게 해결해주는 방식이다. 소스 서버의 이벤트가 레플리카 서버에 잘 전달된 것을 확인한 뒤에서야, 소스 서버의 트랜잭션을 마무리하는 방식이다. 즉, 소스 서버는 레플리카 서버가 소스 서버로부터 전달받은 이벤트를 릴레이 로그에 기록하고 응답(ACK)
을 보내면, 그때서야 트랜잭션을 커밋한다. 앞선 비동기 복제 방식과 달리 소스 서버가 트랜잭션을 커밋하고, 레플리카 서버에 복제하는 2개의 작업 모두가 한 묶음으로 같이 수행된다.
하지만, 이 방식은 이벤트가 레플리카 서버의 릴레이 로그에 기록되는 것 까지만 보장한다. 즉, 릴레이 로그의 내용이 SQL 쓰레드에 의해 레플리카 서버의 스토리지에 실제로 반영되는 것까지 확인하고 응답하지는 않는다. 다시 해석하자면, 동기화 대상을 "전송" 하는 것까지만 보장하고 실제로 스토리지에 "적용" 되는 것 까지는 보장하지 않는다.
이 방식 또한 어찌보면 당연하게 설계된 방식이다. 만약 각 레플리카 서버의 스토리지에 실제로 잘 적용되는 것 까지 모두 확인하고 응답하는 구조였다면, N개의 레플리카 서버를 배치한 환경이라면 하나의 SQL 문을 N+1번 실행하는 것과 같은 성능 저하를 부를 것이기 때문이다.
장점
복제 과정에서 데이터 정합성이 중요하고, 성능은 다소 떨어져도 되는 상황에 사용하기 적합하다. 레플리카 서버로부터 ACK 응답을 받은 뒤 트랜잭션을 커밋하므로 최소 하나의 이상의 레플리카 서버에는 복제가 성공했음을 보장할 수 있다.
단점
계속 설명했듯이, 비동기 방식에 비해 느리다. 반동기를 위한 네트워크 통신이 1번 더 발생하고, 레플리카 서버에 문제가 있어서 ACK 응답속도가 늦어질 경우 그만큼 트랜잭션 수행시간이 더 늦어진다. 이 떄문에 소스와 레플리카 서버가 물리적으로 가까이 위치한 경우에 사용하기 좋을 것이다.
AFTER_SYNC vs AFTER_COMMIT
반동기 복제 또한 2가지 종류로 나뉜다. 정확히 어떤 시점에 이벤트를 레플리카 서버로 전송하고 응답을 받느냐에 따라 AFTER_SYNC
, AFTER_COMMIT
으로 나뉜다.
-
AFTER_COMMIT
: 소스 서버의 이벤트(트랜잭션)를 바이너리 로그에 기록하고, 스토리지에 커밋해서 실제로 적용하는 것까지 모두 마친 뒤에 레플리카 서버로 이벤트를 전송하는 방식이다. 커밋 이후에 복제를 진행하기 때문에 AFTER COMMIT 반동기 복제라고 부른다. -
AFTER_SYNC
: 레플리카 서버의 릴레이 로그에 정상적으로 기록이 되었음을 확인받은 뒤에야 안전하게 스토리지에 커밋하는 방식이다. 이는 동기화 이후에 트랜잭션 커밋을 수행하기 때문에 AFTER SYNC 반동기 복제하고 부른다.
AFTER COMMIT
반동기 복제 방식을 지원하기 시작한 초기에, 디폴트로 실행되던 AFTER COMMMIT 방식은 특정 장애 상황이 발생했을 때 문제가 있었다. 만약 소스 서버가 트랜잭션을 커밋한 뒤, 레플리카 서버에 이벤트를 전송하는 도중에 장애가 터졌다고 가정해보자. 문제점은 아래와 같다.
-
(1)
장애가 터진 소스 서버를 대신하여 레플리카 서버를 새로운 소스 서버로 대체했을 때, 기존 소스 서버에는 정상적으로 반영되었던 데이터 변경이 새롭게 대체된 소스 서버에는 반영되지 않은, 의도하지 않은 롤백이 발생할 수 있다. -
(2)
팬텀리드(Phantom Read)
가 발생할 수 있다. 장애가 터진 소스 서버를 즉시 고쳐서 다시 운영 환경에 투입했을 경우, 새로운 소스 서버에는 반영되어 있지 않은 데이터 변경사항이 FailOver 되어 다시 투입된 소스 서버에는 반영되어 있는 문제가 발생할 수 있다.
흔치 않는 장애 발생 상황이긴 하지만, 팬텀 리드와 같은 데이터 정합성 문제가 일어날 여지가 있는 방식이 AFTER COMMIT 이다.
AFTER SYNC
위와 같은 AFTER COMMIT 의 데이터 정합성 문제를 해결하기 위해, MySQL 8.0 부터는 디폴트로 레플리카 서버의 릴레이 로그에 정상적으로 기록이 되었음을 확인받은 뒤에야 스토리지에 커밋하는 방식을 채택하게 되었다. 앞서 설명했듯이, 이는 동기화 이후에 트랜잭션 커밋을 수행하므로 AFTER SYNC 반동기 복제라고 한다.
AFTER SYNC 방식에서 앞선 장애 상황이 터졌을 때, 레플리카 서버에는 반영되지 않았지만 소스 서버에만 반영되는 일은 발생하지 않는다. 따라서 팬텀 리드가 발생하지 않는다.
어떤 동기화 복제 방식을 선택해야 할까?
데이터 정합성을 위해선 확실히 비동기 방식에 비해 반동기 방식이 유리해보인다. 하지만, 성능은 더 느릴 것이다. 과연 데이터 정합성 보장을 위해 성능을 포기하면서까지 AFTER SYNC 방식을 택하는 것이 좋을까?
사실상 데이터의 레플리케이션 작업은 생각보다 매우 짧은 시간에 이루어진다. 보통의 경우 약 200ms ~ 300ms 라는 짧은 시간안에 데이터를 복제해서, 데이터 불일치로 생기는 불편함을 느낄 일이 흔히 발생하지 않는다.
어떤 동기화 복제 방식을 택할지는 서비스 특성에 따라 다를 것이다. 만약 변경된 데이터를 정말 즉각적으로 읽어야하는 민감한 서비스라면, 아무래도 반동기 방식이 더 유리할 것이다. 상황에 따라 얼만큼을 성능을 고려하고, 데이터 정합성을 고려할지 생각해보자.
정리
- 비동기 복제 방식 : 트랜잭션 처리 속도가 빠르지만, 레플리케이션의 성공을 반드시 보장하지 않는다.
- 반동기 복제 방식 : 트랜잭션 처리 속도가 느리지만, 레플리카 서버로의 전송을 보장해준다.
참고
- https://velog.io/@backfox/무뇽이와-알아보는-대규모-데이터-관리-데이터베이스-복제하기리플리케이션-f4pota6h
- Real MySQL 8.0 - 백은빈, 이성욱