API 성능 테스트 (with fetch join)
(1) 예상
•
1번 방식) 기존 api에서 center 정보 가져올 때
◦
기존 쓰던 대로 api 쓰면 됨(서버 통신 그대로)
◦
센터 정보 찾는 쿼리 날릴 때 최적화 쉬움
▪
jpa에서 제공하는 fetch join+batch 기능을 사용해서 쿼리 수가 n/배치사이즈으로 줄어들고 중복데이터는 안가져와서 최적화 쉬움
•
select * from center where id in (1,2,3)
◦
같은 트랜잭션 내에서 조회해오기에 데이터 일관성을 보장할 수 있음
•
2번 방식) 새로운 api에서 center 정보 추가로 가져올 때
◦
api를 한 번 더 날려야 함(서버 통신 + 1)
◦
센터 정보 찾는 쿼리 날릴 때 최적화 어려움
▪
id 100개면 쿼리도 100번 날려야 함 → n
•
select * from center where id=1
•
select * from center where id=2
▪
설령 in 절 사용하더라도 배치 처리 따로 또 해주지 않으면 최적화 힘듦
•
select * from center where id in (1,2,1) → 중복은 in이 알아서 처리해준다.
•
배치 처리만 안된다.
(2) 결과
•
아래 코드를 컨트롤러 단에 넣어주어 측정했다. 아 근데 포스트맨 사용 중이면 포스트맨에도 실행 시간 뜬다.
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 실행 코드
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
System.out.println("코드 실행 시간 (s): " + stopWatch.getTotalTimeSeconds());
Java
복사
•
2번 방식 테스트 전 짧막한 수정)
◦
어….. ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 70개 가져오는데 12초
◦
문제의 레거시 쿼리…..
@Override
public Center findByIdAndFetchAll(Long id) throws NoResultException, NonUniqueResultException {
// list "select center from Center center join Center.Call on 조건 join
return em.createQuery("select distinct center from Center center " +
"left join fetch center.callList as call " +
"left join fetch call.user " +
"where center.id = :id ", Center.class)
.setParameter("id", id)
.getSingleResult();
}
Java
복사
◦
findById 사용하게 바꿔줬다. fetchAll은 필요가 없으니,, 아마 센터 상세 조회 api가 저 쿼리 사용하는 함수를 같이 쓰고 있어서 문제일 텐데 일단 테스트용이니 잠깐 바꿔줘자.
@Override
public Center findById(Long id) {
return em.find(Center.class, id);
}
Java
복사
◦
3.8초로 줄었지만 그래도 여전히 오래 걸린다.
◦
조금 더 최적화해서 in절 사용하게 해보자..
@Override
public List<Center> findByIds(List<Long> ids) {
TypedQuery<Center> query = em.createQuery("SELECT c FROM Center c WHERE c.id IN :ids", Center.class);
query.setParameter("ids", ids);
return query.getResultList();
}
Java
복사
•
1번 방식) fetch join
◦
0.734초
•
2번 방식) 일반 select
◦
70개 가져오는 데 0.3초 + 4.1초
◦
in절 사용할 땐 70개 가져오는 데 0.3초 + 0.27초 = 0.57초
참고
(3) 후속 테스트
•
1번 방식과 2번 방식의 차이가 애매한데, 좀 더 많은 양의 데이터로 좀 더 정교하게 비교해보기로 했다.
•
일단 가져오는 데이터 필드를 똑같게 만들어줬고 데이터 양을 둘다 똑같이 902개로 늘렸다.
순서 보장
•
1번 방식) fetch join (select in)
◦
0.688초 ~ 0.9xxx초
기존 쿼리
•
2번 방식) select in
◦
0.554초 + 0.322초 = 0.8xxx초
기존 쿼리
select in 쿼리
•
여전히 비슷하다. 더이상 데이터 늘리려면 query string 자리가 부족해지기에 여기까지만 테스트하고 정리했다.
(4) 결론
•
1번 방식을 사용하기로 했다. 직적 쿼리 짤 필요 없이 같은 성능으로 간단히 최적화해주기 때문이다.
N+1문제와 batch
•
정의) 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오는 현상
•
특징) batch를 사용해 fetch join을 하면 in 절로 select 해와 N+1 문제를 해결할 수 있다.