1. 데이터베이스 다중화의 개념
데이터베이스 다중화는 여러 대의 데이터베이스 서버를 구성하여 시스템 전체의 성능 향상과 고가용성을 달성하는 기술입니다. 이는 MySQL Replication과 같은 "복제 기술"을 사용해 데이터를 여러 서버로 동기화 하는 방식으로 구현됩니다.
"복제"는 한 서버의 데이터가 다른 서버로 동기화되는 것을 의미합니다. 특히, 소스 서버로부터 레플리카 서버로 데이터 복제가 이루어지는데, 소스 서버는 원본 데이터를 소유하고 있는 서버를 의미하며, 레플리카 서버는 복제된 데이터가 저장될 서버를 의미합니다. MySQL에서 데이터 복제가 일어나는 과정은 아래와 같습니다.
2. 레플리케이션의 동작 원리
소스 서버에서 발생하는 모든 변경사항(이벤트)은 "바이너리 로그 파일"에 순차적으로 기록됩니다. 바이너리 로그 파일에는 변경 내역, 테이블 구조 변경, 계정, 권한 변경 등의 정보가 저장됩니다. 또한, 바이너리 로그 안에 기록된 변경 기록 하나하나를 "바이너리 로그 이벤트"라고 부릅니다. 레플리케이션(복제)는 레플리카 서버가 소스 서버의 바이너리 로그를 읽어와 서버에 순차적으로 적용하는 과정으로 이루어집니다. MySQL의 레플리케이션은 아래의 4개 의 쓰레드로 동작합니다. 트랜잭션 처리 쓰레드와 바이너리 로그 덤프 쓰레드는 소스 서버에서, 레플리케이션 I/O쓰레드와 레플리케이션 SQL 쓰레드는 레플리카 서버에서 실행됩니다.

- 트랙잭션 처리 쓰레드
- SQL 쿼리를 실행에 소스 서버의 데이터에 적용하는 작업을 수행
- 작업이 끝나면 작업한 내용을 "바이너리 로그"에 기록
- 바이너리 로그 덤프 쓰레드
- 레플리케이션 작업이 레플리카 서버로부터 요청되었을 때, 소스 서버로부터 실행
- "바이너리 로그"의 이벤트를 읽고, 레플리카 서버로 전송하는 역할을 수행
- 이벤트를 읽을 때, 바이너리 로그 파일에 대해 잠금을 수행하고, 읽기 작업이 끝나면 잠금을 즉시 해제 함
- 레플리케이션 I/O 쓰레드
- 레플리카 서버에서 레플리케이션 작업이 시작되면, 레플리케이션 I/O 쓰레드가 생성됨
- 레플리케이션 I/O 쓰레드는 소스 서버로부터 바이너리 로그를 가져옴
- 소스 서버로부터 읽어온 바이너리 로그들은 레플리카 서버의 "릴레이 로그"에 저장됨
- 레플리케이션 SQL 쓰레드
- 릴레이 로그에 기록된 이벤트를 읽고 레플리카 서버의 데이터 파일에 반영하는 역할을 수행
바이너리 로그를 저장하는 방식에는 변경이 일어난 데이터를 전부 바이너리 로그에 기록하는 Row 기반 바이너리 로그 포맷과, 데이터 자체를 로그에 저장하지 않고, 데이터 변경에 사용한 SQL 문을 바이너리 로그에 저장하는 Statement기반 바이너리 로그 포맷 등이 있습니다. 또한, 소스 서버로부터 레플리케이션 서버로 복제가 일어날 때, 비동기적으로 복제가 일어나는 방식과 반동기적으로 복제가 일어나는 방식이 있습니다.
3. 구현 개요
MySQL의 레플리케이션 기술을 통해 현재 진행하는 프로젝트의 데이터베이스를 다중화하여 트래픽을 분산하고자 합니다. 하나의 RDS를 사용하는 기존 구조에서 2개의 EC2에 MySQL을 설치하여 데이터베이스를 다중화합니다. 쓰기 요청은 Source 서버로 요청이 되며 읽기 요청은 Replica 서버로 요청이 됩니다.

애플리케이션으로 요청되는 대부분의 쿼리가 읽기 요청이기에 이러한 구조로 Replica 서버를 추가로 증설하면 읽기 성능을 개선할 수 있을 것이라 예상됩니다. 또한, 향후에 레플리케이션 서버를 백업 목적으로 활용하여 소스 서버의 데이터가 유실된 상황에서도 손쉽게 복구할 수 있습니다. 백업용으로 레플케이션 서버를 사용한다면, 레플리케이션은 매우 빠른 속도로 진행되므로 백업용 서버에 스케줄러를 실행하여 간격을 두고 주기적으로 백업하는 구조 구축이 필요합니다.
4. 구현 과정
1. EC2에 MySQL 설치 (Source 서버, Replica 서버)
apt-get update
apt-get install mysql-server
systemctl start mysql
systemctl enable mysql
2. MySQL 외부에서 접속 가능하도록 변경 (Source 서버, Replica 서버)
MySQL은 기본적으로 127.0.0.1 즉, 로컬 호스트에서만 접속할 수 있습니다. 따라서, "/etc/mysql/mysql.conf.d/mysqld.cnf" 에 들어가서 bind-address와 mysqlx-bind-address 설정을 주석처리해야 합니다.
3. Source 서버 설정
- Source 서버 EC2의 MySQL 인스턴스에서 Replica 서버가 복제될 수 있도록 필요한 권한을 부여
/usr/bin/mysql -u root -p # MySQL 클라이언트 실행
CREATE USER 'aurora-db-replica'@'<레플리카 서버 IP>' IDENTIFIED BY '<레플리카 서버 PW>';
GRANT REPLICATION SLAVE ON *.* TO 'aurora-db-replica'@'<레플리카 서버 IP>';
FLUSH PRIVILEGES;
- Source 서버 변경 "/etc/mysql/mysql.conf.d/mysqld.cnf"
server-id=1
log_bin = aurora-bin
sync_binlog = 1
binlog_format = MIXED
replicate-do-db = aurora
- 서버 재시작
systemctl restart mysql
4. 레플리카 서버 변경
- Replica 서버 변경 "/etc/mysql/mysql.conf.d/mysqld.cnf"
server-id= 2 # Source와 무조건 아이디가 달라야 한다.
relay_log=aurora-relay-bin
relay_log_purge=ON
read_only=1
replicate-do-db = aurora
- Source 서버와의 연결을 설정하고, 복제를 시작
STOP REPLICA;
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='<소스 서버 IP>',
SOURCE_USER='aurora-db-replica',
SOURCE_PASSWORD='<소스 서버 PW>',
SOURCE_LOG_FILE='aurora-bin.000001',
SOURCE_LOG_POS=157,
GET_SOURCE_PUBLIC_KEY=1;
START REPLICA;
- Replica 상태 확인
SHOW REPLICA STATUS\G;
5. application.yml 설정
spring:
datasource:
source:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://<소스 서버 IP>:3306/aurora
username: trackers
password: <소스 서버 PW>
maximumPoolSize: 15
poolName: HikariCP
readOnly: false
replica:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://<레플리카 서버 IP>:3306/aurora
username: trackers
password: <레플리카 서버 PW>
poolName: HikariCP
readOnly: true
6. DataSourceConfig.java, RoutingDataSource.java
- 스프링 애플리케이션에서 다중 데이터 소스를 구성하는 파일 설정를 추가합니다
@Profile({"dev", "prod"})
@Configuration
public class DataSourceConfig {
private static final String SOURCE_SERVER = "SOURCE";
private static final String REPLICA_SERVER = "REPLICA";
@Bean
@Qualifier(SOURCE_SERVER)
@ConfigurationProperties(prefix = "spring.datasource.source")
public DataSource sourceDataSource() { return DataSourceBuilder.create().build(); }
@Bean
@Qualifier(REPLICA_SERVER)
@ConfigurationProperties(prefix = "spring.datasource.replica")
public DataSource replicaDataSource() { return DataSourceBuilder.create().build(); }
@Bean
public DataSource routingDataSource(
@Qualifier(SOURCE_SERVER) final DataSource sourceDataSource,
@Qualifier(REPLICA_SERVER) final DataSource replicaDataSource
) {
final RoutingDataSource routingDataSource = new RoutingDataSource();
final HashMap<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(SOURCE, sourceDataSource);
dataSourceMap.put(REPLICA, replicaDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(sourceDataSource);
return routingDataSource;
}
@Bean
@Primary
public DataSource dataSource() {
final DataSource determinedDataSource = routingDataSource(sourceDataSource(), replicaDataSource());
return new LazyConnectionDataSourceProxy(determinedDataSource);
}
}
- 애플리케이션 레벨에서 소스 서버와 레플리카 서버에 대한 요청을 분기하는 코드를 작성합니다.
@Slf4j
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
final String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
final boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if(isReadOnly){
log.info(currentTransactionName + " Transaction:" + "Replica 서버로 요청합니다.");
return REPLICA;
}
log.info(currentTransactionName + " Transaction:" + "Source 서버로 요청합니다.");
return SOURCE;
}
}
5. 트러블 슈팅
1. DataGrip에서 source-db EC2의 mysql에 접속이 안되는 문제
- 원인 : 권한 부여 문제
- 해결 : MySQL 데이터베이스에서 사용자를 생성하고, 권한을 부여
CREATE USER 'trackers'@'%' IDENTIFIED BY '<데이터베이스 PW>';
GRANT ALL PRIVILEGES ON *.* TO 'trackers'@'%';
2. Source DB에는 테이블이 생겼지만 Replica DB에서는 테이블이 생기지 않는 문제
mysql> stop replica;
mysql> set global sql_replica_skip_counter=1;
mysql> start replica;
3. 중간에 복제가 중단되어 source db와 replica db 사이에 데이터 정합성이 생기는 문제

- 원인: 현재 Source DB의 status는 aurora-bin.00007 이지만 Replica DB에서는 aurora-bin.00004에서 문제가 생겨 그 동안 복제가 이루어지지 않아 데이터 정합성 문제가 발생
STOP REPLICA;
CHANGE SOURCE TO MASTER_LOG_FILE='aurora-bin.000007', MASTER_LOG_POS=14038;
START replica;
위 명령어를 통해 복제 지점을 강제로 변경하여 복제는 다시 재개됩니다. 하지만, 그 동안 있었던 트랜잭션이 반영 되지 않아 여전히 source db와 replica db 사이에 정합성 문제가 발생하고 있습니다.
# Source DB에서 데이터 Dump한 백업 sql 생성
$ mysqldump -u [사용자 계정] -p [원본 데이터베이스명] > [생성할 백업 파일명].sql
$ mysqldump -u root -p aurora > 2024_9_13_aurora_backup.sql
# Source 서버에서 scp를 사용해 Replica 서버로 파일 이동
$ scp -i {db pem 키} {이동시킬 파일} ubuntu@{이동시킬 서버 IP}:/home/ubuntu
$ scp -i aurora-db.pem 2024_9_13_aurora_backup.sql ubuntu@{레플리카 서버 IP}:/home/ubuntu
# 복원 전에 Replica 서버에서 DB 생성
mysql> CREATE DATABASE aurora;
# Replica 서버에서 이동 받은 sql 파일을 사용해 데이터 복원
$ mysql -u [사용자 계정] -p [복원할 DB] < [백업된 DB].sql
$ mysql -u root -p aurora < 2024_9_13_aurora_backup.sql
위 과정을 통해 source db에 있는 데이터를 모두 백업하여 replica db에 적용하여 데이터 정합성 문제를 해결 할 수 있습니다.
참고문헌
https://velog.io/@backfox/무뇽이와-알아보는-대규모-데이터-관리-데이터베이스-복제하기리플리케이션-f4pota6h#싱글-리플리카-복제-구성
대규모 데이터 관리 - 데이터베이스 복제하기(리플리케이션)
이 블로그 보고 데이터베이스 설계했다면 얼마나 좋았을까?
velog.io
https://www.youtube.com/watch?v=NPVJQz_YF2A
'Infra' 카테고리의 다른 글
| [Security] Wireguard VPN으로 내 EC2 지키기 (0) | 2025.11.08 |
|---|---|
| [Infra] 실무에서 사용해본 유용한 Docker Swarm 기능들 (6) | 2025.06.26 |
| [Infra] 고가용성 Redis HA 클러스터 구축 (Redis Sentinel) (0) | 2025.05.01 |
| [CI/CD] 배포에 관한 생각 (0) | 2025.04.05 |
| [Infra] 모니터링 서버 구축 (feat. Prometheus, Grafana) (3) | 2024.12.21 |