Backend/springboot

[JPA] Jmeter로 쿼리문 성능 개선하기 (2) - DISTINCT 대체

설기똥꼬 2024. 4. 9. 10:37

현재 지금까지 시험 치룬 횟수 조회하는 API를 리팩토링 중이다.

토큰을 통해 로그인한 유저를 식별하고, 해당 유저가 치룬 시험 내역들을 조회하여 5회 이상인지, 미만인지에 따라 응답값을 다르게 반환한다.

이때 유저가 치룬 시험 내역을 조회하는 쿼리는 아래와 같다.

select distinct e.*
from exam e, member_exam me
where e.id = me.exam_id
  and me.member_id = :memberId
  and me.status = 'ACTIVE'

 

여기서 개선이 필요하다고 생각이 든 부분은,

1. distinct로 추가적인 중복 제거 연산

2. e.*로 불필요한 컬럼까지 조회

3. exam e, member_exam me와 같이 크로스 조인

 

해당 쿼리와 함께 리팩토링이 필요한 부분을 수정하여 개선 작업을 진행해보겠다.

 

 

현재 비즈니스 로직은 아래와 같다.

로그인한 유저의 Id로 현재 치룬 시험 내역 횟수를 조회한다.

getTakenExams에서 List<Exam>을 반환한 후 ExamConverter를 통해 exams.size()를 최종적으로 반환한다.

같은 팀원분께서 getTakenExams() 메서드를 추후 마이페이지나 시험 뷰에서 재사용할 가능성이 있기에 Exam List로 반환하는 서비스 함수를 제작했다고 하셨다.

 

 

API 테스트시 2469ms 소요되는 것을 확인할 수 있다. 

 

위 사진은 기존 쿼리의 실행 계획이다.

exam, member_exam을 join한 후 distinct를 사용했다. 

distinct는 키워드 하나만으로 간단하게 중복을 제거할 수 있다. 그러나 temp tablespace에 임시로 저장하고 작업하기에 시스템에 부하를 줄 수 있다.

그리고 추후 관리시 데이터를 확실히 알 수 없는 상황이기에 빼지도 못하고, 그대로 두면 효율이 떨어진다. 

 

 

 

우선 쿼리를 실행했을 때 517ms가 소요된다.

해당 쿼리에서 DISTINCT 대신 서브쿼리로 exists (select 1 ~ ..)와 같은 semi join을 사용할 것이다.

 

SELECT e.*
FROM exam e
WHERE EXISTS (
    SELECT 1
    FROM member_exam me
    WHERE e.id = me.exam_id
      AND me.member_id = :memberId
      AND me.status = 'ACTIVE'
);

 

수정한 쿼리 실행 결과 346ms가 소요되었다.

약 200ms가 줄어들었다!