프로젝트를 진행하면서 고가용성과 성능향상을 위해 DB를 이중화하기로 하였고, 이를 Replication을 사용하기로 결정했습니다!
그렇담 이 Replication은 무엇인가?!
데이터베이스를 복제하는 행위
그렇담 왜 하나의 DB를 사용하면 되지만 여러개의 DB를 사용할까?
Case 1
DB의 이상으로 요청에대한 응답이 되지 않았을때
Case 2
요청 트래픽의 증가로 인한 트래픽의 부하 분산이 필요한 상황 일때
이때 스케일업을 통해 어느정도 보완이 가능하겠으나, 스케일업에는 분명 한계가 있습니다!
이러한 상황을 타개하기 위해서 DB Replication을 사용해야 합니다.
일단 원본 데이터를 가진 데이터베이스 서버를 소스서버 복제된 DB의 데이터를 가지고 있는 DB 서버를 레플리카 서버 라고 합니다.
소스 서버에서 변경이 일어나면 변경된 내용은 레플리카 서버에 동일하게 반영이 됩니다. 이런 구조를 소스-레플리카 구조라고 합니다!
그렇기 때문에 앞선 Case 1의 경우 레플리카 서버를 소스서버로 승격시켜 사용할 수 있습니다.
Case2의 경우는 소스 서버를 쓰기 전용 DB, 레플리카 서버를 읽기 전용 DB로 사용해 부하를 해결할 수 있습니다.
그렇담 이러한 레플리카에 데이터 복제는 어떻게 가능하게 하는지 알아보도록 합시다.
모든 변경사항이 로그 파일에 순서대로 기록되는 것을 의미합니다.
즉 소스서버에 변경이 발생되어 바이너리 로그 이벤트가 기록이 되면 이 이벤트는 레플리카 서버가 자신의 로컬 디스크에 저장한 이후 이벤트를 읽어 들여 레플리카DB 자기자신의 데이터 파일에 반영합니다.
좀더 자세히 알아볼까요?
일단 복제 쓰레드는 총 세 가지 쓰레드가 사용됩니다.
이벤트 순서 대로
1. 소스서버의 바이너리 로그에 이벤트가 발생되면 바이너리 로그 덤프 쓰레드가 이 이벤트를 읽어 레플리카 서버로 전송합니다.
2. 레플리카 서버의 I/O 쓰레드는 이 변경 이벤트를 자신의 로컬파일인 릴레이 로그에 저장합니다.
(이때 아직 자신의 DB엔 변경이 일어나지 않습니다.)
3. 레플리카 서버의 DB 변경을 위해 SQL 쓰레드가 변경 내용을 데이터 파일에 저장합니다.
이 방식에는 두 가지 방식이 있습니다.
즉 동일한 이벤트가 레플리카 서버에서도 동일한 파일명의
동일한 위치에 저장된다는 보장이 없습니다.
앞선 단점을 해결하기 위해 MySQL 5.6 버전 부터는 글로벌 트랜잭션 기반 복제를 기본 복제 방식으로 사용하고 있습니다.
그렇담 이 글로벌 트랜잭션 아이디는 무엇일까?
복제에 참여한 모든 데이터베이스들이 고유한 식별값을 가지며 이 값들은 모두 동일하기 때문에 동일한 이벤트에 대해서 동일한 글로벌 트랜잭션만 읽기만 하면 반영이 가능하게 됩니다.
INSERT INTO member VALUES(1,'TESTNAME')
이러한 SQL문이 일어나면 이 SQL문이 바이너리 로그에 그대로 저장되는 방식을 Statement 기반 방식이라고 합니다.
일관되지 않은 데이터가 저장될 위험이 있습니다!
앞선 Statement 기반의 단점을 보완하여 MySQL 5.7.7 버전 부터는 이 Row 기반 바이너리 로그 포맷을 기본으로 사용합니다.
이 방식은 변경 값 자체가 바이너리 로그에 그대로 저장되어 있는 형식을 말합니다.
그럼에도 불구하고 데이터를 일관되게 저장 하는 가장 안전한 방식 입니다.
이 Mixed 방식은 사용자가 커스텀하여 사용이 가능합니다.
기본적인 쿼리는 Statement 포맷으로 저장하되 비확정적 쿼리 라면 Row 포맷으로 저장하는 방식을 사용할 수 있습니다.
그렇다면 복제가 잘 일어났는지 어떻게 확인할까?
소스 서버가 레플리카 서버에서 변경 이벤트가 정상적으로 전달 됐는지 확인하지 않는다.
자세히 알아보자면.
- 데이터 변경 요청이 들어온다면 바이너리 로그의 이벤트를 먼저 작성한 뒤 바로 소스 서버 스토리지 엔진에 커밋을 하게 된다.
- 그 이후 변경 이벤트를 레플리카 서버로 전송합니다.
이 방식은 변경 이벤트를 레플리카 서버로부터 소스 서버로 확인 이벤트를 보내지 않습니다. 그렇기 때문에 성능은 빠를 수 있으나 동기화는 보장하지 않습니다.
앞선 비동기 복제의 단점을 보완하여 MySQL 5.5 버전 부터는 반동기 복제 방식을 사용합니다.
소스 서버는 레플리카 서버가 소스 서버로부터 전달 받은 변경 이벤트를 릴레이 로그에 기록 후 응답을 보내면 그때 트랜잭션을 완전히 커밋한다.
자세히 알아보자면
- 데이터 변경 요청이 소스 서버로 들어오면 바로 이벤트를 바이너리 로그에 기록후 레플리카 서버로 전송합니다.
- 레플리카 서버는 이 변경 이벤트를 잘 받았다는 응답을 보내게 되며, 응답이 오고 난 이후 소스 서버는 변경 내역을 스토리지 엔진에 커밋합니다.
이때 레플리카 서버가 보내는 응답은 변경 이벤트를 잘 받았다는 응답일뿐 이벤트가 레플리카 서버에 적용되었다는 응답을 보내는 것은 아닙니다.
그렇다면 소스 서버와 레플리카 서버를 구성 할까요?
이는 소스 서버와 레플리카 서버를 한대씩 두는 방식이며, 레플리카 서버는 예비 서버 및 데이터 백업용으로 활용합니다.
싱글 레플리카 방식에서 레플리카 서버를 한대 더 둔 멀티 레플리카 방식이 있으며 이때 레플리카 두 대를 사용하는데 한대는 쿼리 부하 분산용으로, 두 번째 레플리카 서버는 백업 용으로 사용할 수 있습니다.
소스 서버에 연결된 레플리카 서버가 많다면 소스 서버의 복제부하가 커지게 됩니다. 이때 다른 레플리카 서버를 소스 서버로 활용하여 복제 부하를 분산시키는데 사용할 수 있습니다.
혹은 기존에 사용하던 서버를 업데이트 하거나 장비 교체할 때 사용할 수 있습니다.
싱글 레플리카를 두개 만들어 놓은 것이며 이는 두 서버 모두 쓰기가 가능한 형태입니다. 이방식은 트랜잭션이 충돌이 일어날 경우 복제 멈춤 현상이 일어나기 때문에 잘 사용되지 않습니다.
하나의 레플리카 서버가 둘 이상의 소스서버를 가지며 이는 데이터를 분석 시 데이터를 모아 분석을 수행할 때 사용하게 됩니다.
그렇다면 소스 서버에 장애가 난 경우엔 레플리카 서버를 활용하는 경우들은 살펴 보았습니다. 하지만 레플리카 서버에 문제가 생긴다면 어떻게 다시 동기화를 만들까?
MySQL에선 크레시 세이프 복제 방식을 제공하고 있습니다.
- 레플리카 서버는 I/O 쓰레드와 SQL 쓰레드를 이용하여 소스 서버에 바이너리 로그 이벤트 위치를 읽을 때와 트랜잭션 실행정보를 읽을 때 어디까지 읽었는지에 대한 포지션 정보를 로컬에 저장해 둡니다.
- 레플리카 서버에 문제가 생겨 다시 재가동했을 시 그 정보를 기반으로 다시 소스서버에 동기화 과정을 거칩니다!