🚨 문제점
코드 내에 88000개의 데이터에서 특정 키워드(시작일, 종료일, 지역코드...)의 정보를 이용해 검색을 하는 로직이 있다.
이 코드는 테스트 환경에서 단일요청에 대해 검색결과를 문제없이 반환 하였다.
하지만, 500개의 요청을 한번에 주는 부하 테스트 할 경우 오류율이 40% 가까이 치솟는 것을 확인하였다.
따라서 이러한 문제의 원인이 무엇이고, 이를 해결하기 위한 방법에 대해서 알아보고자 한다.
🤔 문제 원인
기존 코드
기존 코드는 findAll에 검색 조건과 함께 넘겨주는 값으로 Pageable 객체를 함께 넘겨줬다.
var pageable = org.springframework.data.domain.PageRequest.of(
Math.max(page - 1, 0),
size,
org.springframework.data.domain.Sort.by(direction, sortProp)); //정렬 기준
....
var page = catalogRepo.findAll(spec, pageable);
🤷♂️ Page란?
Spring Data JPA에서 페이징 기능을 쉽게 구현할 수 있도록 제공하는 인터페이스이다.
주로 페이징과 정렬 정보를 포함하고 있고, "몇 번째 페이지를 몇건 단위로 어떤 기준으로 정렬해서 가져올지" 알려주는 역할을 한다.
Page를 반환하는 리포지토리의 메서드는 ,전체 건수와 전체 페이지 수를 계산하기 위해 추가 COUNT(*) 쿼리가 실행된다.
(Select Count(*) from ~)
이는 대량 데이터에서 동시 요청이 많을 경우 COUNT(*)가 커넥션을 오래 붙잡아서 풀에 커넥션 반납이 늦어져 다른 요청이 기다리는 시간이 증가하여 성능 저하를 초래할 수 있다.
수정된 코드
추가적인 COUNT(*) 연산을 피하기 위해 Slice 인터페이스를 사용했다.
var sortProp = "3".equals(sortCol) ? "traEndDate" : "traStartDate";
var direction = "DESC".equalsIgnoreCase(sort) ? Sort.Direction.DESC : Sort.Direction.ASC;
var pageable = PageRequest.of(Math.max(page - 1, 0), size, Sort.by(direction, sortProp));
Slice<HrdCourseRow> slice = (area1 != null && !area1.isBlank())
? catalogRepo.findSliceByArea1(s, e, area1, blankToNull(ncs1), pageable)
: catalogRepo.findSliceNoArea1(s, e, blankToNull(ncs1), pageable);
🤷♂️ Slice란?
Page는 내가 한 페이지에서 보고 싶은 데이터의 양과 총 건수(COUNT(*))를 함께 가지고 오지만,
Slice 인터페이스는 데이터의 전체 크기(총 건수)는 고려하지 않고 오직 현재 페이지와 다음 페이지의 데이터만 처리한다.
Pageable을 Page로 넘기면 요청한 size만큼 가져오고 전체 개수를 알아내기 위해서 COUNT(*)를 수행하지만
Pageable을 Slice로 넘기면 size+1만큼 가져오고, 다음 페이지 유무를 반환하게 된다.
이렇게 함으로써 추가적인 연산 과정(COUNT(*))을 줄여 응답 성능을 높일 수 있다.
⚡️테스트 결과
- 응답 속도 : 약 17.6배 개선
- 오류율 : 37.6% -> 0%
- 처리량 : 39.6/sec → 49.6/sec
'에러 일기' 카테고리의 다른 글
Asus 메인보드 BIOS에서 virtualization Technology 켜기 (0) | 2025.03.19 |
---|---|
H2 데이터베이스 포트 충돌 문제 해결 방법 (0) | 2025.02.08 |