데이터베이스 과부하를 줄이기 위한 읽기-쓰기 분리 작업

Junyeong·2022년 9월 20일
0

주니어 개발자

목록 보기
8/8
post-thumbnail

데이터베이스 분리,,?

처음 이 과제를 받았을 때는 😧멍- 했다.
분리라고 한다면 어디서부터 시작하는 걸 의미하는 지 몰라서다.

그래서 먼저 키워드로 정리했다. (내가 접한 순서다)

  • Master - Slave
  • Multi Data Source
  • Hikari
  • LocalContainerEntityManagerFactoryBean
  • DetermineLookupKey

🎯 마스터 - 슬레이브

보통 미들웨어에서 고가용성을 위해 복제를 취할 때 이런 개념이 쓰이곤 했다.
간단히 요약하면 하나를 '기준'으로 하고 이후 이 기준을 따라오는 '기타'를 구조로 한다.

spring:
	DB...(생략)
    	Hikari:
        	Master:
            	jdbc-url: 여기가 다름
            	username:
                password:
            Slave:
            	jdbc-url: 마찬가지
            	username:
                password:

그래서 보통 위처럼 디비 설정 내부에 세팅한다.
디비가 분리된다는 건 당연히, 데이터베이스가 2개라는 말이다.
때문에 포트번호가 다르던, 아예 도메인까지 다르던, url 이 달라야한다.

🚩Multi Data Source

학생때는 디비 분리를 할 일이 없으니 한 번도 접해보지 못한 용어다.
의미는 간단하게 데이터 소스를 멀티로 구성한다는 말!

사실 이건 위의 READ - WRITE 와 관련이 있을 수도 있고, 없을 수도 있다.
조금 더 상위 개념이기 때문이다.

실제로 하나의 서비스에 DB를 여러개 세팅한다면, 그 때 쓰이는 것이고 나의 경우엔 READ - WRITE 를 분리하는 작업도 결국 2개를 세팅하는 것과 같기 때문에 필요한 내용이었다.

📖 DetermineLookupKey

바로 이어서 등장하는 메서드다.
오버라이드 해와서 사용하는 메서드인데, 메서드명에서 알 수 있듯이 Multi Data Source 기반에서 어떤 DB를 바라보게 할지 결정해주는 역할을 한다.

public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceType = TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "read" : "write";
        return dataSourceType;
    }
}

read write 부분에 master slave로 적어줘도 좋다.
나는 여기에 debug 레벨의 log를 두어 제대로 구분되고 있는 지에 대한 확인도 추가로 진행했었다.

🎁LocalContainerEntityManagerFactoryBean

얘는 정말 생소하다.
보통 유명한 블로그들에도 그렇고, 대부분의 예제들에서 JPA를 설정할 때 의존성 주입하고 Interface 뽑는 형태로 사용한다.

그런데 사내 코드를 보니 그렇지 않았다.
자세히 파보니 Interface 에 CRUD 를 주지도 않고 Default 값을 넣어주고 있었다.

그래서 이게 어떤 건지에 대한 조사부터 시작했다.
Spring에 따르면 부트가 아닌 스프링에서 jpa 적용하기 위해서 존재했던 걸로 보인다. (기간도 2010년 블로그도 있고..)

의문은 우리는 Spring Boot 를 쓰는데? 이거였다.
더 깊히 구글링해서 알아낸 것은 Multi 로 Data source 를 설정하면 디비가 한개가 아니기 때문에 JPA 를 설정하는데 있어서 커스텀이 필요하고 커스텀을 위해선 위 객체가 필요한 것으로 파악했다.

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean aEntityManagerFactory(@Qualifier("aDataSource") DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
        emfb.setDataSource(dataSource);
        emfb.setPersistenceProvider(new HibernatePersistenceProvider());
        emfb.setPersistenceUnitName("aEntityManager");
        emfb.setPackagesToScan(
                "a DataSource를 이용하는 도메인의 entity가 위치한 패키지"
        );
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setShowSql(true);
        jpaVendorAdapter.setGenerateDdl(false);
        emfb.setJpaVendorAdapter(jpaVendorAdapter);

        return emfb;
    }

출처는 블로그 하단에 있다.
위의 코드에서 보는 것처럼 Manager 를 먼저 생성해주고 거기에 datasource 를 셋팅한다.
그리고 vendorAdapter 를 통해서 jpa셋팅까지 완료해주는 코드다.
그래도 이 코드를 단번에 보고 이해할 수 있어서 나름 뿌듯..^^

🙏Hikari

히카리는 디비 풀인데, 에러가 떴어서 겸사겸사 적었다.

분리작업을 마치고 나면 드는 생각이 이게 잘 됐는지 뭘로 확인하지? 이다.
이에 대한 설명을 적어둔 블로그가 없어서 코드를 읽어보며 찾았다.

분리가 정상적으로 되었다면 로직 수행시 Hikari-0 또는 Hikari-1 과 같이 숫자로 분리된 것을 파악할 수 있다!

보통은 Write 를 Master 로 해두고 Read 는 Lazy 설정을 걸어두니까 처음 서비스 실행 시에는 히카리풀이 1개만 떴다가 조회를 수행하면 이어서 뜨는 걸 확인할 수 있었다.

그리고 문제는 에러.

hikari no operations allowed after connection closed

찾아보니 좋은 해결책들이 많았는데, 결론은 어떤 작업의 수행 시간이 max 를 넘어가서 생긴 문제로 시간을 단축시켜주면 해결된다.

우리 서비스는 9분대로 설정해서 해결했는데, querydsl 관련해서 이슈가 있던 다른 블로거분 덕분에 querydsl 쪽 연결할 때 문제 생기면 어떻게 대응할 지 까지 찾아봐둘 수 있었다.

https://do-study.tistory.com/97


배경 지식들은 공식 문서와 여러 블로그를 통해 배웠지만, 실질적으로 보고 참고한 코드는 아래의 블로그에서 가져왔다.
https://www.4te.co.kr/892

멀티데이터소스와 그 안에서 한번 더 Read-Write 분리 작업까지 마쳐두셨으니, 둘 중 뭐라도 필요한 사람은 들어가서 읽어보고 필요한 내용만 커스텀해서 사용하면 된다! 정말.. 감사한 글 😁😃

profile
좋아하는 것을 계속 좋아하자.

0개의 댓글