@Transactional을 사용할 때 읽기 용도의 메서드에는 readOnly=true 옵션을 설정할 때가 대부분이다. 그렇다면 readOnly의 사용하면 좋은 점이 무엇일까? 무조건적으로 읽기 작업의 트랜잭션에는 이 옵션을 키는 것이 맞을까?
읽기 전용 틀랜잭션에서는 DB에 대한 쓰기 작업이 발생하지 않아 DB에 불필요한 로그 작성을 줄이고, 락 관리를 최적화할 수 있다.
더티 체킹은 변경 감지를 위해 스냅샷을 유지하는 등의 리소스를 소모하지만, readOnly=true를 사용하면, 영속성 컨텍스트는 변경 감지를 위한 스냅샷을 보관하지 않아 이에 따라 필요한 리소스를 절약할 수 있다.
읽기 전용 트랜잭션은 데이터 변경을 허용하지 않으므로, 데이터 일관성을 유지하는 데 도움이 된다.
readOnly=true 옵션을 주면 스프링 프레임워크 하이버네이트 세션 플러시 모드를 MANUAL로 설정한다. 이로 인해 강제로 플러시를 호출하지 않는 한 플러시가 일어나지 않아 엔티티의 등록, 수정, 삭제가 강제로 플러시하지 않는 이상 동작하지 않는다.
결론은 무조건적으로 readOnly 옵션을 켜주는 것은 적절지 않다. 왜냐하면 JPA 구현체나 데이터베이스 드라이버는 읽기 전용 트랜잭션을 지원하지 않거나, 제한적으로 지원할 수 있다.
또한 이번에 알게된 내용이지만, readOnly 옵션이 Optimistic Lock에 영향을 줄 수 있다고 한다. Optimistic Lock(이하 낙관적 락)이란 두 개의 트랜잭션이 동시에 동일한 데이터를 수정하려고 시도할 때 발생하는 충돌을 방지하는데 사용되는 메커니즘인데, 트랜잭션은 대부분 충돌하지 않는다고 낙관적으로 가정하는 방법이다.
낙관적 락은 @Version을 이용하여 해당 엔티티의 버전이 기록되며 트랜잭션이 엔티티를 수정할 때마다 업데이트 된다. 만약 서로 다른 트랜잭션이 동일한 엔티티를 수정하려하면 버전 번호를 확인하여 첫 번째 트랜잭션이 수정을 했다 가정하고 두 번째 트랜잭션이 조회 시점의 버전과 비교하여 버전이 다르다면 충돌이 발생했다는 것이다.
그러면 readOnly 옵션 true인 메서드에 엔티티를 수정하는 로직이 있을 경우, 해당 트랜잭션이 엔티티를 수정하는 것이 아니라 읽기 전용으로 설정했기 때문에 버전 번호를 확인하지 못할 수 있다. 이 때 충돌을 감지하지 못하고 동시에 발생한 트랜잭션이 변경사항을 덮어쓰여 데이터의 불일치 문제가 발생할 수 있다. 따라서 낙관적 락이 활성화된 엔티티는 @Transactional(readOnly = true)로 설정된 메서드에서 엔티티를 읽기 작업만하고 수정해서는 안된다.
readOnly 옵션을 이용하면 다양한 이점이 있지만, 더티 체크를 하지 않아 리소스와 성능이 최적화가 되어지는 부분에서 읽기 작업을 수행하는 메서드에는 켜주는 것이 좋다. 그러나 무조건적으로 옵션을 켜주는 것이 아니라 이 메서드의 역할과 조건에 맞추어서 잘 설정해 주는 것이 중요하다고 생각한다.