Docker Compose로 MySQL Replication 구축

2022. 11. 25. 01:28Infra/DevOps

Docker로 MySQL Replication 구축 내용을 간단하게 기록합니다. MySQL Replication을 구축하게 된 계기는 CQRS 패턴을 학습하면서 DB를 이중화하고 읽기와 쓰기를 구분해서 사용하기 위해 구성을 잡아보았습니다. docker로 MySQL Container를 띄워서 사용하였는데, 이번기회에 Docker Compose를 사용하여 띄워보았습니다.

 

환경 세팅

  • Host PC에 MySQL 설치
  • Host PC에 Docker, Docker-compose 설치

아래 내용으로 구성할 예정이고, 실제 배포를 한다면 Spring-Application도 docker로 Container에 띄우고 같은 Network에 연결시켜서 통신을 하겠다고 생각이 드는데... 백엔드 실무 경험은 없어서 일단 여기까지만 띄워봤습니다.


위 환경 세팅이 완료되면 docker-dompose.yml 파일을 아래와 같이 작성합니다.

파일 작성방법은 링크를 참고하면 됩니다.

version: "3.3"

services:
  db_master:
    image: mysql:latest
    container_name: db_master
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mariadb_root_password
    volumes:
      - ./master/data:/var/lib/mysql
      - ./master/config/:/etc/mysql/conf.d
    ports:
      - "33306:3306"
    networks:
      dock_net:
        ipv4_address: 172.16.0.10
    secrets:
      - mariadb_root_password

  db_slave:
    image: mysql:latest
    container_name: db_slave
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mariadb_root_password
    volumes:
      - ./slave/data:/var/lib/mysql
      - ./slave/config/:/etc/mysql/conf.d
    ports:
      - "33307:3306"
    networks:
      dock_net:
        ipv4_address: 172.16.0.11
    secrets:
      - mariadb_root_password
    depends_on:
      - db_master
    
secrets:
  mariadb_root_password:
    file: ./mariadb_root_password.txt

networks:
  dock_net:
networks:
  dock_net:
    driver: bridge
    ipam:
     config:
       - subnet: 172.16.0.0/16

 

위 yml 파일을 보면 컨테이너에서 사용될 볼륨을 Host PC에 mount 시켜두었는데, 해당 디렉토리에 master, slave 각각 아래와 같이 설정을 저장합니다.

 

아래는 mater입니다.

[mysqld]
log-bin=mysql-bin
server-id=1

아래는 slave입니다.

[mysqld]
log-bin=mysql-bin
server-id=2
relay-log=relaylog
log-slave_updates=1

비밀번호는 파일로 관리하는 사례를 보고 고대로 따라해봤습니다. 보통 실무에서는 설정파일을 읽어서 처리하는 부분이다보니 괜찮은 방법이라고 생각했습니다.

 


설정 잡아주기

기본적으로 아래 구성으로 세팅을 잡았습니다. Master와 Slave가 바이너리 로그를 통해 동기화를 이룹니다. 해당 동작원리는 링크가 상세하게 설명을 하고있습니다. 나중에 저도 기회가 된다면 한번 잘 정리해 보겠습니다.

  1. master 서버에서 User를 생성하고 공유할 DB를 하나 생성합니다.
  2. master 서버에서 dump 데이터를 생성합니다.
  3. dump한 데이터를 host pc로 가져옵니다.
  4. host pc로 가져온 dump 데이터를 slave로 옮깁니다.
  5. slave 서버에서 dump한 데이터를 불러옵니다.
  6. slave에서 master 서버를 연동합니다.
  7. 테스트 ( mater에서 table에 데이터 추가 후 slave에서 확인 )

위 순서대로 진행하는 과정은 링크의 블로그를 참고하였습니다.

 


1. Master 서버에서 User 생성 및 DB 생성

mysql> CREATE USER 'repluser'@'%' IDENTIFIED BY 'replpw';  
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repluser'@'%';

테스트 DB까지 생성해 보겠습니다.

mysql> CREATE DATABASE repldb;  
mysql> USE repldb;  
mysql> CREATE TABLE repltable ( no INT(8), PRIMARY KEY (no) );  
mysql> DESC repltable;

 

 

2. dump 생성

mysqldump -u root -p repldb > dump.sql

 

 

3~4. master dump를 slave로 이동

저는 해당 과정을 위 예시 링크와 달리 local에 mount 된 디렉토리를 사용하여 옮겼습니다. 리눅스 명령어를 적절히 사용하면 됩니다.

 

5. slave에서 dump 데이터 불러오기

mysql> CREATE DATABASE repldb;  
mysql> exit  
$ mysql -u root -p repldb < dump.sql

 

6. slave 서버에서 master 연동하기

 

일단 master에 접속하여 아래 정보를 확인 합니다.

'show master status\G;' 커맨드를 입력하여 결과를 확인합니다. 아래 정보를 slave 서버에 연동하기 위해 입력을 넣어야 합니다.

mysql> show master status\G;
*************************** 1. row ***************************
             File: mysql-bin.000015
         Position: 157
     Binlog_Do_DB: 
 Binlog_Ignore_DB: 
Executed_Gtid_Set: 
1 row in set (0.00 sec)

ERROR: 
No query specified

 

 

아래 명령어를 입력합니다. 위 정보중에 File과 Position 정보를 MASTER_LOG_FILE과 MASTER_LOG_POS에 입력합니다.

mysql> CHANGE MASTER TO MASTER_HOST='mysql-master', MASTER_USER='repluser', MASTER_PASSWORD='replpw', MASTER_LOG_FILE='mysql-bin.000015', MASTER_LOG_POS=157;  
mysql> START SLAVE;

 

정상적으로 connection이 되지 않으면 연결이 안되었다고 표시가 됩니다.

mysql> SHOW SLAVE STATUS\G; 
*************************** 1. row ***************************
               Slave_IO_State: Waiting for source to send event
                  Master_Host: db_master
                  Master_User: repluser
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000015
          Read_Master_Log_Pos: 157
               Relay_Log_File: relaylog.000014
                Relay_Log_Pos: 373
        Relay_Master_Log_File: mysql-bin.000015
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 157
              Relay_Log_Space: 745
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
                  Master_UUID: 39b0d6fb-682c-11ed-99f7-0242ac150002
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Replica has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
       Master_public_key_path: 
        Get_master_public_key: 1
            Network_Namespace: 
1 row in set, 1 warning (0.00 sec)

 

 


SpringBoot에서 아래와 같이 연결정보를 설정합니다.

application.yml 파일 설정 내용입니다.

datasource:
  url: jdbc:mysql://127.0.0.1:33307/repldb?useSSL=false&allowPublicKeyRetrieval=true
  slave-list:
    - name: slave_1
      url: jdbc:mysql://127.0.0.1:33307/repldb?useSSL=false&allowPublicKeyRetrieval=true
  username: <설정정보>
  password: <설정정보>

hibernate:
  dialect: org.hibernate.dialect.MySQL5InnoDBDialect


spring:
  jpa:
    hibernate:
    show-sql: true
    properties:
      hibernate:
        enable_lazy_load_no_trans: true
        format_sql: true
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
    open-in-view: false
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
  main:
    allow-bean-definition-overriding: true
  datasource:
    url: jdbc:mysql://127.0.0.1:33306/repldb?useSSL=false&allowPublicKeyRetrieval=true
    username: <설정정보>
    password: <설정정보>
    driver-class-name: com.mysql.cj.jdbc.Driver
    maximum-pool-size: 20

logging:
  level:
    org.springframework.jdbc.datasource.SimpleDriverDataSource: DEBUG
    org.hibernate.SQL: DEBUG

 

 

요렇게 사용을 하면됩니다. 그리고 Read/Write를 구분하기 위해서는 아래와 같이 Transactional Annotation을 사용하면 됩니다. readOnly를 사용하면 slave DB에서 읽기를 수행하게 됩니다. 어노테이션들에 대해서는 다른 글로 정리해보겠습니다.

    @Transactional(readOnly = true)
    public AccumulatedPointResponse getAccumulatedPoint(Long memberId ){
        AccumulatedPoint accumulatedPoint = accumulatedPointPointRepository.getByMemberId(memberId)
                .orElseThrow(AccumulatedPointNotFoundException::new);
        return AccumulatedPointResponse.builder().accumulatedPoint(accumulatedPoint).build();
    }

    @Transactional
    public AccumulatedPointResponse updateAccumulatedPoint(AccumulatedPoint accumulatedPoint){
        AccumulatedPoint saveResult = accumulatedPointPointRepository.saveAndFlush(accumulatedPoint);
        return new AccumulatedPointResponse(saveResult);
    }

 

 


한번 해보면 별거아닌데, 참 ~ 한번이 오래 걸립니다.