트러블슈팅 - 레디스 활용 캐싱

cochocho·2026년 4월 20일

발생 문제

java.lang.ClassCastException: class java.util.LinkedHashMap 
cannot be cast to class com.beyond.qiin.common.dto.PageResponseDto
 (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; 
 com.beyond.qiin.common.dto.PageResponseDto is in unnamed module of loader 
 org.springframework.boot.loader.launch.LaunchedClassLoader @6d03e736)

	at com.beyond.qiin.domain.booking.service.query.
	
	ReservationQueryServiceImpl$$SpringCGLIB$$0.getReservationAppli
	es(<generated>) ~[!/:0.0.1-SNAPSHOT]

요약

linked hash map을 page response dto로 변환할 수 없다는 에러

원인

redis cache config에 추가해둔 GenericJackson2JsonRedisSerializer에 object mapper을 추가해놓았기 때문에
object mapper은 default으로 json 데이터를 linked hash map으로 변환해준다
이를 공통 응답 구조인 page response dto로 넣어주는 것을 시도했기 때문에 불가능했던 것

해결

따라서 object mapper을 제거함

연관 문제1

기본 생성자가 없어서 역직렬화가 불가능하다는 에러 문구

원인

cache 활용의 단계

  • cache miss 후 redis 로 json 데이터를 직렬화해 보관
  • cache hit 시 : redis 에 저장한 json 데이터를 역직렬화해 객체로 변환하게 됨
    이때 객체를 page response dto로 변환해주기 위해서는 기본 생성자가 필요함

해결

따라서 page response dto에 기본 생성자를 추가해줌

연관 문제2

page response dto가 감싸게 되는 get applied reservation response dto에서 기본 생성자가 없어서 역직렬화가 불가능하다는 에러 문구

원인

ppage response dto가 생성되기 위해서는 같은 방식으로 get applied reservation이 먼저 생성되어야하기 때문에 발생

해결

get applied reservation response dto의 경우 먼저 팀 컨벤션으로 정한 빌더를 통해 생성하면서 휴먼 에러 방지를 위해 all args constructor 을 private 으로 두어 외부에서 생성 불가하도록 했다

이에 따라 final 필드들로 구성되어있다는 점에서 no args constructor을 기존 구조에는 추가하지 못한다

흐름 요약

일반 요청에 대한 응답 시) spring mvc에 따라 기본적으로 jackson이 제공되지만 실제로 service method 호출을 바탕으로 필요 객체에 대해 메모리 상에 로드되어있음

그러나 레디스를 추가 시) cache miss로 등록 후 시간이 지난 뒤 cache hit 실제로 발생

이에 따라 레디스로부터 캐시를 가져와 역직렬화하는 상황이 발생한다
그렇기 때문에 jackson을 통한 역직렬화를 거쳐야하며,
기존에 api를 사용시에는 발생하지 않던 기본 생성자 없음에 대한 에러가 발생하는 것


캐싱 적용 시

캐시 전이 캐시 후보다 응답 결과와 초당 처리량이 더 컸음

jmeter 기반 캐시 추가에 대한 동일 요청의 성능 테스트 시 시간 개선이 되지 않은 것이다

구성은 밑과 같다

  • thread group 20
  • ramp up period 1
  • loop 10

이는 레디스를 통해 캐시를 추가 시 cache stampede 현상으로 보인다

  • 동시에 실행되는 요청의 경우 캐시가 곧바로 생성되는 것이 아니기 때문에 캐시 없이 함께 메서드를 실행하도록 접근하는 경우들 있음
  • 캐시를 통하지 않을 경우 레디스를 조회 이후 데이터베이스를 들러야함

이러한 가설을 바탕으로 요청을 몇번 해당 api에 날린 후 다시 테스트해보았고
그를 바탕으로 캐시 전보다 응답 속도, 처리량이 개선되는 것을 확인할 수 있었다

초기 비용 발생에도 불구하고 캐싱을 적용하는 이유

이렇듯 초기 비용으로서는 캐싱을 적용했을 때가 캐싱 전보다 더 발생하게 되는데 그럼에도 불구하고 캐싱을 추가하는 이유는

캐싱 이후 누릴 수 있는 효과를 바탕으로 한다

  • 더 빠른 응답과 처리 속도
  • 요청이 더 다양하게 늘어나는 경우, db로 커넥션이 다 차버리는 것과 같은 db의 부하를 캐시로 완화할 수 있음

이러한 점에서 캐싱을 유지하기로 했다

위와 같은 문제를 극복하는 방법

  • 필요한 캐시를 먼저 채워넣음
    ex) 최근 1시간 내의 인기 검색어

  • 배포 시 블루, 그린 전략
    새로 서버를 배포 시 바로 실서버로 연결하는 것이 아니라 워밍업을 한 뒤 실제 요청을 받음

0개의 댓글