@Transactional(readOnly = true)를 사용하는 이유와 주의할점

junhyeong·2023년 5월 11일
0

이번에 기존 프로젝트에서 @Transactional로 설정해 준 서비스에 readOnly 속성을 사용하여 리팩토링을 하였다.

사실 그동안은 트랜잭션을 적용만 하면된다 생각했고, 이런걸로 성능에 차이가 있을줄은 몰랐기 때문에 이 부분을 리팩토링할 생각은 하지도 못했다. 하지만 이번에 알게 되었으니 자세히 알아보려고 한다.

@Transactional(readOnly = true)

Spring에서 @Transactional 어노테이션을 사용할 수 있는데 트랜잭션을 readOnly = true로 설정해 주면 읽기 전용 모드로 변경할 수 있다.

단 주의사항 한가지를 말하자면, @Transactional(readOnly = true)은 DB에서 데이터를 읽기만 하는 서비스 메서드에 적용을 해야 한다. readOnly에서도 알 수 있듯이 읽기 전용으로 변경하는 것이기 때문에 데이터를 수정하는 서비스에 적용하면 안된다. 자세한 내용은 밑에서 얘기하겠다.

우선 읽기 전용 모드로 했을때의 이점을 알아보자

성능 최적화

트랜잭션을 읽기 전용으로 설정하면 해당 메서드가 데이터를 읽기만 한다는 것을 DB에 알려줌으로써 쿼리 및 캐싱을 최적화할 수 있다.
그리고 읽기 전용으로 설정하며 데이터 변경이 일어나지 않기 때문에 변경감지를 위한 스냅샷을 저장하는 동작 또한 하지 않아 성능이 향상되는 것을 기대할 수 있다.

데이터 일관성

일반적으로 트랜잭션을 사용해서 DB에 데이터의 일관성과 무결성을 보장하기위해 사용하는데 트랜잭션을 읽기 전용으로 설정하면 실수로 데이터를 수정해서 일관성을 위반할 가능성이 낮아진다.

가독성 향상

코드를 작성하는 개발자는 @Transactional(readOnly=true)이 설정된 메서드가 DB에서 데이터를 읽기만 한다는 것을 명확하게 확인할 수 있다. 이로 인해 코드의 가독성이 향상이 된다.

@Transactional(readOnly=true)을 사용할 때 주의해야 할 점

앞에서 잠깐 말했듯 읽기 작업만 하는 모든 메서드에 @Transactional(readOnly=true)을 무지성으로 사용해서는 안된다.

readOnly = true를 적용할 때 Optimistic Lock의 동작에 영향을 미칠 수 있다는 것을 고려해야 한다.
JPA의 동시성 제어에 접근 방식이 두 가지 존재하는데 그중 하나가 Optimistic Lock이고 또 다른 하나가 Pessimistic Lock이다.

그 이유를 알기 위해 Optimistic Lock에 대해 알아보자

Optimistic Lock이란?

낙관적 락(Optimistic Lock)은 두 개의 트랜잭션이 동시에 동일한 데이터를 수정하려고 시도할 때 발생하는 충돌을 방지하는 데 사용되는 메커니즘인데, 트랜잭션의 대부분은 충돌이 발생하지 않는다고 낙관적으로 가정하는 방법이다.

낙관적 락을 사용하려면 Entity에 @Version 어노테이션을 추가해서 사용할 수 있다.

@Entity
public class Post {
@GeneratedValue
@Id
private Long id;

    @Version
    private Long version;
    
    // Constructor, Getter ...
}

@Version 어노테이션을 추가하면 이제 트랜잭션이 엔티티를 수정할 때마다, 현재 버전 번호가 자동으로 업데이트되며 기록이 된다. 다른 트랜잭션이 동일한 엔티티를 수정하려고 시도하면 버전 번호를 확인하는데 이때 첫 번째 트랜잭션이 수정을 했다고 가정하면 두 번째 트랜잭션의 버전 번호는 조회한 시점의 버전과 수정한 시점의 버전이 다르다는 것을 확인한다. 이때 충돌이 발생했다는 것을 두 번째 트랜잭션에 알리게 된다.

즉, 데이터를 조회한 시점의 버전과 수정하려고 할 때 버전이 일치하지 않으면 충돌이 발생한 것으로 간주하고 예외가 발생한다.

@Transactional(readOnly=true)를 사용하면 이러한 낙관적 락 동작에 영향을 미치게 된다.

만일 @Transactional(readOnly=true)로 설정한 메서드에 엔티티를 수정하는 로직이 있을 경우, 해당 트랜잭션이 엔티티를 수정하는 것이 아니라 읽기 전용으로 설정했기 때문에 버전 번호를 확인하지 못할 수 있다. 이때 충돌을 감지하지 못하고 동시에 발생한 트랜잭션의 변경 사항을 덮어쓰게 되어 데이터 불일치 문제가 발생할 수 있다.

예를 들어 트랜잭션이 엔터티를 읽고 수정한 뒤, 다른 트랜잭션이 수정하려고 시도하는 경우 낙관적 락 충돌을 감지하지 않고, 수정된 엔터티는 DB에 저장되어 다른 동시 트랜잭션의 변경 사항을 덮어쓰게 된다.

그래서 낙관적 락이 활성화된 엔티티는 @Transactional(readOnly=true)로 설정된 메서드에서 엔티티를 읽기 작업만 하도록 하고, 수정하지 않도록 조심해야 한다.

정리

@Transactional(readOnly = true)을 적절한 곳에 사용하면 성능의 향상을 기대할 수 있다. 하지만, readOnly = true를 적용했을 때의 동작방식을 이해하고 적절한곳에 사용해야한다.

profile
매일매일이 성장하는 하루가 될 수 있도록!

0개의 댓글