부트캠프를 하고 있는데 학습 자료를 블로그에 올리면 안 된다는 공지 때문에 공부 내용을 못 올리고 있습니다 ㅠ
따로 진행 중에 있는 프로젝트에서 fetch join을 쓸 상황이 생겼고,
쓸 것도 없겠다 아직 블로그에서 한 번도 다뤄보지 않은 fetch join을 통한 N+1 문제 해결을 정리해보겠습니다 ~
문제 상황
위는 쿼리가 두 번 나가는 문제 상황입니다.
현재 엔티티에는 끔찍하게 연관관계가 걸려있는데요
Fetch Type을 Lazy로 설정해두었기 때문에
resume 엔티티를 조회했을 때, ManyToOne 관계를 제외한
OneToMany 관계에 있는 것들은 전부 프록시에 담기고 실질적인 데이터가 담기지 않습니다.
이 프록시 객체에 접근할 때 실제 쿼리가 나가게 되는 것인데요
이 때문에 resume 엔티티를 조회하고 language level에 접근 시 쿼리가 한 번 더 나가게 되는 것입니다.
현재 운영중인 홈페이지의 고유 사용자가 1,498명인 것을 고려하면 사용자가 이력서에 한 번만 접속한다고 해도
약 750 번의 쿼리가 낭비되는 셈인데요
이를 방지하기 위해 fetch join을 사용해 보겠습니다.
문제 해결
fetch join이란 쉽게 얘기하면 그냥 resume 조회할 때 language level 테이블 inner join 시켜서
한 번에 값 전부 가져오는 방식입니다. 이렇게 해서 실제 값이 바로 담겨서 추가적인 쿼리가 나가지 않도록 하는 것이죠
바로 쿼리 작성으로 가보겠습니다.
@Query("select r from Resume r join fetch r.languageLevels
join fetch r.member where r.id = :id")
Resume findResumeWithLanguageLevels(@Param("id") Long id);
조인이 두 번 나갔는데요 그 이유는 Many To One 관계에 있는 엔티티가 존재한다면 무조건 쿼리 한 번이 더 나가기 때문에
(To One 관계에서는 Eager로 동작하기 때문!)
language level에만 조인을 해주고 싶었지만 member 테이블도 조인을 한 번 더 걸어줘야 했습니다 ㅠ
아무튼 이렇게 페치 조인을 적용하면?
이렇게 두 번 나가던 쿼리가 한 번으로 끝이 났습니다.
오랜만에 쓰는 글인데 뭔가 여기서 끝내긴 아쉬우니까 성능측정도 한 번 해보죠
성능 측정
간단한 쿼리에 데이터도 몇 개 안 담겨 있어서 그렇게 유의미한 차이는 보여주지 않지만
그래도 SQL TIME의 평균을 내어보면
Fetch join 적용 전 3ms
Fetch join 적용 후 2ms
로 약 33%의 성능 개선을 이뤄냈다 !
'☃️❄️개발일지, 트러블슈팅❄️☃️' 카테고리의 다른 글
[트러블 슈팅] 배포 후 webp 확장자 변환 에러 (GLIBC_2.29 'not found') (0) | 2025.03.30 |
---|---|
[개발일지] 시스템 아키텍처 설계 (0) | 2025.01.21 |
[개발일지] Auto Scaling Group 적용과 무중단 배포(Rolling Update) 및 CI/CD 파이프라인 설정 (0) | 2025.01.14 |
[개발일지] webp 확장자를 통한 이미지 제공 최적화 작업 (0) | 2025.01.03 |
[트러블 슈팅] 로그인 방식과 보안에 대한 고찰 - 2 (jwt 토큰과 XSS, CSRF 공격, 액세스 토큰과 리프레시 토큰의 저장 위치) (4) | 2024.12.03 |