728x90
1. 문제 상황
- 처음에 구현된 로직에서는 HubPath를 조회할 때 관련된 Hub 정보를 개별적으로 쿼리하여 N+1 문제가 발생했습니다.
- 특히, 각 HubPath마다 출발 허브와 도착 허브에 대해 추가 쿼리가 실행되어 성능 저하가 일어났습니다.
초기 쿼리 로그
Hibernate: select h1_0.hub_id,... from p_hubs h1_0 where h1_0.hub_id=? and not(h1_0.is_deleted)
Hibernate: select h1_0.hub_id,... from p_hubs h1_0 where h1_0.hub_id=? and not(h1_0.is_deleted)
Hibernate: select hp1_0.hub_path_id,... from p_hub_paths hp1_0 join p_hubs dh1_0 on dh1_0.hub_id=hp1_0.departure_id where dh1_0.sequence between ? and ? and hp1_0.is_deleted=? order by dh1_0.sequence
Hibernate: select h1_0.hub_id,... from p_hubs h1_0 where h1_0.hub_id=?
Hibernate: select h1_0.hub_id,... from p_hubs h1_0 where h1_0.hub_id=?
Hibernate: select h1_0.hub_id,... from p_hubs h1_0 where h1_0.hub_id=?
- 위 로그를 보면, HubPath를 조회한 후 각 HubPath에 대해 출발 허브와 도착 허브를 추가로 쿼리하는 N+1 문제가 발생했습니다.
- 즉, 허브 경로를 조회할 때마다 관련 허브에 대해 반복적인 데이터베이스 요청이 발생한 것입니다.
2. 문제 원인
- HubPath 조회 쿼리에서 관련된 출발 허브와 도착 허브를 Fetch Join 없이 각각의 쿼리로 조회하고 있었습니다.
- 각 HubPath에 연결된 허브 정보가 필요할 때마다 데이터베이스에 개별적으로 조회 요청을 보냈습니다.
- 이러한 방식은 허브 경로가 많아질수록 쿼리 수가 기하급수적으로 증가하는 N+1 문제를 야기했습니다.
3. 문제 해결 : Fetch Join 적용
- 이 문제를 해결하기 위해 Fetch Join을 사용하여 HubPath와 관련된 출발 허브와 도착 허브 정보를 한 번의 쿼리로 모두 조회하도록 수정했습니다.
@Override
public List<HubPath> findPathsBetweenHubs(Hub startHub, Hub endHub) {
QHubPath qHubPath = QHubPath.hubPath;
QHub qDepartureHub = QHub.hub;
QHub qArrivalHub = QHub.hub;
return from(qHubPath)
.join(qHubPath.departureHub, qDepartureHub).fetchJoin()
.join(qHubPath.arrivalHub, qArrivalHub).fetchJoin()
.where(qDepartureHub.sequence.between(startHub.getSequence(), endHub.getSequence())
.and(qHubPath.isDeleted.eq(false)))
.orderBy(qDepartureHub.sequence.asc())
.fetch();
}
@Override
public List<HubPath> findPathsBetweenHubsReverse(Hub startHub, Hub endHub) {
QHubPath qHubPath = QHubPath.hubPath;
QHub qDepartureHub = QHub.hub;
QHub qArrivalHub = QHub.hub;
return from(qHubPath)
.join(qHubPath.departureHub, qDepartureHub).fetchJoin()
.join(qHubPath.arrivalHub, qArrivalHub).fetchJoin()
.where(qDepartureHub.sequence.between(endHub.getSequence(), startHub.getSequence())
.and(qHubPath.isDeleted.eq(false)))
.orderBy(qDepartureHub.sequence.desc())
.fetch();
}
- 위 코드에서는 출발 허브와 도착 허브를 각각
fetchJoin()
을 사용하여 즉시 로딩(Eager Loading)합니다. 이렇게 함으로써 한 번의 쿼리로 HubPath와 연결된 허브 정보까지 함께 가져오게 됩니다.
Fetch Join 적용 후 쿼리 로그
Hibernate: select hp1_0.hub_path_id,hp1_0.arrival_id,ah1_0.hub_id,...,dh1_0.hub_id,...
from p_hub_paths hp1_0
join p_hubs dh1_0 on dh1_0.hub_id=hp1_0.departure_id
join p_hubs ah1_0 on ah1_0.hub_id=hp1_0.arrival_id
where dh1_0.sequence between ? and ? and hp1_0.is_deleted=?
order by dh1_0.sequence
4. 성능 개선 효과
- 쿼리 횟수 감소: N+1 문제 발생 시 무수히 많은 쿼리가 발생하던 것을 3개의 쿼리로 줄였습니다.
- (출발 허브 조회, 도착 허브 조회, HubPath 조회)
- 네트워크 통신 오버헤드 감소: 데이터베이스와의 통신 횟수가 줄어들어 전체 시스템 성능이 향상되었습니다.
- 응답 속도 개선: 데이터베이스에서 불필요한 반복 쿼리를 제거하여 API 응답 속도가 크게 향상되었습니다.
'TIL,일일 회고' 카테고리의 다른 글
[TIL, 일일 회고] 2024.09.23 - Feign Client에서 PATCH 메서드 사용 시 발생하는 문제와 해결 방법 (0) | 2024.09.23 |
---|---|
[TIL, 일일 회고] 2024.09.22 - 대용량 데이터 에러 해결 트러블 슈팅 (0) | 2024.09.22 |
[TIL, 일일 회고] 2024.09.20 - N+1 문제에서 Fetch Join을 선택하는 이유 (0) | 2024.09.20 |
[TIL, 일일 회고] 2024.09.19 - DDL 자동 생성하기 (0) | 2024.09.19 |
[TIL, 일일 회고] 2024.09.18 - PostgreSQL에서 문자열 길이 초과 오류 해결: @Lob 어노테이션 (0) | 2024.09.18 |