MySQL RDS 읽기전용 replica 적용

형기브·2023년 9월 2일
0

AWS

목록 보기
3/3
post-thumbnail

읽기 전용 replica란?

하나의 mysql db만 운영하면 모든 crud작업을 혼자 감당해야합니다.
그래서 보통 db의 읽기 작업인 select문의 요청이 많은 것을 감안해서
읽기 전용 복제본을 만드는 것입니다.
복제본은 여러개를 만들수도 있으나 본 글에서는 하나만 만들도록 하겠습니다.

Multi AZ ( 다중 availability zone )이란 쉽게말해서 백업 DB를 만들어서
백업 DB가 실제 쓰이지는 않지만 본 DB의 내용을 동기화 하고 있다고 생각 하시면 됩니다.

RDS를 사용중이라 콘솔에서 간단하게 생성할 것입니다.
백업과 자동승격에 대한 내용은 본 글에서는 다루지 않겠습니다만...
이 부분은 그냥 master인스턴스에 multi AZ 을 적용하면 자동으로 백업을 해두고
master에 이상이 생기면 백업(스탠바이)이 자동으로 master를 대체합니다.
즉 읽기 전용 replica가 master로 승격되는 것은 아닙니다.


실전 적용하기

서론이 길었습니다. 본격적으로 한 단계씩 해보겠습니다.


aws RDS콘솔에서 읽기전용 replica 만들기

RDS에서 이미 생성된 본 DB인스턴스를 선택한 후 작업-읽기 전용 복제본 생성을 눌러줍니다.

인스턴스 식별자 : 그냥 아무 이름이나 설정해 줍니다.

인스턴스 구성 : 버스터블 클래스 , db.t3.micro를 선택했습니다.

가용성 : 다중 AZ DB 인스턴스. 위에서 설명한 것 처럼 replica도 백업을 만들어 놓는 것입니다.

네트워크 : 퍼블릭 액세스 가능을 눌러줍니다.

데이터베이스 인증 : master DB에서도 암호인증을 했기 때문에 똑같이 암호인증으로 합니다.

설정은 원하시는 대로 맞춰서 하시면 됩니다.


Spring 프로잭트에 적용하기

이렇게 만들기만 해서는 실제 적용이 되지 않습니다.

프로젝트에 적용하기 위해선 DataSourceConfig, RoutingDataSource 클래스를 작성해주어야합니다.

대부분의 코드가 다 비슷하지만 저는 가장 간단한 방법을 사용했습니다.

@Slf4j
@Configuration
public class DataSourceConfiguration {

	private static final String MASTER_SERVER = "MASTER";
	private static final String REPLICA_SERVER = "REPLICA";

	@Bean
	@Qualifier(MASTER_SERVER)
	@ConfigurationProperties(prefix = "spring.datasource.master")
	public DataSource masterDataSource() {
		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(MASTER_SERVER) DataSource masterDataSource, 
		@Qualifier(REPLICA_SERVER) DataSource replicaDataSource
	) {
		RoutingDataSource routingDataSource = new RoutingDataSource(); 

		HashMap<Object, Object> dataSourceMap = new HashMap<>(); 
		dataSourceMap.put("master", masterDataSource);
		dataSourceMap.put("replica", replicaDataSource);

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

		return routingDataSource;
	}

	@Bean
	@Primary
	public DataSource dataSource() {
		DataSource determinedDataSource = routingDataSource(masterDataSource(), replicaDataSource());
		return new LazyConnectionDataSourceProxy(determinedDataSource);
	}

}

코드설명

master와 replica의 DateSource를 각각 빈으로 등록해 줍니다.

@Qualifier 같은 타입의 빈을 등록할 때 구분을 해주기위해 적어줍니다.

@ConfigurationProperties(prefix = "spring.datasource.master")
이 부분은 properties 혹은 yml에 있는 db 설정를 읽어오기 위해 적어줍니다.

spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.jdbc-url=jdbc:mysql://master DB host 주소:포트/db이름
spring.datasource.master.username=유저네임
spring.datasource.master.password=비밀번호

spring.datasource.replica.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.replica.jdbc-url=jdbc:mysql://replica DB host 주소:포트/db이름
spring.datasource.replica.username=유저네임
spring.datasource.replica.password=비밀번호

(저는 이 properties의 정보를 계속 읽어오지 못해서 2일동안 온갖 삽질을 했었습니다..
원인은 spring.datasource.master.jdbc-url 여기서 jdbc-url을 그냥 url로 한게 원인이었습니다.
스프링부트의 자동구성이 읽지 못하는게 원인이라는데 참 어렵네요...)

RoutingDataSource객체를 새로 생성하는 걸 볼 수 있는데요. 어떤 쿼리가 오면 master로 갈지 replica로 갈지 결정해 주는 것입니다.
이 클래스는 아래에서 다시 설명하겠습니다.

@Primary가 붙은 DataSource를 주 데이터 소스로 취급합니다.
LazyConnectionDataSourceProxy : DB연결은 비용이 많이 드는 작업입니다. 그래서 실제로 쿼리를 실행하기 전까지 연결을 늦추는 것입니다. 이 부분은 사실 지금 우리가 하고있는 routing과는 상관없이 성능 향상을 위해서 적은 코드입니다.

@Slf4j
public class RoutingDataSource extends AbstractRoutingDataSource {
	@Nullable
	@Override
	protected Object determineCurrentLookupKey() {
		String lookupKey = TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "replica" : "master";
		log.info("Current DataSource is {}", lookupKey);
		return lookupKey;
	}
}

위에서 생성해준 RoutingDataSource 을 정의하는 클래스입니다. AbstractRoutingDataSource를 추상화 하고 있습니다.
isCurrentTransactionReadOnly 이 메서드로 트랜젝션이 readOnly=true일 때는
replica를 반환합니다.

실제 replica를 쓸지 master를 쓸지 결정하는 로직은 AbstractRoutingDataSource에 구현되어있습니다.
lookupKey 값은 AbstractRoutingDataSource에 내장된 로직에 의해 setTargetDataSources에서 설정한 dataSourceMap을 조회할 때 사용됩니다.
앞서 dataSourceMap에 replica와 master를 넣었었죠??

읽기 전용 메서드에 readOnly = true 달기

( DataSourceFactory 패턴을 사용하면 readOnly를 꼭 달아야 하는 것은 아니지만
저는 이 방법을 쓰지 않았기 때문에 읽기전용 메서드에 모두 어노테이션을 달아주었습니다;; ㅜ_ㅜ)

@Transactional(readOnly = true)
	public List<UserInfoDto> getToUsers(Long userId) {

이런식으로 어노테이션을 달아준 뒤 요청을 보내고 로그를 확인해보면.

replica로 잘 가고 있는 것을 볼 수 있습니다.
마찬가지로 쓰기 작업을 하면 master로 조회를 합니다.

profile
Slow but Steady

0개의 댓글