master & slave database 구조

업데이트:


Master & Slave Database란?

Master-Slave 데이터베이스 구조는 하나의 주 데이터베이스(Master)와 하나 이상의 복제 데이터베이스(Slave)로 구성됩니다.



Master Database

  • Master 데이터베이스는 읽기 및 쓰기 작업(INSERT, UPDATE, DELETE)을 처리하는 주 데이터베이스입니다.
  • 모든 데이터 변경 작업은 Master 데이터베이스에서 수행됩니다.



Slave Database

  • Slave 데이터베이스는 읽기 작업(SELECT)을 처리하는 데이터베이스입니다.
  • Master의 데이터를 복제하여 데이터를 동기화합니다.



장점

⓵ 로드 밸런싱 읽기 작업을 Slave 데이터베이스로 분산하여 Master 데이터베이스의 부하를 줄일 수 있습니다.

⓶ 데이터 백업 Slave는 실시간으로 Master의 데이터를 복제하므로 백업역할을 합니다.

⓷ 데이터 안정성 Master 데이터베이스에 장애가 발생하더라도 Slave 데이터베이스가 데이터를 보유하고 있으므로 데이터 손실을 방지할 수 있습니다.


단점

⓵ 지연 시간 Master 데이터베이스의 데이터 변경 작업이 Slave 데이터베이스로 복제되는 시간이 소요됩니다.

⓶ 구조가 복잡해져 관리가 어려워질 수 있습니다.



구현 방법

@Transactional(readOnly = true) 인 경우는 Slave DB 접근
@Transactional(readOnly = false) 인 경우는 Master DB 접근

위와 같은 방식으로 구현을 합니다.

1. application.yml설정

spring:
  datasource:
    master:
      jdbc-url: ${DATABASE_URL}
      username: ${DATABASE_USERNAME}
      password: ${DATABASE_PASSWORD}
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        maximum-pool-size: 10
        minimum-idle: 5
    slave:
      jdbc-url: ${DATABASE_SLAVE_URL}
      username: ${DATABASE_SLAVE_USERNAME}
      password: ${DATABASE_SLAVE_PASSWORD}
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        maximum-pool-size: 20
        minimum-idle: 10

해당 yml파일에 설정해 둔 것과 같이 DB에 접근하기 위해 접근 정보를 정의합니다.


2.  Master, Slave DataSource 설정


@Configuration
@Slf4j
public class DataSourceConfig {

	@Bean("masterDataSource")
	@ConfigurationProperties(prefix = "spring.datasource.master")
	public DataSource masterDataSource() {
		return DataSourceBuilder.create().type(HikariDataSource.class).build();
	}

	@Bean("slaveDataSource")
	@ConfigurationProperties(prefix = "spring.datasource.slave")
	public DataSource slaveDataSource() {
		return DataSourceBuilder.create().type(HikariDataSource.class).build();
	}

	@Bean
	@Primary
	@DependsOn({"masterDataSource", "slaveDataSource"})
	public DataSource routingDataSource(
		@Qualifier("masterDataSource") DataSource masterDataSource,
		@Qualifier("slaveDataSource") DataSource slaveDataSource) {

		RoutingDataSource routingDataSource = new RoutingDataSource();

		Map<Object, Object> targetDataSources = new HashMap<>();
		targetDataSources.put("master", masterDataSource);
		targetDataSources.put("slave", slaveDataSource);

		routingDataSource.setTargetDataSources(targetDataSources);
		routingDataSource.setDefaultTargetDataSource(masterDataSource);

		// 데이터 소스 설정에 대한 로깅 추가
		log.info("Configured RoutingDataSource with master and slave data sources");

		return new LazyConnectionDataSourceProxy(routingDataSource);
	}
}

위와 같이 DataSourceConfig클래스를 생성하여 Master, Slave DataSource를 Bean 으로 등록합니다.


3.  RoutingDataSource 설정

@Slf4j
public class RoutingDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		String dataSourceType = TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "slave" : "master";
		MDC.put("datasource", dataSourceType);
		try {
			return dataSourceType;
		} finally {
			MDC.remove("datasource"); // 컨텍스트 정리
		}
	}

}

RoutingDataSource클래스를 생성하여 AbstractRoutingDataSource를 상속받아 determineCurrentLookupKey()메서드를 오버라이딩하여 Master, Slave DataSource를 구분합니다.


2번과정에서 LazyConnectionDataSourceProxy로 감싼이유는

TransactionManager -> LazyConnectionDataSourceProxy에서 Connection Proxy 객체 획득 -> Transaction 동기화 -> Connection Proxy 객체 반환
  • LazyConnectionDataSourceProxy는 실제 커넥션을 가져오는 시점까지 커넥션을 생성하지 않습니다.
  • 이를 통해 Master, Slave DataSource를 구분하여 사용할 수 있습니다.


참고사이트

https://k3068.tistory.com/102

댓글남기기