[Project] (3) SpringBoot Master-Slave MySQL DB Replication 구축 + MySQL Exporter 연동

Hayoon·2024년 1월 22일
2

토이 프로젝트를 진행하면서 발생했던 문제에 대한 본인의 생각과 고민을 기록한 글입니다.
기술한 내용이 공식 문서 내용과 상이할 수 있음을 밝힙니다.
Mysql 공식문서 참고: https://dev.mysql.com/doc/refman/8.0/en/

MySQL로 Replication을 하는 이유

지난 글에서 PostgreSQL를 사용하여 검색용도를 위한 Multi DB를 구축했다. 성능 개선에서 유의미한 지표가 있었지만, PostgreSQL에서 검색하기 위해서는 MainDB(MySQL)에서 데이터(메뉴리뷰)를 마이그레이션해야 하는 작업이 필요했다.

알아본 결과 Debezium, CDC, ETL 등 데이터를 복제하고 이관하는 방법이 있다고 했지만, 클러스터 또는 Apache Kafka와 같은 메시지 브로커를 구축해야 할 수 있다고 한다. k8s 등 추가적인 기술비용이 들어갈거라 판단해, 같은 RDBMS로 복제를 하고 이후 PostgreSQL를 통해 재도전 해보기로 했다.

데이터 복제 방법

다음 두가지의 방법을 생각했다.

  1. dump 파일로 복제: DB의 덤프 파일은 데이터의 스냅샷이며, 스키마와 데이터를 모두 포함한다. 이 파일을 가져와서 다른 DB에 복원하면 데이터를 복제할 수 있다.
  2. Master-Slave 구조: M-S 구조는 DB의 복제 기능을 사용하여 데이터를 실시간으로 복제하는 방식이다.
  3. 고급 부하 분산 기술: 데이터베이스 클러스터링, 샤딩 기법 활용 (기술적 비용으로 후순위)

Master-Slave

Master-Slave 복제는 DB 구조를 유지하면서 데이터를 동기화하는 기술이다. 이를 통해 Master 서버에서 발생한 변경사항이 Slave 서버로 전달되어 데이터 일관성을 유지할 수 있다.

복제 과정은 다음과 같다.

  1. 클라이언트가 트랜잭션을 커밋하면, 변경사항은 먼저 Master 서버의 Bin Log에 기록된다. 이 Bin Log는 Master 서버의 데이터 변경 이력을 기록하는 파일이다.
  2. Master Thread는 비동기적으로 Bin Log를 읽고, Binlog Dump Thread는 Slave 서버의 I/O 스레드와 통신하여 Bin Log의 내용을 전송한다.
  3. Slave의 I/O Thread는 Master 서버로부터 받은 변경 데이터를 Relay log에 기록한다. Relay Log는 Master 서버의 Bin Log를 Slave로 복제한 내용을 담은 파일이다.
  4. Slave의 SQL Thread는 릴레이 로그에 기록된 변경사항들을 읽어 Slave에 복제한다. 이를 통해 Slave 서버의 데이터가 Master 서버와 동일한 상태로 유지된다.

Master-Slave를 선택했고 이유는 다음과 같다.
1. 트래픽 부하 분산: 쓰기 작업을 Master에서, 읽기 작업을 Slave에서 처리하여 Master에 부하를 분산시킬 수 있다.
2. 실시간성: 메뉴 리뷰는 실시간으로 업데이트 작업이 이루어지는 대용량 트래픽일 것이다.
3. 장애 복구: Master DB에 장애가 발생하더라도 Slave DB가 존재하므로 시스템의 가용성을 높일 수 있다. 장애 발생 시 Slave DB를 새로운 Master로 승격시켜 서비스의 지속성을 확보할 수 있다.

Failover: Master가 예상치 못한 장애로 인해 중단되었을 때, Slave로 자동으로 전환되는 과정을 의미한다. Master의 장애를 감지하면, Slave가 자동으로 활성화되어 서비스를 계속 제공하여 시스템의 지속성을 유지할 수 있다.
Failback: Failover 이후에 장애가 복구되고 원래의 Master가 정상 상태로 돌아올 때, 다시 원래의 Master로 작업을 전환하는 과정을 의미한다. Failover로 인해 Slave가 활성화되었을 때, Master가 복구되면 Master로 작업을 다시 이관한다.

Architecture

Docker Container안에 Mysql 2개를 띄운다. 각각의 호스트 포트는 3306(M), 3307(S)로 설정하고, 이후 Prometheus로 메트릭을 수집하기 위해 Exporter의 포트도 9104(M), 9105(S)로 설정한다.

db_network: 도커 네트워크는 가상의 네트워크를 생성하고, 컨테이너 간에 IP 주소를 할당하여 상호간의 통신을 가능하게 한다. 이를 통해 DB 컨테이너들은 독립적으로 동작하면서도 서로 통신할 수 있다.

MySQL Replication

Docker Network

docker network create -d bridge db_network

Docker DB Container

# master cmd
docker run -d --name mysql80 -p 3306:3306 \
--network db_network \ // 'db_network'라는 이름의 도커 네트워크에 컨테이너를 연결
-e MYSQL_ROOT_PASSWORD=1234 \
-v mysql80-datadir:/var/lib/mysql \
-v /Users/Docker/mysql/master:/etc/mysql/conf.d \
mysql:8

# slave cmd
docker run --name mysql-slave \
-v /Users/Docker/mysql/slave:/etc/mysql/conf.d \
--link mysql80 \ // 'mysql80'라는 이름의 마스터 컨테이너에 대한 링크를 설정
--network db_network \ // 'db_network'라는 이름의 도커 네트워크에 컨테이너를 연결
-e MYSQL_ROOT_PASSWORD=1234 \
-p 3307:3306 -d mysql-slave

DB Configuration File

# master_config.cnf
[mysqld] # MySQL 서버의 설정을 지정
log-bin=mysql-bin # binary log를 사용하여 데이터 변경 사항을 기록
server-id=1 # Master 서버의 고유한 식별자를 설정

# slave_config.cnf
[mysqld] # MySQL 서버의 설정을 지정
server-id=2 # Slave 서버의 고유한 식별자를 설정

여기서 주의해야할 점은 Master-Slave를 연결하기 위해 -v 옵션을 사용하여 호스트의 디렉토리를 도커 컨테이너의 특정 경로와 연결해 호스트의 파일을 도커 컨테이너 내부로 마운트해야한다.

Master 서버에 Replication 계정 생성

# Master DB
mysql> create user 'repluser'@'%' identified by '1234'; 
mysql> GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'repluser'@'%';
mysql> flush privileges;

Replication 계정에 Slave 권한을 부여하고 권한 flush 적용

Test

Master에서 menu 테이블의 데이터를 하나 생성했다.
Slave에서 select * from menu를 통해 Master에서 생성한 데이터가 그대로 복제됨을 알 수 있다.
(DDL script를 통해 schema까지 전부 복제가 되지만, 사전에 프로젝트를 진행하면서 schema 복제는 이미 해놓은 상태라, 지금은 데이터만 복제되었다.)

에러 발생 시 sync 설정

Master 서버의 이벤트 로그 파일(binlog) 손상 또는 Slave 서버의 복제 설정 오류로 복제가 실패했을 때 Slave DB에서 show slave status\G;를 입력하면 아래와 같이 ANONYMOUS ... mysql-bin.00006 SQL_ERROR 로그를 볼 수 있다.

'bin.000006'과 '61115782'은 MySQL 이벤트 로그 파일의 이름과 위치를 나타낸다. 이벤트 로그 파일은 Master 서버에서 변경 작업(INSERT, UPDATE, DELETE 등)이 발생할 때 해당 작업을 기록하는 파일이다.
두 개의 값을 확인하고 Slave DB에서 stop slave를 입력 후 CHANGE REPLICATION SOURCE ... 입력 후 start slave 로 복제받을 데이터 소스를 변경

MySQL Exporter

Docker Mysql Exporter Container

# master exporter cmd
docker run -itd \
-p 9104:9104 \
-v /Users/hayoon/exporter/mysql_exporter.cnf:/.my.cnf \
--network db_network \
--name mysql_exporter \
prom/mysqld-exporter

# slave exporter cmd
docker run -itd \
-p 9105:9104 \
-v /Users/hayoon/exporter/mysql_slave_exporter.cnf:/.my.cnf \
--network db_network \
--name mysql_slave_exporter \
prom/mysqld-exporter

Exporter Configuration File

# mysql_master_exporter.cnf
[client]
host=mysql80
port=3306
user=exporter
password=1234

# mysql_slave_exporter.cnf
[client]
host=mysql-slave
port=3306
user=exporter
password=1234

에러이슈

Master Exporter는 정상적으로 연결이 됐었지만, Slave Exporter는 계속해서 에러로그가 발생했다.
원인 점검
"Error pinging mysqld""dial tcp 172.22.0.3:3307: connect: connection refused"라는 에러 메시지는 MySQL Exporter가 MySQL Slave에 연결을 시도하였으나, 연결이 거부되었다. MySQL Slave가 동작하지 않거나, 네트워크 설정에 문제가 있을 때 발생하는 에러로 원인이 의심되는 설정을 점검했다.

  1. MySQL Slave가 올바르게 실행 중인가? → MasterDB에서 정상적으로 복제 확인

  2. MySQL Slave의 네트워크 설정을 확인: Docker의 네트워크 설정이 올바른지, MySQL Slave가 올바른 IP 주소와 포트에서 수신 중인지 확인 → db_network에 정상적으로 매핑

  3. MySQL Exporter의 DATA_SOURCE_NAME 환경 변수 확인 → DB exporter 계정 삭제 후 재생성 및 디렉토리 확인 후 .cnf 마운트

문제 해결
정상적으로 연결이 되는 Mysql-Master-Exporter의 설정을 되짚어보았다.
의심되는 지점은 cnf 파일이다.
Master Exporter의 Docker Container의 Host Port:Internal Port = 3306:3306이다. Slave Exporter는 3307:3306이다. 포트 중복을 방지하기 위해 호스트 포트를 3307로 우회해서 config파일에 설정을 했지만 방화벽 문제인지, 도커 네트워크 문제인지 내부적으로 포트 매핑이 안 되는 것 같았다. (docker inspect log로 확인했을 때는 정상적 포트 매핑 확인)
따라서, 내부 포트로 바꿔서 cnf 파일을 수정해주었다.

# mysql_slave_exporter.cnf
[client]
host=mysql-slave
port=33073306 // port 수정
user=exporter
password=1234

포트를 수정하니 드디어 정상적으로 연결이 되었다. 내 생각은 다음과 같다.
mysql-slave 컨테이너의 3306 포트가 호스트의 3307 포트에 바인딩되어 있지만, mysql-slave-exporter가 동일한 Docker 네트워크(db_network) 내부에서 동작하고 있기 때문에, mysql-slave-exporter는 mysql-slave 컨테이너의 내부 포트인 3306을 사용해서 mysql-slave 컨테이너의 MySQL 서버에 접근할 수 있게 된 것 같다. (포트 바인딩이 된 건 맞는데 왜 3307로 했을 때는 DB Container 접근이 안 된건지는 아직도 모르겠다.)

Prometheus에도 2개의 Mysql Exporter의 Metric이 수집된다.

생각보다 연동하는데에 시간이 많이 소요되었다. Log를 보면서 Docker Network나 Port 설정에 대해 주의를 기울이며 config file을 작성해야 할 것 같다.

추가로, Master-Slave 이외에 DB Cluster, Shading를 통해 더 많은 자율성과 확장성을 제공하므로, 대규모 트래픽 처리에 적합하다고 한다. 이에 대한 기술적 공부를 하면서 더 나은 성능 개선을 해볼 계획이다.

다음 글은, Prometheus와 Grafana를 함께 사용하여 Exporter를 통해 수집한 Metric 데이터를 시계열 데이터로 시각화하여 분석하는 글로 찾아뵙겠다.

profile
Junior Developer

2개의 댓글

comment-user-thumbnail
2024년 4월 7일

안녕하세요. 글 잘봤습니다. 궁금한 사항이 있어서 Failover, Failback을 자동으로 해줄수 있다고 하는데 어떻게 설정해야 가능한것일까요 ?

1개의 답글