[JPA] 2차 캐시

LDB·2025년 1월 13일
0

JPA 기본

목록 보기
9/10
post-thumbnail

2차 캐시란

2차 캐시는 JPA에 있는 기능중에 하나로 간단히 이야기하면 중복되는 요청이 들어올 시 최초에 들어온 요청은 영속성 컨텍스트에 있는 쿼리문을 실제 DB Table에 실행시켜 값을 가져오지만 2차 캐시를 사용하면 최초의 요청은 DB를 직접 조회하고 똑같은 요청이 다시들어오면 2차 캐시의 값을 리턴한다. 그렇기에 동시성 이슈를 방지 할 수 있다.

영속성 컨텍스트 관련 게시글 : https://velog.io/@half-phycho/JPA-JPA-동작원리


1차 캐시

2차 캐시에 대해 설명하기 앞서서 영속성 컨텍스트 안에있는 1차 캐시에 대해 알아보도록 하자 1차 캐시는 다음과 같이 동작된다.

	@Test
	@DisplayName("JPA 1차 캐시 테스트")
	@Transactional
	public void oneCache(){

		System.out.println("1차 캐시에 존재하지 않을경우");
		springjpaRepository.findById(1).get();
		System.out.println("1차 캐시에 존재 할 경우");
        springjpaRepository.findById(1).get();

	}


(실제로 테스트 코드상으로 하나의 트랜잭션으로 묶어 조회를 시도하면 처음에 조회한 엔티티 값이 1차 캐시에 저장되어있기 때문에 두 번째에는 DB로 가지 않고 1차 캐시안에 있는 값을 리턴한다.)

1차 캐시의 한계

그렇다면 어째서 2차 캐시와 같은 개념이 나왔냐면

1차 캐시는 하나의 트랜잭션을 시작과 종료까지만 동작을하기에 만약 10번의 요청이 들어오면 1차 캐시 10개가 만들어야한다. 결과적으로 매커니즘 관점으로 효율적이지 성능관점에서 보면 그렇게 효율적이지 못하다.

2차 캐시

여기서 2차 캐시의 필요성이 나오는데 2차 캐시는 범위가 하나의 트랜잭션이 아닌 애플리케이션 범위 입니다. 따라서 애플리케이션이 종료될 때 까지 2차 캐시가 유지됩니다. 그래서 2차캐시를 도입하면 다음과 같은 메커니즘이 나온다.

  1. 최초로 조회를 시도한다.
  2. 2차 캐시에서 해당하는 엔티티를 조회한다.
  3. 최초로 시도했기에 DataBase에서 조회를 한다.
  4. 조회한 값을 2차 캐시에 복사한다.
  5. 2차 캐시에 저장된 복사본을 리턴한다.
  6. 조회한 결과를 리턴한다.
  7. 같은 조건으로 동일한 데이터 조회를 시도한다.
  8. 2차 캐시에 해당하는 엔티티를 조회한다.
  9. 동일한 복사본이 2차 캐시에 있기에 반환한다.
  10. 조회한 결과를 리턴한다.

2차 캐시의 특징

위에서 설명한대로 2차 캐시는 조회한 엔티티를 복사하여 반환하는데 그이유는 캐시의 객체를 그대로 반환하면 멀티쓰레드 환경에서 동시성 문제가 발생 할 수있기 때문이다, 이 문제는 Lock을 걸어서 해결이 가능하지만 Lock을 걸어서 해결하는 것보다 객체를 복사하여 반환하는 것의 비용이 더 저렴하다.

위의 내용과 함께 2차 캐시의 특징을 나열하자면 이렇게 나열할 수 있겠다.

  • 2차 캐시는 어플리케이션이 종료되는 시점까지 유효하다.
  • 2차 캐시는 요청을 받으면, 가지고 있는 객체를 복사하여 반환한다.
  • 2차 캐시는 PK기준으로 캐시하지만, 객체를 반환 할 때 복사하여 반환하기 때문에 영속성 컨텍스트가 다르면 객체의 동일성이 보장되지 않는다.

2차 캐시 사용법

build.gradle

// jpa 2차 캐시 사용관련 implement 
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.ehcache:ehcache:3.10.0'
implementation 'org.hibernate:hibernate-jcache:6.0.2.Final'
implementation 'javax.cache:cache-api:1.1.1'

application.properties

# JPA사용시 쿼리 출력여부설정
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=false

# 2차 캐시 활성화
spring.jpa.properties.hibernate.use_second_level_cache=true

# 2차 캐시 처리할 클래스 지정
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.internal.JCacheRegionFactory

# 캐시 적용 여부를 확인
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.javax.persistence.sharedCache.mode=ENABLE_SELECTIVE

Entity 파일

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Comment;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.AccessLevel;
import lombok.Builder;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class springjpaMainEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "SEQ")
    @Comment(value = "일련번호")
    private int seq;                    

    @Column(name = "NAME")
    @Comment(value = "이름")
    private String name;

    @Column(name = "AGE")
    @Comment(value = "나이")
    private int age;

    @Column(name = "UNIQUENUM")
    @Comment(value = "고유번호")
    private int uniqueNum;

    @Builder
    public springjpaMainEntity(String name, int age, int uniqueNum){
        this.age = age;
        this.name = name;
        this.uniqueNum = uniqueNum;
    }
}

위의 설정으로 지정하고 실행을 시키면 조회를 할 경우 최초 1회만 select 쿼리를 실행하고 동일한 조건으로 조회를 한다면 콘솔에서 select쿼리가 나오지 않는다.

@Cache 어노테이션 속성

정확히는 usage 즉 사용범위 설정이다, 기본으로 설정 되어있는 디폴트는 없고 직접 정해주어야 한다.

설정적합한 용도
READ_ONLY조회가 주로 이루어지며 수정이 일어나지 않을 때 사용하는 것이 적합
READ_WRITE조회 및 수정 작업을 할 때 사용하는 것이 적합
NONSTRICT_READ_WRITE수정 작업을 하지 않는 데이터에 적합
TRANSACTIONAL컨테이너 관리환경에서 사용할 수 있다.
NONE캐시를 설정하지 않는다.

2차 캐시의 장단점

장점

  • 영속성 컨텍스트가 달라도 같은 엔티티를 조회한다면 데이터베이스 접근 횟수를 줄일 수 있다.
  • 캐시에 담긴 객체를 복사해서 반환하기에 동시성 이슈를 방지할 수 있다.

단점

  • JPA의 장점중에 하나가 객체의 동일성 보장인데 2차 캐시를 사용하면 동일성 보장이 안된다.
  • 2차 캐시도 결국 메모리를 사용하기에 2차 캐시에 작성되는 내용이 많을수록 메모리 사용량이 그만큼 증가하고 결국 성능이 저하될 수 있다.

2차 캐시의 실무 적용?

결론부터 말하자면 실무에서는 잘 쓰지 않는다고 한다. 왜냐하면 설정이 복잡할 뿐만 아니라 캐시 라이브러리의 범위도 제한적이다.

무엇보다 JPA의 2차 캐시는 엔티티 수준에서만 캐시가 된다. 보통 실무에서는 API통신을 위해 주로 DTO단위로 캐싱을 해야하는데 JPA의 2차 캐시는 그렇지 못한다고 한다.

그래서 캐시를 쓰고 싶으면 JPA의 2차 캐시가 아닌 스프링에서 제공하는 2차 캐시를 사용하는 것을 권장한다고 한다.

나의 생각

이렇게 2차 캐시에 대해 정리를 해보았다. 결론적으로 2차 캐시는 개발하고자 하는 프로젝트에 따라 사용하기 나름이라고 생각했다. 데이터의 변화가 크지 않고 조회 기능이 대부분인 서비스에는 도입하겠지만 매번 데이터가 신규로 입력되고 속도에 신경을 써야 하는 상황이 온다면 타 서비스처럼 비 관계형 데이터베이스인 NoSQL 혹은 Redis를 사용하여 처리할 거 같다.

참고 사이트

https://velog.io/@bagt/JPA-2차-캐시에-대하여

https://eclipse4j.tistory.com/280

https://hoestory.tistory.com/69

https://ojt90902.tistory.com/750

https://www.inflearn.com/questions/33629

(항상 감사합니다.)

profile
가끔은 정신줄 놓고 멍 때리는 것도 필요하다.

0개의 댓글

관련 채용 정보