소개 배경
우리는 흔히 Spring 프로젝트를 공인 IP상에 띄우기 위해 EC2와 같은 환경을 이용하며 DB 또한 RDB를 사용한다는 가정하에 RDS와 같은 서비스를 이용하여 DB를 네트워크 상에 배포하게된다.
이때 네트워크를 통해 데이터베이스에 접근하는 시간 비용은 애플리케이션 서버에서 내부 메모리에 접근하는 시간 비용보다 수만에서 수십만 배 이상 비싸다.
따라서 조회한 데이터를 메모리에 캐시 해서 데이터베이스 접근 횟수를 줄이면 애플리케이션 성능을 획기적으로 개선할 수 있다.
1차캐시와 2차 캐시
우리는 Spring Data Jpa와 같은 작업을 통해 DB에 쿼리 작업을 수행할 경우 모든 작업은 영속성 컨텍스트에 엔티티를 저장하게된다.
이때 영속성 컨테스트 내부에 엔티티를 영구적으로 보관하는 장소를 1차 캐시라고한다. 이것으로 얻을 수 있는 이점이 많지만 일반적인 웹 애플리케이션 환경은 트랜잭션을 시작하고 종료할 때까지만 1차 캐시가 유효하다.
따라서 앞서 말했던 데이터베이스의 접근 횟수를 엄청나게 줄여주지는 못한다.
그래서 Hibernate를 포함한 대부분의 JPA 구현체들은 애플리케이션 범위(Scope)의 캐시를 지원하는데 이것을 공유 캐시 또는 2차캐시라고 부른다.
1차 캐시
1차 캐시는 영속성 컨테스트 내부에 존재한다. EM(Entity Manager)로 조회하거나 수정 작업에 대상이 되는 모든 엔티티가 1차 캐시에 저장된다.
트랜잭션을 커밋하거나 플러쉬(flush)를 호출하면 1차캐시에 존재하는 엔티티의 상태 내역을 데이터베이스에 동기화한다. 일반적으로 JPA는 스프링 프레임워크 같은 컨테이너 위에서 실행하면 트랜잭션을 시작하거나 종료할 때 영속성 컨텍스트도 시작하거나 종료한다.
물론, OSIV(Open-Session-In-View)는 제외이다.
위에 이미지처럼 find를 했을 때 해당 엔티티가 1차 캐시에 존재하면 1차 캐시에 저장된 엔티티를 반환하고 존재하지 않으면 DB를 조회한다. 하지만 동일한 트랜잭션 내에서만 1차 캐시가 존재하기 때문에 이미 1차 캐시에 저장되어 있는 경우는 흔치 않다.
2차 캐시
공유캐시라고도 불리는 2차 캐시(2L Cache)의 경우 애플리케이션을 종료할 때까지 유지된다. 분산 캐시나 클러스터링 환경의 캐시는 애플리케이션보다 더 오래 유지될 수도 있다.
2차 캐시를 적용하면 엔티티 매니저를 통해 데이터를 조회할 때 우선 2차 캐시에서 찾고 없으면 데이터베이스에서 찾는다. 2차 캐시를 적절히 활용하면 데이터베이스 조회 횟수를 획기적으로 줄일 수 있다.
1차 캐시에서 엔티티를 찾아보고 존재하지 않는다면 2차 캐시에서 해당 엔티티를 조회한다.
찾는 엔티티가 2차 캐시에 존재할 경우 그대로 캐시에서 꺼내 반환하지만, 존재하지 않다면 DB를 조회해서 반환한다.
이때 2차 캐시는 동시성을 극대화하기 위해 캐싱한 객체를 직접 반환하는 것이 아닌 copy(복사본)을 반환한다.
만약, 캐싱한 객체를 그대로 반환하게 될 경우 여러 곳에서 같은 객체를 동시에 수정할 수 있는 동시성에 관한 이슈가 발생할 수 있기 때문에 이 문제를 해결하기 위해 객체에 락을 걸게되는데, 이 때문에 동시성이 떨어지는 문제도 있다
그렇기 때문에 2차 캐시는 객체를 직접 반환하지 않고 복사본을 만들어 반환한다.
1차 캐시와 뭐가 다르냐고 생각할 수 도 있지만 2차 캐시는 애플리케이션 범위의 캐시이므로 데이터베이스 조회가 1차 캐시만 사용할 때 보다 획기적으로 줄어든다.
'Spring-boot' 카테고리의 다른 글
CustomAnnotation을 이용하여 가독성과 불필요한 Import 줄이기 (0) | 2023.03.05 |
---|---|
엔티티의 생명주기와 변경감지, @Transactional 이슈 (0) | 2023.02.26 |
SpringBoot 외부 Rest API 호출 방식(JAVA) - RestTemplate (0) | 2023.02.25 |
io.jsonwebtoken.SignatureException 정리 (0) | 2023.02.24 |
N+1 이슈 (0) | 2022.12.06 |