읽기-쓰기 분리 도입의 필요성
단일 데이터베이스 서버가 모든 읽기 및 쓰기 요청을 처리하는 환경에서는 시스템 부하가 증가함에 따라 여러 가지 문제가 발생할 수 있습니다. 특히, 동시 접속자 수가 많아지거나 데이터 처리량이 급증할 경우, 데이터베이스는 성능 저하를 겪게 됩니다. 또한, 단일 서버의 디스크 손상이나 시스템 장애는 전체 서비스 중단 및 데이터 손실로 이어질 수 있는 단일 장애점(SPOF)을 형성합니다.
이러한 문제들을 해결하고 데이터베이스의 확장성과 가용성을 높이기 위한 전략 중 하나가 바로 읽기-쓰기 분리(Read-Write Separation)입니다. 읽기-쓰기 분리는 데이터베이스의 읽기 작업과 쓰기 작업을 각각 다른 서버로 분산시켜 전체 시스템의 처리 능력을 향상시키는 기법입니다.
MySQL 마스터-슬레이브 복제 개요
MySQL 읽기-쓰기 분리의 핵심 기반 기술은 마스터-슬레이브(Master-Slave) 복제입니다. MySQL 복제는 비동기 방식으로 동작하며, MySQL 데이터베이스의 내장 기능인 바이너리 로그(Binary Log)를 활용합니다. 이는 마스터 데이터베이스(Master)에서 발생하는 모든 데이터 변경 사항을 하나 이상의 슬레이브 데이터베이스(Slave)로 복사하고 적용하여, 슬레이브 데이터베이스가 마스터 데이터베이스와 동일한 데이터를 유지하도록 합니다.
MySQL 복제 과정
MySQL 복제는 다음과 같은 3단계 과정을 거쳐 이루어집니다.
- 마스터 서버에서 바이너리 로그 생성: 마스터 데이터베이스에서 INSERT, UPDATE, DELETE와 같은 데이터 변경 작업이 발생하면, 해당 변경 내역이 바이너리 로그에 기록됩니다.
- 슬레이브 서버로 바이너리 로그 전송: 슬레이브 서버의 I/O 스레드가 마스터 서버에 연결하여 마스터의 바이너리 로그를 자신의 릴레이 로그(Relay Log)로 복사합니다.
- 슬레이브 서버에서 릴레이 로그 적용: 슬레이브 서버의 SQL 스레드가 릴레이 로그의 내용을 순차적으로 읽어 자신의 데이터베이스에 적용함으로써 마스터와 동일한 데이터 상태를 유지합니다.
MySQL 복제 및 읽기-쓰기 분리 구성 예시
일반적인 읽기-쓰기 분리 환경은 하나의 마스터 서버와 여러 개의 슬레이브 서버로 구성됩니다. 마스터 서버는 모든 쓰기(INSERT, UPDATE, DELETE) 요청을 처리하고, 슬레이브 서버들은 읽기(SELECT) 요청을 담당하게 됩니다.
마스터 서버 설정
1. MySQL 설정 파일 수정 (/etc/my.cnf)
마스터 서버의 MySQL 설정 파일(예: /etc/my.cnf)을 열어 바이너리 로깅을 활성화하고 고유한 서버 ID를 지정합니다.
[mysqld]
log_bin = mysql_bin #[필수] 바이너리 로그 활성화
server-id = 101 #[필수] 서버 고유 ID (중복되지 않아야 함)
2. MySQL 서비스 재시작
설정 변경 사항을 적용하기 위해 MySQL 서비스를 재시작합니다.
systemctl restart mysqld
3. 복제 사용자 계정 생성 및 권한 부여
슬레이브 서버가 마스터 서버의 바이너리 로그에 접근할 수 있도록 특정 사용자를 생성하고 REPLICATION SLAVE 권한을 부여합니다. 이 예시에서는 replication_user를 생성합니다.
CREATE USER 'replication_user'@'%' IDENTIFIED BY 'StrongP@ssw0rd1';
GRANT REPLICATION SLAVE ON *.* TO 'replication_user'@'%';
FLUSH PRIVILEGES;
4. 마스터 서버의 바이너리 로그 상태 확인
현재 마스터 서버의 바이너리 로그 파일명과 위치를 확인합니다. 이 정보는 슬레이브 서버 설정 시 필요합니다.
SHOW MASTER STATUS;
실행 결과에서 File과 Position 값을 기록해둡니다. 예: File: mysql-bin.000001, Position: 1234
슬레이브 서버 설정
1. MySQL 설정 파일 수정 (/etc/my.cnf)
슬레이브 서버의 MySQL 설정 파일에도 고유한 서버 ID를 지정합니다. 이 ID는 마스터 서버 ID와 달라야 합니다.
[mysqld]
server-id = 102 #[필수] 서버 고유 ID (마스터와 달라야 함)
2. MySQL 서비스 재시작
설정 변경 사항을 적용하기 위해 MySQL 서비스를 재시작합니다.
systemctl restart mysqld
3. 마스터 정보 설정 및 슬레이브 시작
슬레이브 서버에 마스터 서버의 연결 정보를 설정하고 복제를 시작합니다. 앞서 기록한 마스터 서버의 IP 주소, 복제 사용자 정보, 바이너리 로그 파일명 및 위치를 사용합니다.
CHANGE MASTER TO
MASTER_HOST='192.168.1.100',
MASTER_USER='replication_user',
MASTER_PASSWORD='StrongP@ssw0rd1',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=1234;
START SLAVE;
MASTER_HOST는 마스터 서버의 IP 주소로, MASTER_LOG_FILE과 MASTER_LOG_POS는 마스터 서버의 SHOW MASTER STATUS; 결과 값으로 대체해야 합니다.
4. 슬레이브 복제 상태 확인
슬레이브 서버에서 복제가 정상적으로 동작하는지 확인합니다.
SHOW SLAVE STATUS\G
결과에서 Slave_IO_Running: Yes와 Slave_SQL_Running: Yes를 확인하면 복제가 정상적으로 실행 중임을 의미합니다. Last_IO_Error나 Last_SQL_Error 필드에 오류 메시지가 없는지도 확인해야 합니다.
애플리케이션에서 읽기-쓰기 분리 구현 (ShardingSphere-JDBC 활용)
MySQL 마스터-슬레이브 복제를 설정한 후에는 애플리케이션 레벨에서 읽기-쓰기 분리 로직을 적용해야 합니다. 애플리케이션이 쓰기 쿼리는 마스터로, 읽기 쿼리는 슬레이브로 자동으로 라우팅하도록 돕는 라이브러리가 필요합니다. Apache ShardingSphere의 한 모듈인 ShardingSphere-JDBC가 이러한 기능을 효율적으로 제공합니다.
ShardingSphere-JDBC 소개
ShardingSphere-JDBC는 경량의 자바 프레임워크로, JDBC 계층에 추가 서비스를 제공합니다. 이 라이브러리는 애플리케이션 내부에서 JDBC 드라이버처럼 동작하며, 데이터베이스에 직접 연결합니다. 별도의 서버 배포나 의존성 없이 JAR 형태로 제공되어, JDBC 및 다양한 ORM 프레임워크(JPA, Hibernate, MyBatis, Spring JDBC Template 등)와 완벽하게 호환됩니다. 또한, DBCP, C3P0, Druid, HikariCP와 같은 모든 표준 JDBC 연결 풀을 지원하며, MySQL, Oracle, SQL Server, PostgreSQL 등 JDBC 규격을 따르는 데이터베이스에서 사용할 수 있습니다.
ShardingSphere-JDBC를 이용한 읽기-쓰기 분리 단계
1. Maven/Gradle 의존성 추가
프로젝트의 pom.xml (Maven) 또는 build.gradle (Gradle) 파일에 ShardingSphere-JDBC 의존성을 추가합니다. Spring Boot 환경에서는 다음을 사용할 수 있습니다.
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-spring-boot-starter</artifactId>
<version>5.3.0</version> <!-- 최신 안정 버전 사용 권장 -->
</dependency>
2. 애플리케이션 설정 파일에 읽기-쓰기 분리 규칙 정의
Spring Boot의 application.yml 또는 application.properties 파일에 마스터 및 슬레이브 데이터소스 정보를 정의하고, ShardingSphere-JDBC가 이를 활용하여 읽기-쓰기 분리 규칙을 적용하도록 설정합니다. 이는 일반적으로 데이터소스 이름, 마스터-슬레이브 그룹 지정, 부하 분산 전략(예: 라운드 로빈) 등을 포함합니다.
spring:
shardingsphere:
datasource:
names: master_db,slave_db_0,slave_db_1
master_db:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.100:3306/your_db?useSSL=false&serverTimezone=UTC
username: root
password: your_master_password
slave_db_0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.101:3306/your_db?useSSL=false&serverTimezone=UTC
username: root
password: your_slave_password
slave_db_1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.102:3306/your_db?useSSL=false&serverTimezone=UTC
username: root
password: your_slave_password
rules:
read-write-splitting:
data-sources:
ds_0: # 논리적 데이터소스 이름
transaction-type: XA
auto-aware-data-source-name: master_db # 마스터 데이터소스 이름
slave-data-source-names: slave_db_0,slave_db_1 # 슬레이브 데이터소스 이름 목록
load-balancer:
type: ROUND_ROBIN # 로드 밸런싱 전략 (RANDOM, ROUND_ROBIN 등)
3. Spring Bean 정의 오버라이딩 설정 (선택 사항)
Spring Boot 환경에서 ShardingSphere-JDBC를 사용할 때, 기존에 정의된 데이터소스 Bean과 ShardingSphere가 생성하는 데이터소스 Bean이 충돌하여 오류가 발생할 수 있습니다. 이를 방지하기 위해 application.yml에 다음 설정을 추가할 수 있습니다.
spring:
main:
allow-bean-definition-overriding: true
위와 같이 설정하면, 애플리케이션의 쓰기 작업은 마스터 데이터베이스로, 읽기 작업은 슬레이브 데이터베이스로 자동 라우팅되어 효과적인 읽기-쓰기 분리가 이루어집니다.