DB Replication
읽기 부하분산과 데이터 백업을 위해 데이터베이스 이중화를 진행했다.
개발환경은 아래와 같다.
- Spring boot 2.7.14
- MySQL 8.0.33 (RDS)
- Spring Data JPA
RDS 설정
현재는 이미 복제가 완료 되었지만, 손쉽게 진행할 수 있다.
버튼 하나만 누르면 Master DB와 DB 계정과 데이터부터 보안그룹까지 똑같은 환경으로 복제된다.
Master DB는 쓰기와 수정 전용, 복제본은 읽기 전용으로 활용한다.
Spring application.yml 수정
spring:
datasource:
master:
hikari:
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://{master db url}:{port}/{schema}
readOnly: false
username: {username}
password: {password}
replica:
hikari:
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://{replica db url}:{port}/{schema}
readOnly: true
username: {username}
password: {password}
메인 DB, 레플리카 DB 설정
@Configuration
public class DataSourceConfig {
@Primary
@Bean(MASTER_DATE_SOURCE)
@ConfigurationProperties(prefix = MASTER_PREFIX)
public DataSource masterDataSource() {
return DataSourceBuilder
.create()
.type(HikariDataSource.class)
.build();
}
@Bean(REPLICA_DATE_SOURCE)
@ConfigurationProperties(prefix = REPLICA_PREFIX)
public DataSource replicaDataSource() {
return DataSourceBuilder
.create()
.type(HikariDataSource.class)
.build();
}
}
yml을 적절히 읽어 설정해준다.
읽기 / 쓰기 설정
public enum DataSourceType {
MASTER,
REPLICA;
}
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
return DataSourceType.REPLICA;
}
return DataSourceType.MASTER;
}
}
@EnableJpaRepositories(
basePackages = BASE_PACKAGES,
entityManagerFactoryRef = ENTITY_MANAGER_FACTORY,
transactionManagerRef = TRANSACTION_MANAGER
)
@Configuration
public class RoutingDataSourceConfig {
@Bean(ROUTING_DATA_SOURCE)
public DataSource routingDataSource(
@Qualifier(MASTER_DATE_SOURCE) final DataSource master,
@Qualifier(REPLICA_DATE_SOURCE) final DataSource replica
) {
RoutingDataSource routingDataSource = new RoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.MASTER, master);
dataSourceMap.put(DataSourceType.REPLICA, replica);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(master);
return routingDataSource;
}
@Bean(DATA_SOURCE)
public DataSource dataSource(@Qualifier(ROUTING_DATA_SOURCE) DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}
@Bean(ENTITY_MANAGER_FACTORY)
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
@Qualifier(DATA_SOURCE) DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setPackagesToScan(BASE_PACKAGES);
entityManagerFactory.setJpaVendorAdapter(this.jpaVendorAdapter());
entityManagerFactory.setPersistenceUnitName(ENTITY_MANAGER);
return entityManagerFactory;
}
private JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setGenerateDdl(false);
adapter.setShowSql(false);
adapter.setDatabasePlatform(HIBERNATE_DIALECT);
return adapter;
}
@Bean(TRANSACTION_MANAGER)
public PlatformTransactionManager platformTransactionManager(
@Qualifier(ENTITY_MANAGER_FACTORY) LocalContainerEntityManagerFactoryBean emf
) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(emf.getObject());
return jpaTransactionManager;
}
}
LazyConnctionDataSourceProxy
Spring에서는 @Transaction에 진입하면 자동으로 DB 커넥션을 사용한다.
따라서 DataSource가 여러개라면 실제 커넥션이 필요할 때 DataSource를 선택하지 못한다.
LazyConnectionDataSourceProxy을 사용하면 실제로 필요한 시점에만 커넥션을 점유하게 할 수 있다.
읽기 / 쓰기 전용 DB가 나눠져 있을 때 유용하게 쓰일 수 있다.
상수
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class DbConstUtil {
public static final String PROFILE_PROD = "prod";
public static final String BASE_PACKAGES = "xyz.iwasacar.api.domain";
public static final String MASTER_DATE_SOURCE = "masterDataSource";
public static final String REPLICA_DATE_SOURCE = "replicaDataSource";
public static final String MASTER_PREFIX = "spring.datasource.master.hikari";
public static final String REPLICA_PREFIX = "spring.datasource.replica.hikari";
public static final String ROUTING_DATA_SOURCE = "routingDataSource";
public static final String DATA_SOURCE = "dataSource";
public static final String ENTITY_MANAGER_FACTORY = "entityManagerFactory";
public static final String TRANSACTION_MANAGER = "transactionManager";
public static final String ENTITY_MANAGER = "entityManager";
public static final String HIBERNATE_DIALECT = "org.hibernate.dialect.MySQL8Dialect";
}
처음 yml을 설정할 때 replica의 depth가 잘못되어 조금 헤맸다. 이 점을 주의하자.
또한, RDS를 활용해서 쉽게 복제된 DB를 만들 수 있었는데, 나중에는 이 작업도 진행해보고 싶다.
'우리 FISA' 카테고리의 다른 글
우리FISA 클라우드 서비스 개발 - 프로젝트 시작 전 CI/CD 파이프라인 만들기 (0) | 2023.08.20 |
---|---|
우리FISA 클라우드 서비스 개발 - 프로젝트 시작과 프로젝트 산출물 (0) | 2023.08.13 |
우리FISA 클라우드 서비스 개발 - Spring Framework에서 테스트 (0) | 2023.08.06 |
우리FISA 클라우드 서비스 개발 - EC2, Docker, Jenkins를 사용한 Spring-boot 어플리케이션 배포 (0) | 2023.07.30 |
댓글