@Transactional(readOnly=true)와 없는 것의 차이

Woody·2024년 8월 21일

TIL

목록 보기
7/19

배경

원티드 BE 챌린지 강의를 듣던 중 ‘가능한 @Transactional(readOnly=true) 어노테이션을 사용하는 것이 좋아요.’라는 말을 들었다. 이에 대해 의문을 갖고 왜? 사용하는 것이 좋은지 학습하고자 한다.

스프링 트랜잭션이 무엇이고, 어떻게 동작하는지 궁금하다면 해당 아티클에 정리한 내용을 참고하자.

@Transacitonal(readOnly = true)

@Transactional 어노테이션에는 여러 속성을 제공하는데 그중 하나가 readOnly 속성이다.

트랜잭션이 읽기 전용인지 읽기/쓰기 전용인지 선언하며, 기본 값은 false(읽기/쓰기)로 설정되어 있다. 읽기 전용 트랜잭션은 성능 최적화가 가능하다.

어떻게 성능 최적화가 가능할까?

  • 수행 시간 및 메모리를 절약할 수 있다.

    JPA는 변경 감지 기능을 지원해서 트랜잭션을 데이터베이스로 전달하기 전에 스냅샷과 결과 값을 비교하고, 변경 사항이 있으면 추가 쿼리를 실행한다.

    하지만 읽기 전용으로 실행한다면 변경 감지를 할 필요가 없어서 스냅샷을 생성하지 않는다. 이로 인해서 변경 감지를 안 하기 때문에 수행 시간이 줄어들고, 스냅샷을 생성하지 않기 때문에 메모리를 절약할 수 있어 성능 최적화가 가능하다.

  • 데이터베이스 Replication 부하 분산

    DB의 부하를 분산하기 위해서 사용하는 Replication은 Master-Slave 구조로 있다. Master에는 쓰기 연산을, Slave에서는 읽기 연산을 수행하면서 DB의 부하를 분산한다.

    readOnly를 설정한 트랜잭션은 Slave DB에 읽기 요청을 보낸다.

    만약 readOnly를 설정하지 않으면 모든 트랜잭션이 Master로 향하게 되어 부하 분산이 제대로 이뤄지지 않는다. readOnly를 설정함으로써 Replication의 취지에 맞게 DB가 동작하게끔 만들 수 있다.

그렇다면 무조건 readOnly를 사용하는 것이 좋을까?

그렇지 않다.

읽기 전용으로 트랜잭션을 실행해서 성능을 최적화할 수 있지만, 결국 DB의 커넥션을 점유한다.

이로 인해 읽기 연산이 오래 걸린다면 커넥션을 오래 소유하게 되어 DB 커넥션 고갈로 이어질 수 있기 때문에 이 점을 주의해야 한다.

@Transactional(readOnly=true)와 붙이지 않는 것의 차이는 뭘까?

@Service
public class TestService {

    @Transactional
    public void method() throws InterruptedException {
        Thread.sleep(1000L);
    }

    @Transactional(readOnly = true)
    public void method2() throws InterruptedException {
        Thread.sleep(1000L);
    }

    public void method3() throws InterruptedException {
        Thread.sleep(1000L);
    }
}

함수가 위와 같고, DB 커넥션 풀의 수를 1로 설정한 뒤 3개의 메서드를 동시에 실행한 결과, method()과 method2()는 2초 뒤에 종료가 되고, method3()은 바로 실행이 된다.

그렇다. @Transactional 어노테이션을 붙이지 않는다면 DB 커넥션을 점유하지 않는다.

이것을 보고, @Transactional 어노테이션을 붙이지 않으면 readOnly의 이점을 가져감과 동시에 DB 커넥션을 점유하지 않기 때문에 @Transactional 어노테이션을 붙이지 않는 것이 더 좋지 않은가?’란 생각을 했다.

@Transactional 유무의 차이는 OSIV 설정을 false로 했을 때, 알 수 있다.

💡 OSIV란?

영속성 컨텍스트를 View Layer까지 유지하는 속성으로 클라이언트 요청 시점(Filter / Interceptor - Controller)부터 끝날 때까지 영속성 컨텍스트를 생성되어 유지된다. 이를 통해 View Layer에서도 Entity의 Lazy Loading이 가능해진다.

기본적으로 OSIV는 true로 설정되어 있어 @Transactional 유무의 차이를 알 수 없다.

하지만 OSIV가 false일 땐, 트랜잭션 범위를 벗어나는 순간 Entity는 영속성 컨텍스트의 관리를 받지 않는 준영속 상태가 되어서 Lazy Loading이 불가능해지는 문제점이 있다.

OSIV : true
// @Transactional(readOnly = true)
public void method2() throws InterruptedException {
    excuteLazyLoading(); <- 성공
}

OSIV : false
// @Transactional(readOnly = true)
public void method2() throws InterruptedException {
    excuteLazyLoading(); <- 에러
}

마치며

이번 학습을 통해서 @Transactional 어노테이션을 붙이는 나만의 기준이 생겼다.

  • @Transactional 을 안 붙이는 상황 데이터베이스를 Replication을 사용하지 않을 때 조회 쿼리이면서 조회 이후에 Lazy Loading과 같이 영속성 컨텍스트의 도움이 없어도 되는 경우
  • @Transactional(readOnly=true) 데이터베이스를 Replication을 사용할 때 조회 쿼리이면서 조회 이후에 영속성 컨텍스트의 도움이 필요한 경우
  • @Transactional 엔티티를 변경하는 로직일 때

이러한 기준을 가지면서 비즈니스 로직을 분석해 @Transactionl 을 사용하려 한다.

또한, DB 커넥션을 짧게 가져가기 위해 OSIV를 false로 바꿀 것이다.

참고

Is @Transactional(readOnly=true) a silver bullet?

[Spring] @Transactional(readOnly = true)는 왜 붙여야 할까??

0개의 댓글