[Pet-Hub] MySQL 데이터 분산 처리를 위한 Master-Slave 이중화 구성(MySQL Replication 설정)

DevSeoRex·2023년 5월 29일
9
post-thumbnail

😈 프로젝트에 굳이 DB를 두개나 쓴다고?

지금껏 프로젝트를 진행하면서 대부분 RDS 서비스를 이용해 한개의 데이터베이스만 사용해왔습니다.
이번 프로젝트의 중점 목표 중 하나인 고가용성의 설계와 확장성 있는 구조를 가진 서버를 구축하는 것에 초점을 두고 Master - Slave 구조의 DB 이중화 작업을 하게 되었습니다.

이중화를 하는 이유

데이터베이스의 트랜잭션을 readOnly로 셋팅할경우 성능이 상승하는 이점을 누릴 수 있습니다.
데이터베이스에서 일어나는 작업을 읽기와 쓰기로 나눈다면 대부분의 작업은 읽기에 해당한다고 볼 수 있습니다.

애플리케이션의 성능을 향상시키려고 할 때 쿼리를 튜닝한다면 조회 쿼리를 튜닝해 성능상의 이점을 얻는 경우가 굉장히 많습니다. 확장성 측면에서도 Master DB는 쓰기 작업만 담당하고 하위에 여러 Slave DB를 둘 수 있도록 구성하였습니다.

이중화를 구성하기 위해서는 현재 MySQL을 사용중이기 때문에 MySQL Replication에 대해서 알고 있어야 합니다.

MySQL Replication 이란?

말 그대로 Replication은 '복제'라는 뜻을 가지고 있습니다.
MySQL의 Master 서버의 데이터를 n개의 Slave 서버와 동기화 시켜주는 기능입니다. MySQL의 데이터를 실시간으로 백업하거나, DB 서버의 부하를 분산하기 위하여 사용합니다.

MySQL Replication의 동작 방식

MySQL Replication은 어떻게 동작할까요?

Replication은 다음과 같은 순서로 진행됩니다.

  1. Master 데이터베이스가 binary log를 만들어 이벤트를 기록합니다.
  2. 각 Slave 데이터베이스는 어떤 이벤트까지 저장되어 있는지 기억하고 있습니다.
  3. Slave 데이터베이스의 IO Thread를 통해서 Master 데이터베이스에 이벤트를 요청하고 받아옵니다.
  4. Master 데이터베이스는 이벤트를 요청받으면 binlog dump thread를 통해서 클라이언트에게 이벤트를 전송합니다.
  5. IO thread는 전송받은 덤프 로그를 이용하여 relay log를 만듭니다.
  6. SQL thread는 relay log를 읽어서 이벤트를 다시 실행하여 Slave 데이터베이스에 데이터를 복사합니다.

Replication은 Master 데이터베이스에 데이터를 insert 했을때 뿐만 아니라, 테이블을 생성하거나 테이블의 구조를 변경하였을때도 이벤트가 동작하여 Slave 데이터베이스에도 반영이됩니다.

👻 MySQL Container 실행 및 Replication 설정

이번 실습에서는 Master와 Slave 하나를 사용할것이기 때문에 docker-compose를 이용하여 MySQL을 두개 실행하도록 하겠습니다.

실습을 위해 만든 폴더 구조는 아래와 같습니다.

docker-compose.yml 파일과 같은 위치에 master, slave 폴더가 하나씩 있습니다.
master과 slave 폴더는 각 데이터베이스마다 사용할 DockerfileMySQL 설정을 위한 my.cnf 파일을 가지고 있습니다.

docker-compose.yml 작성

version: "3"
services:
  # 서비스의 이름
  db-master:
    # 도커 이미지를 빌드하는 방법을 지정
    build: 
      # 현재 디렉토리를 빌드 컨텍스트로 사용
      context: ./
      # master 디렉토리에 위치한 Dockerfile을 사용하여 도커 이미지를 빌드
      dockerfile: master/Dockerfile
    # 컨테이너가 비정상적으로 종료되었을 때 자동으로 재시작
    restart: always
    # 컨테이너가 실행될 플랫폼을 지정
    platform: linux/x86_64
    # 컨테이너 내에서 사용할 환경 변수를 설정
    environment:
      # MySQL 데이터베이스의 이름을 db로 설정
      MYSQL_DATABASE: 'db'
      # MySQL에서 사용될 사용자 이름을 설정
      MYSQL_USER: 'user'
      # MySQL에서 사용될 사용자의 비빌번호를 설정
      MYSQL_PASSWORD: '1234'
      # MySQL 루트 사용자의 비밀번호를 설정
      MYSQL_ROOT_PASSWORD: '1234'
    # 호스트와 컨테이너 간의 포트 매핑을 정의한다 -> 호스트의 3306 포트와 컨테이너의 3306 포트를 연결
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    ports:
      - '3306:3306'
    # 컨테이너 이름을 정의
    container_name: master-db
    # 데이터를 영속적으로 저장할 볼륨을 정의 -> 볼륨은 컨테이너가 시작될 때 생성되고 컨테이너가 종료되어도 데이터가 유지된다.
    volumes:
      - my-db-master:/var/lib/mysql
      - my-db-master:/var/lib/mysql-files
    # 서비스가 연결될 네트워크를 지정
    networks:
      - net-mysql
  
  db-slave:
    build: 
      context: ./
      dockerfile: slave/Dockerfile
    restart: always
    platform: linux/x86_64
    environment:
      MYSQL_DATABASE: 'db'
      MYSQL_USER: 'user'
      MYSQL_PASSWORD: '1234'
      MYSQL_ROOT_PASSWORD: '1234'
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    ports:
      - '3307:3306'
    container_name: slave-db
    # Where our data will be persisted
    volumes:
      - my-db-slave:/var/lib/mysql
      - my-db-slave:/var/lib/mysql-files
    networks:
      - net-mysql
  
# 컨테이너에서 사용할 볼륨의 이름을 정의한다.
volumes:
  my-db-master:
  my-db-slave: 

# net-mysql 이라는 이름의 네트워크를 정의한다.
networks: 
  net-mysql:
    # 이 네트워크는 Docker 컨테이너들 사이의 통신을 가능하게 하는 bridge 네트워크임을 정의
    driver: bridge

docker-compose 파일을 위와 같이 작성하시면 됩니다. 개인적으로 수정해야 할 부분은 수정하시고 사용하시면 되겠습니다.

Trouble Shooting - Mac 사용자만 해당(M1, M2)

M1 또는 M2 칩을 사용하는 Mac 사용자 분들은 아래 사진에 표시된 부분을 지우게 되면 문제가 생깁니다.

이 부분을 지우지 않고 사용하셔야 됩니다! 다른 OS를 사용하시는 분들은 상관 없으시니 넘어가주셔도 됩니다!

Master DB용 파일 작성

지금부터는 master 폴더안에 들어갈 파일을 작성하겠습니다.

Dockerfile

# mysql:5.7 도커 이미지를 기반으로 하여 새로운 이미지 빌드
FROM mysql:5.7

# 호스트 머신의 ./master/my.cnf 파일을 새로운 이미지의 /etc/mysql/my.cnf 경로로 추가한다.
ADD ./master/my.cnf /etc/mysql/my.cnf

my.cnf

[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

# MySQL 서버(daemon)의 설정을 지정한다.
[mysqld]
character-set-client-handshake = FALSE
character-set-server           = utf8mb4
collation-server               = utf8mb4_unicode_ci
default-time-zone='+9:00'

# 이진 로그를 사용해서 MySQL 서버에서 발행하는 변경 사항을 기록한다. mysql-bin은 이진로그의 파일 이름을 지정한다.
log-bin = mysql-bin

# MySQL 서버의 고유한 식별자를 설정한다. 이 식별자는 MySQL 복제(replication) 설정에서 사용된다.
server-id = 1

# MySQL 사용자의 인증 플러그인을 mysql_native_password로 지정한다 -> 5.7 이전 버전과의 호환성을 유지하기 위해 사용되는 매커니즘이다.
default_authentication_plugin=mysql_native_password

코드 라인별로 주석을 달아 놓았으니 이해하시는데 어려움이 없으실거라 생각합니다!

Slave DB용 파일 작성

이제 이어서 slave 폴더에 들어갈 파일을 작성하겠습니다.

Dockerfile

FROM mysql:5.7
ADD ./slave/my.cnf /etc/mysql/my.cnf

my.cnf

[client]
default-character-set = utf8mb4

[mysql]
default-character-set = utf8mb4

[mysqld]
character-set-client-handshake = FALSE
character-set-server           = utf8mb4
collation-server               = utf8mb4_unicode_ci
default-time-zone='+9:00'

log_bin = mysql-bin
server_id = 2

# MySQL 슬레이브 서버에서 사용되는 중계로그(relay log)의 저장 위치를 지정한다.
relay_log = /var/lib/mysql/mysql-relay-bin

# 슬레이브 서버에서 발행하는 변경 사항도 이진 로그에 기록하도록 하게 하는 설정이다. 기본적으로 슬레이브 서버의 변경 사항은 이진 로그에 기록되지 않는다.
log_slave_updates = 1

# 슬레이브 서버를 읽기 전용 모드로 설정한다.
read_only = 1

default_authentication_plugin=mysql_native_password

이제 파일 작성이 끝났으니 MySQL을 도커 컨테이너에 올려주고 Replication 작업을 해보겠습니다.

🥳 MySQL Replication 설정하기!

docker-compose.yml 파일이 있는 경로로 이동해서 아래의 명령어를 입력해주면

$ docker-compose up -d

Master와 Slave 데이터베이스가 잘 실행되었습니다. 이제 Replication을 위한 설정을 시작해보겠습니다.

Master - Slave 간의 통신을 위한 내부 IP 주소 확인하기

docker network ls 명령어로 도커 컨테이너의 네트워크 목록을 확인합니다.

현재 MySQL에서 사용하는 network의 이름은 mysql_compose_net-mysql 입니다. Master의 IPv4Address가 필요하므로, 조회하도록 하겠습니다.

docker inspect <bridge의 network ID> 명령어를 입력하면 아래의 화면이 출력됩니다.

이중에서 필요한 정보는 master-db의 IPv4Address 입니다.
IPv4Address를 슬래시( / ) 이전까지 복사합니다. 예를 들어 172.18.0.3/16 -> 172.18.0.3 이런식으로 생각하시면 됩니다.

이 IP 주소는 Slave 데이터베이스를 설정할때 필요한 정보이기 때문에 잘 기록해두시면 되겠습니다.

Master 설정

Master DB에 접속해서 설정을 해보겠습니다.

// Master 계정에 접속하기 위해 명령어 입력
docker exec -it master-db bash
// mysql을 root 계정으로 접속
mysql -u root -p

이 명령어를 입력하면 비밀번호를 입력하라는 멘트가 나오는데, 비밀번호를 입력하면 접속이됩니다.
Slave 설정을 위해 master 에서는 특정 정보만 가져오면 됩니다.

아래의 명령어를 콘솔에 입력해줍니다.

show master status;

File(mysql-bin.000006)과 Position(154) 값을 잘 기록해둡니다.

Slave 설정

이제 Slave 계정에 접속해서 Slave 설정을 해보겠습니다.
접속중인 Master DB에서 나가서 다시 터미널로 이동합니다.

// Slave 계정에 접속하기 위해 명령어 입력
docker exec -it slave-db bash
// mysql을 root 계정으로 접속
mysql -u root -p


Master와 같은 방법으로 접속되었습니다.

Slave 설정을 위해 아래와 같은 명령어를 입력해줍니다.

CHANGE MASTER TO MASTER_HOST='{master-db의 네트워크 IP}',
MASTER_USER='root',
MASTER_PASSWORD='{root 계정의 비밀번호}',
MASTER_LOG_FILE='{master-db의 바이너리 파일 이름}',
MASTER_LOG_POS={master-db에서 조회한 파일 포지션};


실행이 완료되면 slave를 아래의 명령어를 이용해 실행해줍니다.

start slave;

아래의 명령어를 입력해, 현재 replication 설정이 잘 되어있는지 확인합니다

show slave status\G;

많은 정보들이 출력되지만 여기서 봐야할 것은 딱 두가지 정보입니다.
Slave_IO_Running & Slave_SQL_Running 입니다. 이 두가지가 YES라면 현재 설정이 잘 되어있는 것입니다.

그렇다면 이제 Master - Slave 간에 Replication이 잘 작동하는지 테스트를 해보겠습니다.

다시 Master로 접속합니다.

MySQL에 접속하고 db 스키마를 이용하겠습니다. db 스키마를 이용하는 방법은 use db; 구문으로 스키마를 선택해줄 수 있습니다.

테이블을 하나 만들고 데이터를 간단히 넣어보겠습니다.

이제 Slave DB에서 현재 만든 테이블과 데이터가 존재하는지 확인해보겠습니다.


Slave DB에도 Master DB의 데이터가 존재하는 것을 볼 수 있습니다.

🧐 무조건 이중화가 좋은걸까?

Master DB에 값을 입력하게 되면, Slave DB에 복제가 이루어지게 됩니다. MySQL Replication은 기본적으로 비동기 방식을 사용하기 때문에 복제되는 시간 사이에 조회를 하게 되면 데이터가 다를 수 있습니다.

이 부분은 실시간성이 높은 데이터의 경우 Master DB에 요청을 보내도록 애플리케이션에서 컨트롤해주면 충분히 극복 가능한 부분이라고 생각이 되지만, 데이터 복제 중 누락이 발생할 수 있고 Master DB 장애로 인해 Slave DB를 승격 시킬 경우, 필요 시 레플리카에 누락분을 수동으로 반영해줘야 할 수 있습니다.

물론 단점만 있는 것은 아닙니다. 장점으로는 읽기 전용 트랜잭션 사용을 통한 성능 향상을 기대할 수 있고, 대부분의 게시글이나 기타 글들은 실시간성이 떨어지는 데이터라고 볼 수 있기 때문에 충분히 이중화를 통한 이점을 많이 누릴 수 있다고 생각합니다.

다음 게시글에서는 Master Slave 두개의 데이터베이스를 구성하였으니 Spring에서 Transactional의 readOnly가 true라면 Slave로, false라면 Master로 요청을 보낼 수 있도록 구성해보도록 하겠습니다.

오늘도 읽어주셔서 감사합니다.

다음 시리즈 게시물로 이동
MySQL - 데이터 분산 처리를 위한 Master-Slave 이중화를 구성하다(Spring과 JPA 설정)
게시물로 이동하기 ->

🙇

참고한 레퍼런스

https://hudi.blog/mysql-replication-synchronous-type/

https://blog.naver.com/ncloud24/221443452960

https://blog.seulgi.kim/2015/05/how-mysql-replication.html

3개의 댓글

comment-user-thumbnail
2024년 2월 14일

명글 잘 보고 갑니다!

답글 달기
comment-user-thumbnail
2024년 4월 29일

이해하기 쉽게 설명해주셔서 감사합니다 !

1개의 답글