반응형

1차 캐시 와 스냅샷, 2차 캐시


스냅샷

영속성 컨텍스트가 생성될 때, 향후 변경 감지를 위해서 원본을 복사해서 만들어둔 객체

1차 캐시

단순히 엔티티를 캐싱해두고 같은 key이면 디비로 재요청하지 않기 위한 것이다.

그럼 디비에서 값이 바뀌면 어떻게하나? 라는 생각이 들 수 있지만,

오히려 디비가 변경되었어도 같은 key를 조회했을 때는 같은 값을 가져와야 혼란이 적기 때문에 전혀 문제가 되지 않는다.

즉, Repeatable read 등급의 트랜잭션 격리 수준을 디비가 아닌 애플리케이션 차원에서 제공한다는 장점이 있다.

이를 동일성(주소가 같은 것)이라고 한다.

2차 캐시

1차 캐시가 commit, flust를 하거나 osiv를 사용할때 까지 캐시를 유지한다면,

2차 캐시는 애플리케이션이 종료될 때까지 유지한다는 것이 특징이다.

이를 잘 활용하면 디비 조회를 훨씬 더 줄일 수 있기 때문에 디비 조회 횟수를 획기적으로 줄일 수 있다.

동작 방식은 간단하게 아래와 같다.

  1. 영속성 컨텍스트는 엔티티가 필요하면 2차 캐시를 조회한다.
  2. 2차 캐시에 엔티티가 없으면 디비를 조회해서 2차 캐시에 보관한다.
  3. 2차 캐시는 자신이 보관하고 있는 엔티티를 복사해서 반환한다.
  4. 2차 캐시에 저장되어 있는 엔티티를 조회하면 복사본을 만들어 반환한다.

2차 캐시가 복사본을 반환하는 이유는 동시성 이슈 때문이다.

만약 동일한 객체를 그대로 반환하면 이슈를 방지하기 위해 락을 걸어야 하는데 이 비용보다는 복사가 더 저렴하기 때문이다.

즉, 동일성을 보장하지 않고, 동등성을 보장한다.

@QeuryHint와 @Transactional(readOnly = true) 차이


@QeuryHint란?

하이버네이트의 전용 힌트인 org.hibernate.readOnly를 사용하면 엔티티를 읽기 전용으로 조회할 수 있다.
읽기 전용이므로 영속성 컨텍스트는 스냅샷을 보관하지 않는다. 따라서 메모리 사용량을 최적화할 수 있다.
단 스냅샷이 없으므로 엔티티를 수정해도 데이터베이스에 반영되지 않는다.

TypedQuery<Order> query = em.createQuery("select o from Order o", Order.class);
qeury.setHint("org.hibernate.readOnly", true);

@Transactional(readOnly = true)

트랜잭션을 읽기 전용 모드로 사용되기 때문에 영속성 컨텍스트가 관리하지만 플러시를 하지 않는다.
플러시를 하지 않기 때문에 등록, 수정, 삭제는 동작하지 않는다. 하지만 스냅샷 비교(더티 체킹)와 같은
무거운 작업을 수행하지 않으므로 성능이 향상된다. 물론 트랜잭션을 시작했으므로 트랜잭션 시작,
로직 수행, 트랜잭션 커밋의 과정은 이루어진다. 단지 영속성 컨텍스트를 플러시하지 않을뿐이다.

차이점

@QueryHint는 트랜잭션과는 무관하고, 읽기 전용이며, 스냅샷 자체를 만들지 않기 때문에
메모리 사용량을 최적화할 수 있다. @Transactional(readOnly = true)는 트랜잭션을
생성하고, 영속성 컨텍스트에 관리하며, 스냅샷은 만들지만 플러시를 하지 않는다.

참고

  • DTO로 바로 조회가 되는 경우는 스냅샷이 만들어지지 않기 때문에 성능 향상은 없다.
  • 두 가지를 조합하면 최적의 성능 최적화를 이룰 수 있다.
  • 스프링 5.1 이후부터 @Transactional(readOnly=true)를 쓰면,
    @QueryHint의 readOnly까지 모두 동작한다.

@Transactional(readOnly = true)가 있을 때와 없을 때 차이


@Transactional, @Transactional(readOnly) 모두 영속성 컨텍스트에 관리된다.

하지만 없다면 당연히 영속성 컨텍스트에 관리되지 않는다. 이 전제로 풀어나가 보자.

@Transactional은 다루지 않는다.

OSIV가 켜졌을 때

@Transactional(readOnly = true) 있을 때

  • 당연히 조회 가능
  • 변경 감지, flust 하지 않음. commit함
  • 영속성 컨텍스트에서 관리하므로 lazy loading 가능

@Transactional(readOnly = true) 없을 때

  • 디비는 트랜잭션이 없어도 조회가 되기 때문에 조회가 가능하다.
  • 변경 감지, flust, commit 하지 않음.
  • 영속성 컨텍스트에서 관리하므로 ***lazy loading 가능

OSIV가 꺼졌을 때

@Transactional(readOnly = true) 있을 때

  • 당연히 조회 가능
  • 변경 감지, flust 하지 않음. commit함
  • 영속성 컨텍스트에서 관리하므로 lazy loading 가능

@Transactional(readOnly = true) 없을 때

  • 디비는 트랜잭션이 없어도 조회가 되기 때문에 조회가 가능하다.
  • 변경 감지, flust, commit 하지 않음.
  • 영속성 컨텍스트에서 관리하지 않으므로 lazy loading 불가능

여기서 중요한 부분은 두 가지이다.

  1. OSIV가 꺼지고, @Transactional(readOnly = true) 없을 때이다.
    없어도 조회는 되지만 준영속 상태가 바로 되기 때문에 lazy loading은 불가능하게 되어
    LazyInitializationException이 발생하게 된다.
  2. 반대로 OSIV가 켜지고, @Transactional(readOnly = true) 있을 때이다.
    이때는 OSIV에 의해서 영속성 컨텍스트에 관리를 해주기 때문에 lazy loading이 가능하게 된다.

OSIV


OSIV는 대부분 DTO로 변환하여 사용하는데 만약 사용해야 한다면,

주의 사항들을 살펴보고, 코딩하자. 주의 사항이 몇가지 있다.

문제점

  1. Transaction 바깥 계층까지 트랜잭션이 유지되므로 변경될 경우 커밋이 되기 때문에 위험하다.
  2. Nontrasactional reads로 해결해도, 엔티티 수정 직후 트랜잭션을 시작하는
    서비스를 호출하면 엔티티 변경이 반영된다.

1번 문제 해결

스프링 프레임워크는 이런 해결을 위해 몇가지 OSIV 구현체를 제공한다.

간단하게 동작 방식만 알아보자. 이를 트랜잭션 없이 읽기(Nontransactional reads)라고 한다.

  1. 클라이언트의 요청이 들어오면 서블릿 필터나 ,스프링 인터셉터에서
    영속성 컨텍스트를 생성한다. 단, 이때 트랜잭션은 시작하지 않는다.
  2. 서비스 계층에서 @Transactional로 트랜잭션을 시작할 때 1번에서 미리 생성해둔
    영속성 컨텍스트를 찾아와서 트랜색션을 시작한다.
  3. 서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시한다.
    이때 트랜잭션은 끝내지만 영속성 컨텍스트는 종료하지 않는다.
  4. 컨트롤러와 뷰까지 영속성 컨텍스트가 유지되므로 조회한 엔티티는 영속 상태를 유지한다.
  5. 서블릿 필터나, 스프링 인터셉터로 요청이 돌아오면, 영속성 컨텍스트를 종료한다.
    이때 플러시를 호출하지 않고 바로 종료한다.

정리하면 아래와 같다.

  • 영속성 컨텍스트를 프레젠테이션 계층까지 유지한다.
  • 프레젠테이션 계층에는 트랜잭션이 없으므로 엔티티를 수정할 수 없다.
  • 프레젠테이션 계층에는 트랜잭션이 없지만 트랜잭션 없이 읽기를 사용해서 지연 로딩을 할 수 있다.
  • OSIV 1번 문제를 해결하기 전에는 요청이 오자마자 컨트럴로에서 트랜잭션을 시작했다.
    즉, 커넥션도 바로 연결 되었다. 하지만 지금은 그렇지 않다.

2번 문제 해결

이건 스프링이 제공한다기 보다, 개발자가 지켜야 하는 부분이다.

트랜잭션이 있는 비지니스 로직을 모두 호출하고 나서 엔티티를 변경하면 된다.

즉, 프레젠테이션 계층에서 다시 서비스 계층을 호출하지 않도록 하면 된다.

참고

스프링부트에서 open-in-view는 default true이다.

참고 자료


https://joyykim.tistory.com/25

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.data.spring.jpa.open-in-view

https://www.inflearn.com/questions/227574/transactional-어노테이션-질문드립니다

https://www.inflearn.com/questions/31497/queryhint의-readonly-와-transaction의-readonly-차이

http://www.yes24.com/Product/Goods/19040233

반응형
복사했습니다!