티스토리 뷰

개요

제가 만들고 있는 Helparty는 대용량 트래픽이 요청되는 상황에서도 안정적인 운영을 보이도록 설계하고 있습니다. 

그러기 위해서는 프로그램에서 성능 이슈에 밀접하게 연관된 데이터베이스 영역을 눈여겨 봐야합니다. 

지금의 데이터베이스는 하나의 하드웨어에서 모든 쿼리 연산을 책임지고 있습니다. 

하나의 하드웨어만 잘못되도 프로그램 전체가 멈추게 되는 불안정한 구조이고 성능적인 면에서도 이곳에서 문제가 발생하면 프로그램 전체가 느려지는 병목 지점이 됩니다. 

따라서 저는 데이터베이스 Replication을 만들어서 쿼리의 성격에 따라 분기를 하여 두대의 데이터베이스로 쿼리를 보내려고 합니다.

이렇게 함으로써 하나의 데이터베이스에서 모든 쿼리의 작업을 도맡아 했던 걸 2개의 데이터베이스가 나눠서 작업을 처리하므로 당연하게 성능이 올라갈 것이며 하나의 데이터 베이스가 고장나더라도 다른 하나를 사용하면 되므로 고장이나 에러에도 취약하지 않게 됩니다. 

 

그럼 이제 어떻게 이중화를 할건지 구조를 알아보겠습니다.

 

데이터베이스 이중화 구조

 

 

데이터 베이스의 이중화를 먼저 그림으로 대강 흐름을 보면 위 그림과 같습니다.

서버에서 db로 쿼리를 보낼 때 일단 이 쿼리가 readOnly인지 살펴봅니다. 만약 readOnly면 Slave 데이터베이스로 쿼리를 보내고 그렇지 않으면 Master로 쿼리를 보냅니다. 

 

보내는 쿼리의 성격에 따라서 다른 db를 사용하므로 db의 작업 부담감도 줄고 성능도 좋아지는 효과를 볼 수 있습니다.

 

그럼 이제 방식과 결과를 알아봤으니 이제 이것의 원리를 알아보겠습니다. 

 

데이터베이스 이중화의 원리

 

Master

1. DML이 Master에 작업을 요청하면 DataSource를 통해 받은 Connection으로 작업을 진행합니다. 

2. 이때 Master에서 작업되는 모든 명령어들은 Binary Log에 저장됩니다. 

3. 이제 Master의 DB Storage에서 데이터를 조작하는 쿼리를 작업하고 커밋합니다. 

 

Slave

4. 이제 Master의 Binary Log를 전달받아 Slave의 Relay Log에 저장합니다. 

5. 마지막으로 Relay Log에 있는 쿼리 명령어들을 Slave의 DB Storage에 똑같이 작업합니다.  

 

결과적으로 Slave와 Master의 데이터는 같게 됩니다. 

 

이제 저의 프로젝트 Helparty에 데이터베이스 이중화를 적용해보겠습니다.

 

Helparty에 데이터베이스 이중화 적용

 

Master DataSource, Slave DataSource빈 등록

먼저, master와 slave 데이터베이스를 연결해줄 DataSource를 빈으로 등록합니다. 

 

masterDataSource
slaveDataSource

 

RoutingDataSource 설정

그리고 이 DataSource들을 routing 해 줄 RoutingDataSource를 만들고 그 안에 만들어 놓은 DataSource들을 넣어줍니다. 

 

이제 RoutingDataSource를 보면 

이렇게 determineCurrnetLookupKey() 메서드만을 오버라이딩 해주면 됩니다.

 

이런 식으로 분기되어 정해진 DataSource에서 Connection을 받게 됩니다. 

 AbstractRoutingDataSource를 클래스를 들어가서 보면 더 쉽게 이해가 될 겁니다.

 

getConnection()
determineTargetDataSource()
determineCurrentLookupKey()

 

맨 아래 그림의 determineCurrentLookupKey를 보면 추상 클래스인 것을 알 수 있습니다. 

그래서 제가 Slave인지 Master인지 재정의 해준 것입니다.

 

ProxyDataSource 설정

이제 TransactionManager에 DataSource를 넘겨주기 전에 해야할 한가지 작업이 있습니다. 

바로 connection을 얻는 시점을 조정하는 것입니다. 

 

문제

원래 connection은 DataSource로 부터 Transaction이 시작할 때 얻게 됩니다. 

하지만 제가 작성한 코드에서는 Transaction이 시작할 때 connection을 얻으려 하면 에러를 나올겁니다. 

왜냐하면 트랜잭션이 readOnly인지를 판별하고 분기해서 얻은 DataSource로 부터 connection을 얻어야 하는데 readOnly인지 판별하는 작업 이전에 connection을 얻으려하기 때문입니다. 

그림으로 표현하면 대략 이렇습니다. connection을 얻기 위해서는 동기화를 해야하는데 원래는 동기화는 Transaction이 시작될 때 connection을 얻고 시작되기 때문에 순서가 모순적이게 된 겁니다.

 

 해결

해결법은 간단합니다. Connection을 트랜잭션이 시작할 때 얻는게 아니라 쿼리를 보낼 때 얻도록 시점을 바꾸는 것입니다. 

 

 

TransactionManager한테 DataSource를 보낼 때 먼저 프로시를 한번 더 감싸서 LazyConnection으로 바꿔주고 전달해줍니다. 

 

이제 SqlSessionFactory에 dataSource를 전달해주고 모든 작업을 마무리 합니다. 

 

 

Finish

프록시 객체와 지연 로딩을 통해서 데이터베이스 이중화를 완성하였습니다. 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함