JPA를 공부하는 중에 다음과 같은 의문이 들었다. 영속성 콘텍스트가 제공하는 1차 캐시의 범위는 아무리 길어도 요청과 응답 주기이다. 그럼 자주 사용되는 데이터를 애플리케이션 내부 메모리에 저장하여 DB 연결을 최소화하여 재사용하면 성능이 오르지 않을까?
그래서 해당 데이터를 메모리에 캐시했다고 했을 경우 DB가 외부 시스템에서 데이터를 변경 혹은 추가했다면 해당 캐시는 데이터의 동일성을 유지하지 못하지 않을까? 이러한 해결책은 무엇일까?
그래서 한번 찾아보았다.
첫 번째의 해답은 2차 캐시를 사용하는 것이며, 2차 캐시를 사용하기 위해선 JPA의 표준 캐시와 구현체인 Hibernate의 캐시 기능을 활용할 수 있었다. JPA의 표준 캐시는 루트 엔티티 수준에서 캐시를 적용할 수 있지만 Hibernate의 JCache 및 EhCache라이브러리를 활용하면 좀 더 세밀한 수준까지 캐시를 적용할 수 있다.
해당 기능은 공식문서를 참조하여 적용하는 것이 올바르다.
그렇다면 동기화 문제는 어떻게 처리해야 하는지 찾아봤는데 사실 이렇게 데이터가 외부의 영향을 받는 데이터 값을 캐시 한다는 것 자체가 문제가 있다고 생각했다. 캐시 적용하는 것도 최대한 데이터의 수정이 없는 것을 해야 캐시라는 기능을 사용하는 의도와 일치하지 않을 까라는 생각을 해본다.
따라서 해당 데이터 값이 외부에 영향을 미치지 않고 조회의 빈도 수가 큰 경우 2차 캐시를 사용하면 성능을 대폭 향상할 수 있으며 여러 캐시 라이브러리가 존재한다. 그중 Ehcache는 경량화된 캐시화 라이브러리이고,
ehcache.xml 설정 파일을 통해 캐시에 관련된 정책을 설정할 수 있다.
JPA 2차 캐시
네트워크를 통한 데이터베이스를 접근하는 시간과 한번 조회된 데이터를 애플리케이션 서버에서 내부 메모리에 접근하는 시간 비용의 차이는 수십만 배 비싸다. 따라서 캐시를 통해 데이터베이스 접근 횟수를 줄여 성능을 획기적으로 개선할 수 있다.
1차 캐시는 영속성 콘텍스트 내부에 엔티티를 보관하는 저장소가 존재하며, 이것을 1차 캐시라고 한다.
하지만 1차 캐시는 트랜잭션을 시작하고 종료할 때까지만 1차 캐시가 유효하다. OSIV를 사용해도 결국 클라이언트 요청이 들어올 때 부터 끝날 때 까지만 1차 캐시가 유효하다.
하이버네이트를 포함한 대부분의 JPA 구현체들은 애플리케이션 범위의 캐시를 지원하는데 이것을 2차 캐시라고 한다.
우선 1차 캐시는 영속성 콘텍스트 내부에 존재하고 엔티티 매니저를 통한 엔티티의 변경과 조회는 모두 1차 캐시에 저장된다. 트랜잭션을 커밋하거나 플러시를 호출하면 1차 캐시에 있는 엔티티의 변경 내역을 데이터베이스에 동기화한다.
1차 캐시는 설정을 할 수가 없다 왜냐면 영속성 콘텍스트 자체가 1차 캐시이기 때문이다.
1차 캐시의 동작 방식을 글로 설명해 본다.
1. 사용자의 요청이 들어오면 OSIV를 적용하지 않았다고 가정할 때 트랜잭션 범위 안에서 엔티티의 조회 요청이 들어온다.
2. 엔티티 매니저는 해당 엔티티를 데이터베이스를 통해서 조회하며 자신의 영속성 콘텍스트에 저장한다. 그리고 해당 엔티티를 반환해준다.
3. 동일 트랜잭션 범위 내에서 같은 엔티티의 조회 요청이 들어온다면 1차 캐시에 저장된 엔티티를 반환해준다.
그와 반면에 2차 캐시가 존재하는데 JPA에서는 shared cache라고 한다. 일반적으로 second level cache라고 부른다. 구글 검색을 위해 해당 용어를 기억해 놓자.
2차 캐시가 동작하는 방식은 다음과 같다.
1. 엔티티 매니저는 최초 조회를 2차 캐시에 한다. 2차 캐시에 존재하지 않는다면 db에 조회를 한다.
2. 조회의 결과 값을 2차 캐시에 저장하고 해당 엔티티의 복사 본을 1차 캐시인 영속성 콘텍스트에 반환해준다.
3. 동일 조회가 다른 영속성 컨텍스트에 들어온다면 2차 캐시에 존재하는 엔티티를 복사하여 전달해 준다.
2차 캐시는 동시성을 극대화하려고 캐시 한 객체를 직접 반환하지 않고 복사본을 만들어서 반환한다. 만약 캐시한 객체를 그대로 반환하면 여러 곳에서 해당 엔티티를 동시 접근해 문제가 발생할 수 있다.
알아 둘 것은 2차 캐시는 데이터베이스의 기본 키를 기준으로 캐시 하지만 영속성 콘텍스트가 다를 경우 객체의 동일성을 보장하지 않는다.
실제로 2차 캐시를 적용하기 위해선 다음과 같은 설정이 필요하다.
javax.persistence.Cacheable 어노테이션을 엔티티에 추가한다. 기본값은 true이며 false로 조절할 수 있다.
spring 프레임워크를 사용할 경우 EntityManagerFactory property설정에서 sharedCacheMode를 설정해준다.
<bean id="entityManagerFactory" class="org.springframwork.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="sharedCacheMode" value="ENABLE_SELECTIVE"/>
.....
프로퍼티에 대한 설정값은 ALL, NONE, ENABLE_SELECTIVE 등등이 존재한다.
만약 순수 자바로 구성된 경우 영속성 유닛 단위에 shared-cache-mode를 설정해주어야 한다.
JPA가 표준화한 캐시 기능을 적용하려면 구현체의 설명서를 읽어야 한다.
하이버네이트 구현체는 JCache와 Ehcache로 2차 캐시를 적용한다.
docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#caching
docs.oracle.com/javaee/6/tutorial/doc/gkjio.html
'데이터 접근 기술 > JPA' 카테고리의 다른 글
JPA 공부하기 전 (0) | 2022.10.22 |
---|---|
[JPA] 공부하면서 나중에 찾아볼 것들 (0) | 2021.04.17 |
[JPA] JPA 기본 정리 (0) | 2021.04.11 |
[JPA] 벌크 연산 (0) | 2021.01.31 |
[JPA] JPQL JOIN (0) | 2021.01.31 |
댓글