Refactor: 캐싱 적용#67
Open
seokjun01 wants to merge 9 commits into
Open
Conversation
jwnnoh
reviewed
Aug 11, 2025
jwnnoh
left a comment
Member
There was a problem hiding this comment.
고생한 흔적이 보입니당!! 저도 잘 아는건 아니지만..
- 도메인 별로 TTL을 다르게 설정하면 더 좋을 것 같아요
- 캐시 스탬피드 문제를 해결하기 위해 우선 간단하게
@Cacheable(value="notificationSlots", key="#memberId", sync=true)
를 끼얹을 수도 있겠죵..?!
3. 그리고 NotificationSlot 엔티티 자체를 캐싱하기보다는, DTO를 캐싱하는건 어떨..까요?!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📌 작업한 내용
기존 몇몇 도메인의 조회로직을 레디스를 사용하여 분리하였습니다.
레디스 선정 이유
: 레디스는 관계형 데이터가 아닌, key-value 형태로 데이터를 저장하기 때문에 단순 조회에 있어 성능 향상 기대(고속 읽기)
: 또한 일반 DB처럼 디스크에 데이터를 쓰는 구조가 아닌, 메모리에서 데이터를 처리하기 때문에 작업 처리 속도가 빠름
: 다양한 자료구조 지원 ,메모리 기반이지만 데이터의 영속적인 보존이 가능
캐싱이란?
: 한 번 조회된 데이터를 미리 특정 공간에 저장해두고, 똑같은 요청이 발생 시, 서버에 요청하지 않고 저장해 둔 데이터를 빠르게 제공해주는 것
: 조회에만 캐싱을 적용하였으므로, Look aside패턴을 사용
Look aside 패턴
캐시 조회 → MISS면 DB 조회→ 캐시에 put → 반환
(캐시 히트 : MISS 아니면 바로 데이터 반환! )
고려사항
어떤 데이터를 캐싱을 해야할까를 고민하고 알아본 결과
적용 기준 3가지를 적용하였습니다.
치카해 도메인들에 위 조건을 적용하면 ,, 사실 실 사용자도 없고 볼륨이 크지 않다 생각해
퀴즈 ,아이템 , 공지사항,FAQ(정적 컨텐츠), 알림 , 마이페이지 등등이 있었습니다.
위 대상 중 마이페이지 , 알림을 선정하여 캐싱을 적용하였습니다.
저는 서비스 레이어에 캐싱을 적용하였는데 ,
읽기: getSlots() → 캐시 히트 시 바로 반환
쓰기: updateSlotTime() → 캐시 비우고 DB만 변경 → 다음 getSlots()가 다시 DB 읽어 캐시에 반영
이런식으로 동작하기를 기대하였습니다.
적용 과정
로컬 터미널에 도커로 레디스를 pull 받아옴 -> 6379포트로 레디스를 띄움 -> 의존성 추가 -> config파일 생성 ->Jmeter를 사용한 서버 부하 테스트 및 응답 시간 차이 시각화
서버 부하 테스트 도구 : Jmeter
-> 처음에는 프로메테우스 + 그라파나를 사용한 데이터 수집 + 시각화를 하려했으나 ,
-> 서버에 부하를 주는 방식이 로컬에서는 어렵다고 판단하여 jmeter를 사용
-> 비교적 정확한 테스트를 위해 100명의 사용자가 한 번에 몰려 1회 조회를 무한 반복 (1분 동안 유지)
(ps. 또 대부분 어떻게 테스트 하는지가 궁금합니다.)
🖼️ 스크린샷
평균: 281ms → 203ms (약 –28%)
중앙값: 271ms → 197ms (약 –27%)
95% 라인: 467ms → 355ms (약 –24%)
99% 라인: 580ms → 450ms (약 –22%)
최대: 914ms → 792ms (약 –13%)
대충 20% 정도 응답 시간 단축됨을 확인하였습니다.
🔗 관련 이슈
@Cacheable 만 붙인다고 되는게 아니었습니다.
missing type id '@Class' 오류가 발생하였습니다.
찾아보니 , 스프링 캐시는 내부적으로 Object로 값을 다루기 때문에, 다시 읽을 때 원래 타입을 복원하려면 이 정보가 필요하다고 함 => 모든 타입에 @Class를 같이 JSON에 넣음으로써 해결
순환 참조의 발생(ㅠ.ㅠ)
JPA 양방향 연관관계가 원인
Jackson이 JSON 만들 때 필드를 타고 내려가며 직렬화:
member → slots → member → slots → … 무한 반복
@JsonIgnore로 순환을 끊음
Jackson은 객체를 JSON으로 만들 때 , 필드를 돌아가며 깊이 우선 탐색을 함
예를 들어 Member 객체를 직렬화하기 시작하면 { "id":1, "slots":[ … ] }
근데 이 안에서 한 객체를 직렬화할 때 그 객체가 들고 있는 필드 객체들도 같이 직렬화되면서
Member → slots[0] → member → slots[0] → member → … 이런 원리였다..
어노테이션을 사용해서 해결하는 방식도 있지만, 응답이나 캐시에 필요한 필드로만 구성된 DTO를 만들어 단방향으로 설계해도 된다고 합니다. 허허
(두서 없이 작성했지만 ,, 긴 글 읽어주셔서 감사드립니다 )
✅ 체크리스트