2021-nolto
2021-nolto copied to clipboard
[BUG] 다중 DataSource와 OSIV 사이의 문제
내용
OSIV에 의해서 잘못된 DataSource가 선택되는 문제
정확히는 다중 DataSource 와 OSIV에서 생기는 문제입니당
시나리오
현재 DB Replication을 위해서 쓰기전용 DataSource = masterDB
그리고 읽기전용 DataSource = slaveDB
를 사용중입니다.
Resolver에서 User를 조회할 때 readOnly 트랜잭션을 사용합니다. readOnly 트랜잭션을 사용한다는건 읽기전용 DataSource => slaveDB 를 사용합니다. 여기서 OSIV에 의해 트랜잭션이 끝나도 영속성 컨텍스트는 유지됩니다.
댓글 등록, 수정 등등의 데이터 쓰기 api 호출 시 Resolver에서 User를 조회하는데 readOnly 트랜잭션이 먼저 사용되면서 slaveDB의 DataSource를 사용합니다. 이때 사용된 DataSource를 계속 유지한채로 쓰기 api의 Service까지 도달합니다.
댓글 등록을 예로 들면
- MemberArgumentResolver에서 User 조회
- EntityManager에 SlaveDB DataSource 등록됨
- 댓글 등록 CommentController -> CommentService
- CommentService 에서 댓글 등록 메서드 호출 -> 이미 등록된 DataSource가 있으므로 그대로 사용(SlaveDB의 DataSource 😈 )
- 댓글 등록이 SlaveDB로 쓰기를 실행
- SlaveDB에 댓글이 저장됨
그럼 지금까지는 masterDB에 잘 저장됐는데?? 라는 의문을 가질 수 있어요
아래 코드는 MemberArgumentResolver에서 호출하는 AuthService의 findUserByToken 메서드입니다.
사진을 보면 @Transactional 애너테이션이 생략됐습니다. 즉 readOnly=false 트랜잭션으로 사용하므로 Resolver에서 사용하는 DataSource는 MasterDB의 dataSource 입니다. 그래서 댓글 등록도 MasterDB의 DataSource를 사용하는 것이죠. 즉 의도한대로 동작은 하지만 문제는 있다고 봅니당.
현재는 OSIV를 켜고 MemberArgumentResolver에서 호출하는 AuthService의 findUserByToken 메서드에 @Transactional(readOnly=true)를 주면 Resolver는 SlaveDB를 찌르고 Service의 메서드에서 쓰기 메서드를 사용해도 SlaveDB를 찌릅니다.
- Replication의 본래 목적인 Write&Read 부하 분산이 일어나지 않는다.
- 어디서 시작되든 처음 시작되는 트랜잭션의 DataSource를 계속 사용하는점
- 지금처럼 Resolver에 의해서 쓰기에서 잘못된 DataSource를 사용할 수 있는점
- 하나의 Controller에서 여러 service를 호출해야하는 상황이 생겼을때 각각의 DataSoure를 줄 수 없는점
이런 문제들이 있다고 봤어요. OSIV를 꺼버리면 의도한대로 Resolver는 조회DB Service레이어의 쓰기 메서드는 쓰기DB를 사용하도록 할 수 있습니다. (코드에서 좀 수정이 필요!)
참고문서
저희 서비스에서는 OSIV를 User 접근 용도에서만 거의 대부분 사용되고 있으니, replication을 적절하게 하기 위해 제거해주는 방향이 더 좋을 수도 있겠다는 생각이 드네요! 저는 OSIV를 끄면 물론 커넥션이 조금 더 발생한다는 단점도 있겠지만, replication 역시 그런 부하를 줄이기 위한 역할 중 하나고 전체적으로 봤을 때 replication을 우선순위에 두는 것이 좀 더 좋겠다고 생각합니다! 👍👍👍👍