SW 개발/Spring
Spring Boot, JPA, 커넥션 풀을 read-write, read-only로 분리하기
지단로보트
2021. 12. 14. 23:00
개요
- MySQL 데이터베이스의 Master-Slave 레플리케이션은, 조회만 발생하는 트랜잭션은 복수개의 Slave 노드로, 조회 뿐만 아니라 데이터베이스 변경점이 발생하는 트랜잭션은 1개의 Master 노드로 요청되어야 한다. 이번 글에서는 Spring Boot, JPA 환경에서 커넥션 풀을 read-write, read-only로 분리하는 방법을 설명하고자 한다.
- 특히, Amazon Aurora MySQL의 Single-Master 레플리케이션을 사용할 경우, 최대 15개의 레플리카 인스턴스를 운영할 수 있는데 애플리케이션 레벨에서 read-only 트랜잭션을 레플리카 엔드포인트를 바라보도록 설정하면 조회 성능을 크게 향상시킬 수 있다.
기본 흐름
- 어노테이션에 따라 커넥션 풀을 분기해주는 클래스를 직접 제작하거나, 이미 공개된 것을 적용한다. 본 예제에서는 권남 님의
LazyReplicationConnectionDataSourceProxy
클래스를 사용하여 제작했다. [관련 링크] @Transactional
이 명시된 클래스 또는 메써드는 read-write 커넥션 풀을 사용하게 된다.@Transactional(readOnly = true)
이 명시된 클래스 또는 메써드는 read-only 커넥션 풀을 사용하게 된다.
application.yml
- 환경 설정 프로필은 아래와 같이 작성한다. 최대한 Spring Boot의 기본 골격을 유지하는 방향으로 제작했다.
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url:
read-write: {read-write-url}
read-only: {read-only-url}
username: {username}
password: {password}
hikari:
maximum-pool-size: 30
auto-commit: false
connection-init-sql: SET NAMES utf8mb4
DatabaseConfiguration 클래스
- 이제
DataSource
빈을 생성할 차례이다. read-write, read-only 2개의 DataSource 빈을 생성한 후, 앞단에서 프록시로 작동하는 DataSource 빈을 생성하는 것이 목적이다.
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@EnableJpaRepositories({"{your-repository-package}"})
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement
public class DatabaseConfiguration {
@Value("${spring.datasource.driver-class-name}")
private String DRIVER_CLASS_NAME;
@Value("${spring.datasource.url.read-write}")
private String READ_WRITE_URL;
@Value("${spring.datasource.url.read-only}")
private String READ_ONLY_URL;
@Value("${spring.datasource.username}")
private String USERNAME;
@Value("${spring.datasource.password}")
private String PASSWORD;
@Value("${spring.datasource.hikari.maximum-pool-size}")
private int MAXIMUM_POOL_SIZE;
@Value("${spring.datasource.hikari.auto-commit}")
private boolean AUTO_COMMIT;
@Value("${spring.datasource.hikari.connection-init-sql}")
private String CONNECTION_INIT_SQL;
@Bean(name = "readWriteDataSource")
public DataSource readWriteDataSource() {
return this.buildDataSource(
DRIVER_CLASS_NAME,
READ_WRITE_URL,
USERNAME,
PASSWORD,
"read-write",
MAXIMUM_POOL_SIZE,
AUTO_COMMIT,
CONNECTION_INIT_SQL
);
}
@Bean(name = "readOnlyDataSource")
public DataSource readDataSource() {
return this.buildDataSource(
DRIVER_CLASS_NAME,
READ_ONLY_URL,
USERNAME,
PASSWORD,
"read-only",
MAXIMUM_POOL_SIZE,
AUTO_COMMIT,
CONNECTION_INIT_SQL
);
}
@Primary
@Bean(name = "dataSource")
public DataSource dataSource(
@Qualifier("readWriteDataSource") DataSource readWriteDataSource, @Qualifier("readOnlyDataSource"
) DataSource readOnlyDataSource) {
return new LazyReplicationConnectionDataSourceProxy(readWriteDataSource, readOnlyDataSource);
}
private DataSource buildDataSource(
String driverClassName,
String jdbcUrl,
String username,
String password,
String poolName,
int maximumPoolSize,
boolean autoCommit,
String connectionInitSql
) {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClassName);
config.setJdbcUrl(jdbcUrl);
config.setUsername(username);
config.setPassword(password);
config.setPoolName(poolName);
config.setMaximumPoolSize(maximumPoolSize);
config.setAutoCommit(autoCommit);
config.setConnectionInitSql(connectionInitSql);
return new HikariDataSource(config);
}
}