MySQL Replication
우아한테크캠프Pro 2기 마지막 주차의 마지막 미션은 데이터 베이스 이중화. 정말 벌써 마지막 주차의 마지막 미션을 진행하게 되었다,,, 감격,,,
마지막 미션인 Replication 은 복제
라는 뜻으로 Master 라는 DB와 동일한 환경의 동일한 구성으로 Slave DB를 만들어 주는 것이다. nginx
와 같이 웹 어플리케이션의 부하를 막기 위해 로드밸런싱 역할을 수행하면 웹 어플리케이션 자체의 부하는 막을 수 있지만 DB의 부하도 분산을 해주어야 CRUD 의 기능을 원활히 수행할 수 있도록 해준다
보통 등록/삭제/수정은 Master db를 조회는 Slave db를 접속하여 분산 시킨다
MySQL에서 지원하는 Replication 기능을 이용하여 분산을 해주었다.
먼저 Master DB를 설정해준다.
- 주의할 점은 Master DB를 먼저 설정한 후에 Slave DB를 설정해 주어야 한다,
$ docker run --name mysql-master -p 13306:3306 -v ~/mysql/master:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=master -d mysql
$ docker exec -it mysql-master /bin/bash
$ mysql -u root -p
mysql> CREATE USER 'replication\_user'@'%' IDENTIFIED WITH mysql\_native\_password by 'replication\_pw';
mysql> GRANT REPLICATION SLAVE ON _._ TO 'replication\_user'@'%';
mysql> SHOW MASTER STATUS\\G
************\*\*\************* 1. row ************\*\*\*************
File: binlog.000002
Position: 683
Binlog\_Do\_DB:
Binlog\_Ignore\_DB:
Executed\_Gtid\_Set:
1 row in set (0.00 sec)
다음 Slave DB를 설정해준다.
$ docker run --name mysql-slave -p 13307:3306 -v ~/mysql/slave:/etc/mysql/conf.d -e MYSQL\_ROOT\_PASSWORD=slave -d mysql
$ docker exec -it mysql-slave /bin/bash
$ mysql -u root -p
mysql> SET GLOBAL server\_id = 2;
mysql> CHANGE MASTER TO MASTER\_HOST='172.17.0.1', MASTER\_PORT = 13306, MASTER\_USER='replication\_user', MASTER\_PASSWORD='replication\_pw', MASTER\_LOG\_FILE='binlog.000002', MASTER\_LOG\_POS=683;
mysql> START SLAVE;
mysql> SHOW SLAVE STATUS\\G
...
Slave\_IO\_Running: Yes
Slave\_SQL\_Running: Yes
SHOW SLAVE STATUS\G
실행 시 에 볼 수 있는 것처럼 Running이 Yes 상태로 있어야 정상적으로 적용이 된다. Connecting 이나 다른 상태를 보여주면 실행이 아직 안된 걸로 볼 수 있다.
다음 Master db로 접속해서 Create Schema test_schema
를 생성하고 Slave db 로 접속하여 show databases
를 실행하면 동일하게 schema가 설정된 것을 볼 수 있다.
- application.properties
spring.datasource.hikari.master.username=root
spring.datasource.hikari.master.password=master
spring.datasource.hikari.master.jdbc-url=jdbc:mysql://ip:13306/test\_schema?useSSL=false&useUnicode=yes&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.hikari.slave.username=root
spring.datasource.hikari.slave.password=slave
spring.datasource.hikari.slave.jdbc-url=jdbc:mysql://ip:13307/test\_schema?useSSL=false&useUnicode=yes&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true
- ReplicationRoutingDataSource.java
public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
public static final String DATASOURCE\_KEY\_MASTER = "master";
public static final String DATASOURCE\_KEY\_SLAVE = "slave";
@Override
protected Object determineCurrentLookupKey() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
return (isReadOnly)
? DATASOURCE_KEY_SLAVE
: DATASOURCE_KEY_MASTER;
}
}
- DataBaseConfig.java
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"nextstep.subway"})
class DataBaseConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
ReplicationRoutingDataSource routingDataSource = new ReplicationRoutingDataSource();
HashMap<Object, Object> sources = new HashMap<>();
sources.put(DATASOURCE_KEY_MASTER, master);
sources.put(DATASOURCE_KEY_SLAVE, slave);
routingDataSource.setTargetDataSources(sources);
routingDataSource.setDefaultTargetDataSource(master);
return routingDataSource;
}
@Primary
@Bean
public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}
}
마지막으로 Transaction이 발생하는 service 메서드에 @Transactional(readOnly = true)
annotation을 추가하면 ReplicationRoutingDataSource.java
의 determineCurrentLookupKey
메서드에 의해 단순 select는 slave db를 나머지 수정/삭제/등록에 대한 메서드는 Master db를 향할면서 db 이중화의 기능을 수행할 것이다
마치며
웹어플리케이션의 부하를 줄일 수 있는 방법이 정말 여러가지라는 것을 배웠고 그에 따라 천차만별의 성능을 낸다는 것도,, 평소에 DB 이중화를 알고는 있었는데 실제로 눈으로 DB가 어떻게 백업이되고 복제가 되는지 확인할 수 있었다. 그리고 Master dbms 변경 시 즉각 Slave dbms에 반영이 되는 것도 신기,,
나는 flyway를 통해 Master dbms에 ddl를 수행하였는데 이 ddl도 Slave에 바로 복제가 되는 것을 확인할 수 있었다!
'Programming > Spring' 카테고리의 다른 글
[Springboot] Pageable (0) | 2021.07.11 |
---|---|
[Spring] 롬복(Lombok) (0) | 2021.01.21 |
[Spring] #4. MAVEN 설치 및 Eclipse 연동 (0) | 2021.01.14 |
[Spring] #3. Eclipse SVN 설치 (0) | 2021.01.14 |
[Spring] #2. Spring Tool Suite 설치 및 프로젝트 생성 (0) | 2021.01.14 |