본문 바로가기

Database/MySQL

나는 왜 MySQL Replication을 적용했을까?

 

많은 트래픽이 흐르는 상황에서 서버의 성능을 올리기 위해 애플리케이션 서버를 늘리는 방법을 선택할 수 있습니다. 하지만 서비스를 운영하기 위해선 애플리케이션 서버뿐만 아니라 데이터를 저장하는 역할인 DB 서버 또한 필요로 합니다. 애플리케이션 서버의 부하는 위와 같은 방법으로 해소가 가능하지만 DB 서버는 어떨까요?

 

실제로 DB 서버는 데이터를 Disk에 직접 쓰고, 읽는 I/O 작업이 일어나기 때문에 애플리케이션 서버보다 훨씬 큰 병목 지점이 될 수 있습니다. 애플리케이션 서버를 아무리 늘려도 데이터에 직접 액세스 하는 부분에서 똑같이 병목이 발생하면 서비스 전체의 병목은 그대로이기 때문에 DB 서버도 부하를 분산하여 큰 트래픽에 대응하는 것이 좋습니다.

 


MySQL Replication

MySQL은 Replication을 제공해줍니다. Replication은 복제라는 의미로 Master/Slave 구조를 기반에서 Master MySQL 서버의 변경 사항을 Slave MySQL 서버로 받아와(복제) 데이터의 정합성을 맞추고 애플리케이션 서버의 요청을 Slave에서 처리할 수 있는 형태입니다.

 

MySQL Master/Slave 구조

 

동작 원리

위 그림과 같은 구조에서 애플리케이션 서버로부터 오는 데이터 삽입, 수정, 삭제 쿼리를 Master 서버로 보내면 Master 서버는 쿼리를 처리하고 바이너리 로그 스레드를 사용해 변경 내역(쿼리나 변경된 ROW 자체)을 바이너리 로그저장합니다.

그럼 Slave 서버의 Replication I/O 스레드가 Master 서버로 접속해 변경 내역을 요청하면 Master 서버의 바이너리 로그 덤프 스레드가 바이너리 로그를 읽어 변경 내역을 Replication I/O 스레드에게 전달하고 Replication I/O 스레드는 변경 내역을 Slave 서버의 릴레이 로그저장합니다. 마지막으로 Replication SQL 스레드가 릴레이 로그를 읽어 변경 내역을 적용하고 서버 간의 데이터 정합성을 맞추게 됩니다.

 

단점

  • Replication을 적용하면 Master/Slave 서버 간 데이터 동기화까지의 시간이 소요되기 때문에 데이터 정합성 문제가 발생할 수 있습니다. 
  • Slave 서버에서 데이터 변경이 일어나면 Master 서버와의 정합성이 깨지고 이를 위한 추가 작업이 필요합니다.
  • Master 서버에서 바이너리 로그를 안정적으로 기록하기 위해 락(Lock)을 유지하고, 로그를 기록하는 행위 또한 디스크 I/O를 추가적으로 유발합니다.
  • 애플리케이션 서버에서는 Master/Slave 서버로 분기할 수 있는 기능이 필요합니다.

 

하지만 이러한 단점을 보완할 수 있으면 많은 트래픽이 한 서버로 집중되는 것을 분산시켜 좀 더 안정적인 서버를 운영할 수 있는 큰 장점이 있습니다.


Replication 단점 보완을 위한 고려 사항

 

데이터 정합성 문제

Master와 Slave 간의 데이터 정합성 차이는 해결하기보다는 정합성 차이를 허용 가능한 데이터 조회만 분기시키기로 했습니다. 예약 및 결제처럼 정합성이 중요한 요청이나  실시간성이 보장돼야 하는 쿼리는 Master 서버로, 정합성 차이가 어느 정도 허용 가능한 캠핑용품 조회와 같은 쿼리는 Slave 서버로 분기시켰습니다.그리고 Slave 서버는 Select처럼 데이터 조회만 할 수 있게 read-only 속성을 부여하여 Slave 서버의 데이터가 변경되지 않도록 설정하였습니다.

 

바이너리 로그 오버헤드

이 부분은 안정적인 Replication을 구성하기 위해선 필수적으로 감수해야 하는 부분이라 생각했습니다. 바이너리 로그로 인한 오버헤드와 Replication 구성으로 얻는 부하 분산을 Trade-off 하면 바이너리 로그 한 번의 작성으로 여러 개의 Slave 서버를 구성하여 더 많은 트래픽에 대응할 수 있으며 실보다는 득이 많다고 판단하였습니다.

 

애플리케이션 서버에서의 Master/Slave 요청 분기 구현

Master/Slave 요청 분기를 위해 Spring jdbc에서 제공해주는 AbstractRoutingDataSource 추상 클래스Transactional 애노테이션을 사용해 구현했습니다. 해당 추상 클래스는 템플릿 메소드 패턴을 기반으로 여러 개의 DataSource를 특정 조건에 의해 결정할 수 있는 방법을 제공해줍니다. 저는 이 추상 클래스와 Transactional의 readOnly 속성을 통해 어떤 요청을 분기를 할 것인지 쉽게 지정하고 분기하는 기능을 구현할 수 있었습니다.

 


나의 프로젝트에 Replication이 필요한 이유

제가 진행하고 있는 프로젝트는 '지속적으로 성장하는 서비스다.'라는 가정을 하고 있습니다. 그래서 많은 트래픽이 들어오는 경우를 고려하여 개발이 진행돼야 했습니다. 특히나 DB는 Disk의 직접적인 I/O가 발생하여 병목이 가장 크게 발생할 수 있는 부분이고 이로 인한 운영 중인 DB 서버의 장애는 서비스 운영에 매우 큰 영향을 끼칩니다.

장애로 인해 부정적인 사용자 경험, 환경이 쌓이면 매출, 수익 악화와 직결되기 때문에 장애를 최소화하고 좀 더 안정적인 운영이 가능한 Replication이 필요했습니다.

 

 

마무리

MySQL Replication의 단점을 어느 정도 보완, Trade-off 하여 하나의 DB를 사용하는 것보다 훨씬 안정적인 서비스를 제공할 수 있게 되었습니다.  반드시 Replication을 사용해야 한다는 것은 아닙니다. 자신의 상황을 객관적으로 분석하여 좀 더 안정적인 DB 서버를 운영하고 싶으면 Replication 은 좋은 고려 사항이라고 말할 수 있습니다.

 

 

'Database > MySQL' 카테고리의 다른 글

MySQL 실행 계획 분석으로 성능 개선한 이야기  (0) 2021.05.04