-
Notifications
You must be signed in to change notification settings - Fork 2
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
[BE] feat: Spring Cache 적용 #792
Conversation
Test Results156 tests 153 ✅ 5s ⏱️ Results for commit 23fc0cd. ♻️ This comment has been updated with latest results. |
성능 부하 테스트 하면서 전/후 비교해봐도 좋겠습니다. |
캐시 라이브러리 방식 고민후보
우리 상황
Ecache vs Caffein 비교
추가로 고려할 상황만약 캐시에 질문지가 올라갔는데 오타가 있는 게 발견되어서 db에서 question의 content 수정한다면 어떻게 이 변경 사항을 캐시에 반영할 수 있을까? 결론설정 방법이나 난이도가 크게 차이나지 않는 상황에서, caffein이 동시성, 성능 문제가 적기때문에 더 효과적일 것이라고 생각합니다! |
현재는 cahe hit,miss actuator로 확인하게만 설정 |
e6beb27
to
b35d7af
Compare
- ttl 설정 - maxSize 설정 - cache 통계 위한 metric 활성화
b35d7af
to
dd77084
Compare
🚀 어떤 기능을 구현했나요?
🔥 어떻게 해결했나요 ?
📚 참고 자료, 할 말1. ttl은 30일로 설정했습니다.
2. max size는 1000으로 설정했습니다.캐시에 올라가있는 값들이 1000개를 넘어가면 접근빈도나 최신성을 카페인이 알아서 판단해 순차적으로 제거합니다.. 3. 캐시키 식별 방식캐시는 key-value로 map에 저장되는 방식이라 어떤 메서드에서 어떤 목적으로 어떤 파라미터를 갖고 값을 저장하는지를 key로 식별해야합니다. 따라서 아래 두 캐시는 별도로 구분되지 않는 것이죠. @Cacheable(value = "templateCache", key = "'#questionId")
@Query("""
SELECT q FROM Question q
WHERE ts.templateId = :templateId
""")
List<Question> findAllById(long questionId);
@Cacheable(value = "templateCache", key = "'#questionId")
@Query("""
SELECT q.id FROM Question q
WHERE ts.templateId = :templateId
""")
List<Long> findAllById(long questionId); 따라서 이를 식별해주기 위해 key에 클래스명_메서드명을 추가해주었습니다. 최종은 keyGenerator로 동적으로 생성해주는 방식을 선택했습니다. 그 과정에서 여러 방식을 시도해보았는데 커밋 보면서 더 나은 방식이 있는지 알려주세요! |
팀 논의 결과
추가 작업위 내용을 반영해서 push, merge |
- config에서 지정하지 않으면 새로운 @Cacheable value 추가할때 동적으로 ConcurrentHashMap을 생성함
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어어 RC가 안된다;;
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.context.annotation.Profile; | ||
|
||
@Profile({"local", "dev", "prod"}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 모르니까 (다른 Profile이 들어올 수 있으니까) 변경을 최소화하기 위해서 클래스 단위가 아니라 아래 @Bean
에 프로필 다는 건 어떨까요?
@@ -38,6 +39,7 @@ public class TemplateMapper { | |||
private final OptionGroupRepository optionGroupRepository; | |||
private final OptionItemRepository optionItemRepository; | |||
|
|||
@Cacheable(value = "templateCache", key = "#root.targetClass.simpleName + '_' + #root.methodName + '_' + #reviewGroup.templateId") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 캐시 이름이
templateCache
보다는template_response
처럼 어떤 것을 캐싱하는지 정확하게 알았으면 좋겠습니다. - 캐싱되는 조건이
templateId
니까 그냥#reviewGroup.templateId
로 하는 게 낫지 않을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 동의합니다. template_response 좋네요!
- 만약 value가 templateCache이고, key가 long 타입이라면, 같은 캐시에 대해 동일한 키로 인식될 수 있어 캐시 항목이 (덮어씌워질x), 잘못 가져와질 위험이 있어요. 따라서, 클래스와 메서드 이름을 조합하여 캐시 키를 고유하게 만들려고 했어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
덮어씌워진다는 게 어떤 의미인가요? templateId
가 같은 것에 대해서는 항상 같은 템플릿을 내보내야 하는 것 아닌가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
덮어씌워진다는 설명이 잘못됐네요. 이 문제를 말한 거였어요!
- value = "templateCache", templateId = 1이라는 키에 캐시가 저장됨.
@Cacheable(value = "templateCache", key = "#reviewGroup.templateId")
public TemplateResponse mapToTemplateResponse(ReviewGroup reviewGroup) {
- 만약 템플릿과 관련된 다른 캐시가 추가됨
@Cacheable(value = "templateCache", key = #exampleId")
public TemplateExampleResponse mapToTemplateExampleResponse(long exampleId) {
exampleId = 1로 새로 캐시에 저장되어야하는데, 기존에 1이라는 key로 저장되어있는 캐시가 있어서 그걸 불러옴(잘못된 데이터)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 그럼 정말 키값을 templateId로, 리턴값을 templateResponse로 하는 map을 다룬다고 생각하면 안 될까용?
Map<Long, TemplateResponse>처럼 다루고 싶은거고 - 같은 바구니 쓰면서 다른 타입이 나온다는 게 이해가 안 되어서요
- 여러 타입 들어가면 잘못하면 캐스팅 예외 튀어나올 것 같지 않나요
(디코에서 가져왔슴다)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동의합니다 변경완!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(어프루브임)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로컬에서 돌려서 캐싱 적용되는 것 확인했습니다~🥳
수고했어요!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
깔끄므쓰 하군요
🚀 어떤 기능을 구현했나요?
캐싱 기능을 구현했습니다. ConcurrentHashMap을 기반으로 함에 유의하세요.
반복되는 쿼리를 방지하는 것이지, 한 번에 여러 쿼리가 나가는 것에 대한 근본적인 해결책이 아님에 유의하세요.
그럼에도 리뷰 작성은 자주 발생하는 상황이므로, 이를 캐싱하는 것이 효율이 좋을 것이예요.
ConcurrentHashMap
이 너무 나이브하고 추가 기능이 없다면, 로컬에서 작동하는 다른 캐시 라이브러리를 고려해도 좋습니다. 그 중 하나로 ehcache가 있습니다, 이 친구도 캐싱 자바 표준(JSR-107)을 지원해 스프링과 호환됩니다. 확인해보시고 이게 더 낫겠다고 생각하시면 RC 주세요. 만약 추가된다면, 캐싱에 대한 추가 메타데이터를 얻을 수 있습니다. (Cache Hit 비율, Cache log 등)🔥 어떻게 해결했나요 ?
spring-boot-starter-cache
의존성과 어노테이션 세 개...📚 참고 자료, 할 말