Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 견종 목록 조회시 로컬 캐시 적용 #533

Merged
merged 4 commits into from
Nov 9, 2023
Merged

Conversation

iamjooon2
Copy link
Collaborator

@iamjooon2 iamjooon2 commented Nov 8, 2023

📄 Summary

#532 로컬 캐시 적용

세 줄 요약

  1. breedRepository.findAll()에 로컬 캐싱 적용함
  2. admin에 견종 등록 API 추가함
  3. (2) 번 통해 견종 추가되면 캐시 깨짐

인트로

견종 목록의 경우, 새로운 견종이 추가될 일이 극히 드물겠죠
리뷰 조회나 식품 조회같이 필터링이 필요한 경우도 아니구요
그래서, 로컬 캐싱을 적용해보았습니다

추가된 의존성

스프링 공식문서를 보면, CacheManager라는 인터페이스를 제공합니다. 이 친구를 통해 WAS 레벨에서의 캐싱을 적용할 수 있어요!

구현체인 라이브러리는 caffeine이라는 친구를 사용했습니다
크게 구관인 Enacache와 요즘 대세 Caffeine 이 두 친구가 있는데, Enacahe는 xml 방식으로 사용해야하고, 스타수도 10배씩이나 차이나서 caffeine으로 선택했습니다

아니 그래서 적용은 어떻게 되냐?

CacheConfig 설정을 통해, CacheManager와 Caffeine을 Bean으로 등록했어요

@CacheEvict(cacheNames = "breeds", key = "'allBreeds'")
위 어노테이션을 붙인 메서드가 실행되면 캐시가 무효화되는데요,
어드민에 견종 추가 API를 만든 후 해당 어노테이션을 붙여줬습니다

@Cacheable(cacheNames = "breeds", key = "'allBreeds'")
위 어노테이션을 붙인 메서드가 실행되면 캐시가 적용되는데요,
breedRepository의 findAll()을 감싼 findAllBreeds() 메서드 위에 붙여줬습니다

캐싱 잘 되나요?

image 위와같이 첫 요청시에만 쿼리가 나가고, 두번째 요청부터는 캐시가 적용 돼 쿼리가 안나가는 걸 확인할 수 있습니다 😃 (feat. 무민 쿼리카운터)

결과

select * from breeds 쿼리를 사용하는 두 개의 API를 대상으로
견종 300개 넣은 로컬에서 실험해보았습니다

[GET] /pets/breeds는
image

[GET] /reviews/metadata는
image

200ms -> 약 20~30ms로

로 유의미한 차이를 확인할 수 있었습니다🙂

배포 완료 후 수정

image
767ms -> 2~30ms

Copy link
Collaborator

@wonyongChoi05 wonyongChoi05 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

캐시 적용 잘 봤습니다..! 스프링에서 제공해주는 인메모리 캐시를 적용했을 때 쿼리가 안나가기까지 하다니 스프링이 문득 대단하다고 느껴지네요.. 크게 변경 사항은 없고 간단하게 궁금한 것들 코멘트 남겼습니다~

@@ -41,6 +41,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-cache'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spring-web에서 기본적으로 제공해주는 캐시 라이브러리(에를 들면 @EnabalCaching) 도 있는걸로 알고있는데 이 의존성은 뭔가요??

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wonyongChoi05

spring-web 모듈에는 캐싱과 직접적으로 관련된 기능이 포함되어 있지 않습니다. 이 모듈은 주로 웹 애플리케이션 개발과 관련된 클래스와 인터페이스, 예를 들면 MVC 패턴 구현, RESTful 웹 서비스 생성, 웹 클라이언트 기능 등을 포함합니다. 하지만, 캐시 저장소나 매니저를 수동으로 구성해야합니다. 예를 들어, EhCache나 Redis와 같은 캐시 저장소를 사용하기 위해서는 해당 저장소에 대한 의존성을 직접 추가하고 적절한 구성을 제공해야 합니다.

라고합니..

@@ -70,6 +71,8 @@ dependencies {
implementation 'software.amazon.awssdk:s3:2.20.121'
implementation 'software.amazon.awssdk:url-connection-client:2.20.121'

implementation 'com.github.ben-manes.caffeine:caffeine'
Copy link
Collaborator

@wonyongChoi05 wonyongChoi05 Nov 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가비의 설명에 덧붙이자면 아래와 같은 Cache Storage의 종류가 있습니다!

  • ConcurrentMapCache: JDK의 ConcurrentHashMap을 사용해 구현한 캐시
  • SimpleCache: 기본적으로 제공하는 캐시가 없어 사용할 캐시를 직접 등록하여 사용하기 위한 캐시
  • EhCache: 자바에서 유명한 캐시 프레임워크 중 하나인 EhCache를 지원하는 캐시
  • CompositeCache: 1개 이상의 캐시 매니저를 사용하도록 지원해주는 Mixed 캐시
  • CaffeineCache: Java 8로 Guava 캐시를 재작성한 Caffeine 캐시를 사용하는 캐시
  • JCache: JSR-107 기반의 캐시를 사용하는 캐시

@@ -149,4 +156,10 @@ public void deletePetFood(Long petFoodId) {
petFoodRepository.deleteById(petFoodId);
}

@CacheEvict(cacheNames = "breeds", key = "'allBreeds'")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 어노테이션을 붙이지 않은 메서드에서만 캐싱이 되는건가요??
++ cacheNames랑 key는 각각 무슨 역할인지 궁금합니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 애노테이션을 붙이면 정보에 해당하는 캐시를 무효화하는 것이라 이해했습니당. cacheNames 는 캐시의 이름입니다. 키랑 다른 개념이요

캐시 매니저 빈을 생성할 때 캐시 이름을 등록하더라구요.

@Bean
public CacheManager cacheManager() {
    SimpleCacheManager cacheManager = new SimpleCacheManager();
    cacheManager.setCaches(caches());
    return cacheManager;
}

private List<CaffeineCache> caches() {
    return Arrays.stream(CacheType.values())
            .map(cacheType -> new CaffeineCache(cacheType.getName(), cache(cacheType))) // 요기
            .toList();
}
  • 가비가 정의한 캐시타입
@Getter
@AllArgsConstructor
public enum CacheType {

    BREEDS("breeds", 1),
    ;

    private final String name;
    private final int maxSize;

}

Comment on lines +160 to +164
public Long createBreed(Long petSizeId, String breedName) {
PetSize petSize = petSizeRepository.getReferenceById(petSizeId);
Breed breed = breedRepository.save(Breed.builder().name(breedName).petSize(petSize).build());
return breed.getId();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻👍🏻👍🏻

Copy link
Collaborator

@kyY00n kyY00n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

너무 좋아요 👍🏻 개인적으로는 사료에도 적용해볼 수 있을 것 같아요. 가비 생각은 어떤가요?

@@ -41,6 +41,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-cache'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wonyongChoi05

spring-web 모듈에는 캐싱과 직접적으로 관련된 기능이 포함되어 있지 않습니다. 이 모듈은 주로 웹 애플리케이션 개발과 관련된 클래스와 인터페이스, 예를 들면 MVC 패턴 구현, RESTful 웹 서비스 생성, 웹 클라이언트 기능 등을 포함합니다. 하지만, 캐시 저장소나 매니저를 수동으로 구성해야합니다. 예를 들어, EhCache나 Redis와 같은 캐시 저장소를 사용하기 위해서는 해당 저장소에 대한 의존성을 직접 추가하고 적절한 구성을 제공해야 합니다.

라고합니..

@@ -149,4 +156,10 @@ public void deletePetFood(Long petFoodId) {
petFoodRepository.deleteById(petFoodId);
}

@CacheEvict(cacheNames = "breeds", key = "'allBreeds'")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 애노테이션을 붙이면 정보에 해당하는 캐시를 무효화하는 것이라 이해했습니당. cacheNames 는 캐시의 이름입니다. 키랑 다른 개념이요

캐시 매니저 빈을 생성할 때 캐시 이름을 등록하더라구요.

@Bean
public CacheManager cacheManager() {
    SimpleCacheManager cacheManager = new SimpleCacheManager();
    cacheManager.setCaches(caches());
    return cacheManager;
}

private List<CaffeineCache> caches() {
    return Arrays.stream(CacheType.values())
            .map(cacheType -> new CaffeineCache(cacheType.getName(), cache(cacheType))) // 요기
            .toList();
}
  • 가비가 정의한 캐시타입
@Getter
@AllArgsConstructor
public enum CacheType {

    BREEDS("breeds", 1),
    ;

    private final String name;
    private final int maxSize;

}

@iamjooon2 iamjooon2 merged commit d37fe10 into develop Nov 9, 2023
1 check passed
@iamjooon2 iamjooon2 deleted the feat/#532 branch November 9, 2023 06:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

[BE] 견종 조회 API 로컬 캐시 적용
3 participants