Spring

15만권 도서 검색 서비스에 캐시 적용하기

윤승 2025. 6. 9. 21:55
도서 키워드 검색 캐싱 처리 이유

 도서 데이터가 약 15만개 정도 있는 데이터베이스에서, 사용자가 입력한 키워드로 도서를 검색할 때마다 데이터베이스에 직접 접근하게 되면 여러 요청이 반복될수록 응답 속도가 느려지고, 서버의 과부하가 발생하게 된다.

이를 해결하기 위해 검색 결과에 대한 캐싱 전략을 적용했고, 적용 전/후 성능 차이를 정리해보았다.

 


성능 테스트 환경

  • 성능 테스트 툴: Apache JMeter
  • 테스트 조건
    • Thread (동시 사용자): 1000 users
    • Ramp-Up Period: 5 seconds
    • Loop Count: 1
    • 동일 키워드로 1000회 반복 요청
  • 평가 지표: 캐싱 적용 전/후 평균 응답 시간 비교

캐싱 적용 전(도서 키워드 검색 메서드)

// 캐시 x
public Page<BookResponseDto> searchByTitleWithoutCache(String keyword, Pageable pageable) {
    Page<Book> searchedBooks = bookRepository.findByTitle(keyword, pageable);
    keywordRepository.save(Keyword.of(keyword));
    return BookResponseDto.fromEntityPage(searchedBooks);
}

 

처음 구현한 검색 메서드(캐싱 적용 전)는 여러 사용자가 키워드를 가지고 도서검색을 하게 되면 매 요청 마다 DB에 접근하게 되면서 높은 요청 빈도로 인해 성능에 저하와 응답이 지연되는 것을 알 수 있다.

JMeter 성능 테스트 결과

 

포스트맨 응답 시간 결과

 

성능 테스트 결과 (캐시 적용 전)

  • JMeter 결과
    → 높은 평균 응답 시간, 트래픽 증가 시 서버 부하 심화
  • Postman 응답 시간
    → 여러 번 요청 시 응답 속도 점점 느려짐

캐시 적용 후: 인메모리 캐시 사용

구현 코드

@Cacheable(
    value = "bookSearchCache",
    key = "#keyword + '_' + #pageable.pageNumber + '_' + #pageable.pageSize"
)
public Page<BookResponseDto> searchByTitle(String keyword, Pageable pageable) {
    Page<Book> searchedBooks = bookRepository.findByTitle(keyword, pageable);
    keywordRepository.save(Keyword.of(keyword));
    return BookResponseDto.fromEntityPage(searchedBooks);
}
  • 캐시 이름: bookSearchCache
  • "{keyword}_{pageNumber}_{pageSize}": 키워드,페이지 정보를 조합하여 페이징 캐시 충돌 방지

최초 요청 시에만 DB를 조회하고, 이후 동일한 검색 조건의 요청은 인메모리 캐시에서 결과를 반환하도록 구현했다.

 

JMeter 성능 테스트 결과

 

포스트맨 응답 시간 결과

 

성능 테스트 결과 (캐시 적용 후)

JMeter 결과

  • 평균 응답 시간 크게 개선
  • 부하 상황에서도 안정적인 처리 가능

Postman 응답 시간

  • 첫 요청 이후 응답 속도 일정하게 빠름

 

🔥 캐싱 후 인 메모리 캐시에서 반환하여 응답 속도를 감소하고  DB 부하 감소로 서버 안정성 향상되었다.

 


개선점

스프링의 기본 캐시 기능(@Cacheable)은 TTL(캐시 유효 시간) 설정이 불가능하여, 캐시 갱신 주기를 제어할 수 없다. 이에 따라 향후에는 Redis를 도입하여 캐싱 주기 설정 및 유연한 캐시 관리가 가능하도록 개선할 예정이다.