회사에서 새로운 프로젝트에 투입되었고 kotlin, spring webflux, r2dbc를 사용하기로 하였습니다. 회사의 기존 프로젝트는 mongoDB를 사용하느라 spring data mongo를 사용했었는데 새로운 프로젝트에서는 RDB를 쓰게 되면서 R2DBC를 사용하게 되었습니다.
그러다가 단순 조회를 하는 API를 하나 만들었는데 과거에 spring data jpa를 공부했을 때 조회 시 자주 사용했던
@Transactional(readOnly = true)
도 R2DBC에서 사용가능한 것을 알았고 이 글은 R2DBC 사용하고 단순 조회 기능을 개발할 때 @Transactional 기능을 사용하면 안좋다는 것을 알게되서 작성하는 글입니다.
단순 조회 기능을 개발하다가 갑자기 코파일럿이 아래와 같이 코드를 추천해줬습니다.
@Transactional(readOnly = true)
이 옵션은 JPA로 단순 조회 기능을 개발하다 보면 자주 사용하는 옵션입니다.
JPA를 사용하면서 이 옵션을 붙이게 되면 스프링 프레임워크에서 제공하는 트랙잭션을 읽기 전용 모드로 설정할 수 있습니다.
이 옵션을 통해 스프링 프레임워크가 하이버네이트 세션 플러시 모드를 MANUAL로 설정하게 되고 이렇게 되면 강제로 플러시를 호출하지 않는 한 플러시가 일어나지 않습니다.
따라서 트랜잭션을 커밋하더라도 영속성 컨텍스트가 플러시 되지 않아서 엔티티의 등록, 수정, 삭제가 동작하지 않고, 읽기 전용으로써 영속성 컨텍스트는 변경 감지를 위한 스냅샷이 보관하지 않으므로 성능이 향상됩니다.
한창 취업 준비할 때 스프링을 공부하면서 JPA를 공부하는 것은 당연한 일이었고 그러다보니 JPA가 만능인줄 알고 공부를 했었습니다.
그리고 단순 게시글 조회 기능이라도 만들게 되면 어김없이 @Transactional(readOnly = true)
이 어노테이션과 옵션을 추가했었습니다. 성능도 좋아질 뿐더러 개인적으로 가독성에도 도움이 된다고 생각했습니다.(이 기능은 조회만 있어~라는 뜻)
그래서 그런지 @Transactional(readOnly = true)
에 대해서는 좋은 기억만 있었습니다.
그러다가 현재 R2DBC를 사용하게 되었고 코파일럿이 해당 코드를 추천해주니 성능에 향상이 있을 것만 같았습니다.
하지만 의문이 있었습니다. R2DBC에서 readOnly 옵션을 통해 성능의 향상을 경험하려면 JPA의 영속성 컨텍스트와 같이 뭔가 있어야하는데 R2DBC에서는 영속성 컨텍스트라는 개념이 존재하지 않습니다.
그래서 진짜 성능에 향상이 있나 아니면 그 반대인지 테스트를 해보기로 했습니다.
suspend fun test() {
xxRepository.findAll()
}
@Transactional
suspend fun test() {
xxRepository.findAll()
}
@Transactional(readOnly = true)
suspend fun test() {
xxRepository.findAll()
}
suspend fun test() {
return transactionalOperator.execute { xxRepository.findAll() }
}
JPA는 위에서 언급한 것처럼 해당 옵션에 성능 최적화를 시켜놔서 빠른 거였습니다.. (성능최적화는 구현체 마음이니까)
하지만 R2DBC나 다른 구현체에서는 (readOnly = true) 옵션을 붙인다고 성능이 좋아지지 않습니다. 해당 옵션에 대해 성능이 좋아지도록 구현하지 않았기 때문인듯합니다.
R2DBC에서는 @Transactional(readOnly = true) 를 사용하지말자!
필자가 작성한 쿼리는 굉장히 단순한 쿼리였다. 이런 단순한 기능에도 눈에 보이는 성능 차이가 있었습니다. (최소 3배 정도)
아마 쿼리가 복잡하거나 비즈니스 로직이 복잡하다면 아마 훨씬 극적인 성능차이를 보일 것입니다.
그래서 R2DBC를 사용하면서 단순 조회 기능을 만들고 꼭 트랜잭션이 필요한 경우가 아니라면 아무 어노테이션을 붙이지 않는 것이 성능상으로 좋습니다.
제가 잘못이해하고 있거나 잘못 작성한 부분이 있다면 지적, 비판, 피드백 뭐든 해주시면 감사하겠습니다!