서론
Spring Boot를 사용해서 Database Sharding을 처리할 수 있을까?
요즘 NoSQL에서는 Sharding과 관련해서 많은 편의를 제공한다. 알아서 Sharding을 제공해주고, 클러스터에 노드가 추가되면 Shard key를 기반으로 자동으로 새로운 노드로 값들을 분배해주거나, 노드를 제거하면 대상 노드에 있던 값들을 Shard Key에 맞추어 남은 노드로 분배해준다. 자세히 알아보지는 않았지만 RDBMS 측의 각종 벤더들도 이러한 아키텍처를 구현기위한 방법을 제공할 것이라 생각된다.(예를 들어 MySQL의 Fabric - 현재 MySQL Utilities에 통합)
이번 포스트에서는 RDBMS Sharding을 각 벤더에 의존해서 구현하지 않고, Spring Boot로 편리하게 설정을 추상화하여 사용할 방법에 대해 공유하려 한다. 앞서 Sharding에 대해서 거창하게 이야기를 했지만, 본문에서 다루고자 하는 영역은 아래의 두 가지 기능이다.
- 이름 기반으로 다중 DataSource 정보를 등록 : AbstractRoutingDataSource
- 특정 값을 기반으로 대상 DataSource를 사용 : Spring AOP
이름 기반으로 다중 DataSource 정보 등록하기
AbstractRoutingDataSource
우선 spring-jdbc
모듈에 있는 AbstractRoutingDataSource에 대해서 먼저 소개를 해야할 것 같다. 여러 DataSource를 등록하고, 특정 상황에 맞게 원하는 DataSource를 사용할 수 있도록 추상화한 클래스다. 이 클래스의 public void setTargetDataSources(Map<Object, Object> targetDataSource)
를 호출하여 String:DataSource
을 키:값
으로하는 Map
을 저장할 수 있다. 또한 Object determineCurrentLooupKey()
를 오버라이드해서 상황에 맞게 Key를 반환하도록 구현할 수 있다.
예시로 MyBatis의 ReplicationRoutingDataSource를 살펴보자.
아래 소스는 ReplicationRoutingDataSource
의 내용을 간략화한 것이다.
1 | public class ReplicationRoutingDataSource extends AbstractRoutingDataSource { |
1: 생성자를 통해서 master
로 사용할 DataSource
와 slave
로 사용할 DataSource
목록을 받고 있다.
2: 현재 트랜잭션이 활성화되어 있고, 읽기 전용이면 slave
중에 하나를 선택해서 반환하도록 되어 있다.
이를 응용해서 이름 기반으로 동작하는 AbstractRoutingDataSource
를 구현할 수 있다.
application.yml을 이용해서 구현해보기
아래와 같은 구조로 RDBMS가 구성되어 있다고 가정해보자.
이러한 구조를 아래와 같은 property를 등록하여 설정되도록 하려고 한다.
1 | named-routing-data-source: |
위 properties는 java로 아래와 같이 나타낼 수 있다.
1 | // 전역 설정 |
NamedRoutingDataSourceTargetProperties
에서name
이나slaveOf
둘 중 하나만 값이 있어야 한다.slaveOf
가a
인 경우name
은"a" + slaveSuffix
가 된다.
NamedRoutingDataSource와 NamedDataSource
NamedRoutingDataSource
는 AbstractRoutingDataSource
를 구현하였다. NamedDataSource
는 NamedRoutingDataSource
에서 들고 있을 대상이다. 먼저 NamedDataSource
부터 살펴보자.
1 |
|
NamedDataSource
는 slave(slaveOf()
)로, 혹은 master(asMaster()
)로 생성된다. 그리고 DataSource
를 구현하고 있는데, 구현에 필요한 메서드는 모두 delegate
에 위임하였다.
다음은 이 NamedDataSource
를 가지고 routing 처리를 할 NamedRoutingDataSource
를 살펴보자.
1 | public class NamedRoutingDataSource extends AbstractRoutingDataSource { |
NamedRoutingDataSourceManager
에서 현재 사용할 DataSource의 이름을 가지고 올 수 있다(getDataSourceName()
). NamedRoutingDataSourceManager
의 구현은 아래와 같다.
1 | public class NamedRoutingDataSourceManager { |
ThreadLocal
로 현재 사용할 DataSource의 이름을 설정할 수 있다. 어디선가는 이 이름을 설정해줘야 Sharding
이 제대로 동작할 것이다. 만약 이름이 없다면 NamedRoutingDataSource
는 항상 defaultDataSource
만 반환할 것이다.
Spring AOP를 활용해서 DataSource 이름 설정하기
긴 설명보다는 아래 소스를 보고 어떻게 동작할 지 설명하는게 빠를 것 같다.
1 |
|
1: findByWriterId
의 id
값의 hashCode()
결과에 modular 연산을 수행하여 대상 DataSource의 이름을 설정할 수 있다.
2: SpEL를 적용하여 메서드의 특정 argument를 대상으로 DataSource의 이름을 설정할 수 있다.
이를 가능하게 하는 SetDataSourceNameAspect
클래스는 다음과 같이 정의할 수 있다.
1 |
|
결론
여러 DataSource를 이름 기반으로 등록하고, Spring AOP를 통해서 사용할 DataSource의 이름을 정하도록 소스를 작성해보았다. AbstractRoutingDataSource
와 Spring AOP의 원리를 이용해 간단하게 추상화해보고 구현까지 해보았다. 해당 소스에서는 복잡성을 증가시키지 않기 위해 slave
는 하나라고 단정하고 진행하였는데, 필요하다면 slave
용도의 DataSource를 따로 SlaveNamedRoutingDataSource
같은 클래스로 묶어서 단순한 round-robin 정도라면 간단히 load balancing까지 쉽게 할 수 있을 것이다.
한 가지 애매한 것이 DataSource의 이름은 무슨 기준으로 정하느냐 하는 것이다. String으로 되어 있어, 등록하는 부분(NamedDataSource
)과 실제로 값을 가져오는 부분(DataSourceNameGetter
)에서 문자열을 잘못 입력하여 런타임 오류가 발생하는 가능성도 있는데, 이 부분에 있어서 좋은 해결책이 딱히 떠오르지는 않았다. 만약 강한 제약을 넣고 싶다면 NamedDataSource
가 아니라 EnumedDataSource
를 사용하는 것도 하나의 옵션으로 고려할 수 있지 않을까 생각된다.
위에서 소개한 내용 중에 application.yml
에 설정된 property를 기반으로 NamedRoutingDataSource
를 생성하는 소스는 빠져있다. property와 java 소스가 대칭을 이루기에 굳이 구현 소스를 넣지는 않았다. 상세한 구현이 궁금하다면 github 소스를 참고하자.
Github 소스 : https://github.com/supawer0728/parfait-spring-boot-starter-sharding