Kuma's Curious Paradise
스프링부트 2차캐시 적용하기 본문
2차 캐시를 적용하여 데이터베이스 조회를 최소화하고자 한다.
먼저, 1차 캐시와 2차 캐시의 차이점에 대해 알아보자.
JPA의 1차 캐시는 영속성 컨텍스트 내에서만 유지되는 캐시다. 2차 캐시는 애플리케이션이 동작하는 동안 유지되는 캐시다.
예를 들어, 고객 정보를 조회한다고 가정해 보자.
- 첫 번째 트랜잭션에서 고객 1에 대한 조회가 이루어지면 데이터는 1차 캐시에 저장된다. 트랜잭션이 끝난 후 고객 1의 데이터는 캐시에서 사라진다.
- 두 번째 트랜잭션에서 같은 고객 ID 1을 조회하면, 캐시된 데이터가 없기 때문에 DB에서 첫 번째 트랜잭션에서 요구한 정보를 다시 가져와야 한다.
- 만약 2차 캐시가 활성화되어 있다면, 고객 1에 대한 조회가 이루어질 때, 해당 데이터가 2차 캐시에도 저장된다. 트랜잭션이 끝난 후에도 고객 1의 데이터는 2차 캐시에 남아 있다.
- 2차 캐시에서 데이터를 가져올 수 있기 때문에 DB에 다시 다녀오지 않아도 같은 정보를 제공할 수 있다.
- 따라서 2차 캐시는 반복적으로 동일한 데이터를 조회하는 경우, DB 조회를 크게 줄일 수 있다.
오늘은 스프링부트에서 2차 캐시를 적용하는 방법을 알아보고자 한다.
1. build.gradle에 의존성 추가하기
'org.hibernate.orm:hibernate-jcache:6.4.4.Final'
Hibernate Jcache 통합 라이브러리이다. Hibernate는 Java의 대표적인 ORM(Object-Relational Mapping) 프레임워크로, 자바 객체를 활용하여 DB에 쉽게 접근할 수 있도록 도와준다. JCache는 Java에서 캐시를 관리하는 표준 API이다. Hibernate-Jcache는 이 JCache를 기반으로 Hibernate에서 2차 캐시를 구현할 수 있도록 도와주는 라이브러리, 쉽게 말하면 ORM과 2차 캐시를 연결해 주는 라이브러리이다.
'javax.cache:cache-api:1.1.1'
자바에 인터페이스가 있듯이, 캐시 또한 구현체에 종속되지 않고 캐시를 사용할 수 있도록 만든 것이 바로 Jcache. ORM과 캐시 구현체(밑에 서술될 Ehcache)를 연결해 준다. 캐시 생성과 관리를 쉽게 만든다.
'org.ehcache:ehcache:3.10.0'
Ehcache는 JVM 기반의 캐시 라이브러리이다. 실제 캐시 기능을 구현한 구현체이다. Redis나 Infinispan을 이용할 수도 있지만, 적용해 보는 것이 큰 목적이었기 때문에 구글링에서 가장 많이 언급된 Ehcache를 먼저 사용해 보았다. Jcache가 캐시 라이브러리를 갈아끼우는 것을 쉽게 만들었기 때문에 나중에 변경되어도 큰 무리는 없을 것이다.
2. application.yml 수정하기
jpa:
properties:
hibernate:
cache:
use_second_level_cache: true
region.factory_class: jcache
javax.cache.provider: org.ehcache.jsr107.EhcacheCachingProvider
설정은 Hibernate User Guide를 참고하였다.
hibernate.cache.use_second_level_cache: true
이 설정은 2차 캐시를 활성화할지 말지를 결정한다.
hibernate.cache.region.factory_class: jcache
이 설정은 hibernate에게 jcache를 사용하여 캐시를 관리하라고 지시한다. user guide에서 볼 수 있듯이 hibernate는 jcache와 infinispan 사용을 권장한다.
javax.cache.provider: org.ehcache.jsr107.EhcacheCachingProvider
이 설정은 구체적인 캐시 프로바이더로 Ehcache를 지목한다. 쉽게 말해 Jcache의 구현체가 Ehcache다.
hibernate.cache.use_query_cache(사용 안 함)
이 설정은 자주 사용하는 쿼리의 캐시를 저장하도록 활성화한다. db가 삽입, 수정, 삭제 시 해당 캐시를 업데이트해야 하는데, 이를 번번히 추적하는 것이 상당한 부담이므로 default는 false이다. 사용하지 않았다.
3. 엔티티에 @Cache 붙이기
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Member {...}
@Cacheable과 @Cache 어노테이션을 사용하여 엔티티마다 캐시 적용을 할지 말지 결정할 수 있다. (전역 설정도 가능하다!)
먼저 Member 엔티티에 캐시를 적용하기 위해 두 어노테이션 중 @Cache을 사용하였다.
@Cacheable은 jakarta.persistence에서 제공하는 JPA 표준 어노테이션이다. @Cacheable이 적용된 엔티티를 조회하면(메서드가 호출되면), 해당 결과가 캐시에 저장된다. 이후 동일한 요청에 캐시된 데이터를 보내준다. 쉽게 말해, JPA에게 해당 엔티티를 캐시할 수 있다고 알리고 실제로 캐시를 수행한다.
@Cache는 hibernate에서 2차 캐시를 적용하는 어노테이션이다. db에서 엔티티를 조회할 때 2차 캐시에 엔티티를 저장하고 수정하면 캐시를 갱신한다. usage를 통해 캐시를 어떻게 사용할지, 어느 region에 저장할지 등 구체적인 설정이 가능하다. 여기서는 읽기/쓰기 전략을 채택하여, 데이터가 변경되더라도 일관성을 지키고자 한다.
결론적으로 @Cacheable과 @Cache 애너테이션을 함께 사용하기로 했다. JPA 표준을 준수하면서 Hibernate에서 제공하는 세세한 2차 캐시 기능을 활용하기 위함이다.
캐시를 통한 성능 변화는 애플리케이션을 좀 더 발전시키면서 차차 알아보기로 한다.
'스프링' 카테고리의 다른 글
[유레카] 떠오르는대로(!) 정리하는 유레카 수업 1 : DB 커넥션 풀은 무엇일까? (5) | 2024.09.06 |
---|---|
토스 결제 코드 : 프론트가 없으면 만들어서 확인하기 (1) | 2024.09.05 |
Soft Delete 구현할 때 고려해야 할 사항 (0) | 2024.08.10 |
테스트 코드 7 - 일관적인 테스트 코드를 위한 설정들 (0) | 2024.08.05 |
테스트 코드 6 - ItemCartRepositoryTest: TransientPropertyValueException (0) | 2024.07.16 |