1. 상품 목록 조회 API (GET /items) 최적화
상품 목록 조회 API는 한 페이지당 20개의 상품 데이터를 반환하도록 구현되어 있다.

API 호출 시 그라파나(Grafana)를 통해 쿼리 실행 내역을 모니터링한 결과, 단 한 번의 요청에 무려 21개의 쿼리가 발생하는 것을 확인했다.

1.1. 원인 분석
이러한 현상이 발생하는 원인은 ItemService의 getItemList() 메서드 로직에 있다.
- ItemRepository.searchItems(...)가 호출되면서 Item 엔티티 목록을 조회하는 쿼리 1회가 실행된다.
- 조회된 엔티티들을 DTO로 변환하기 위해 .map(item -> ItemListResponse.fromEntity(...))가 실행된다.
- 문제는 ItemListResponse.fromEntity 내부에서 item.getItemImages()를 호출하는 순간 발생한다.
- Item과 ItemImage는 지연 로딩(Lazy Loading) 관계이므로, 각 상품(Item)마다 이미지를 가져오기 위한 추가 쿼리가 20회 발생한다.
결과적으로 1 (목록 조회) + 20 (단건 상세 조회) = 21번의 쿼리가 나가는 전형적인 N+1 문제가 발생한 것이다.
1.2. 해결: default_batch_fetch_size 설정
Item과 ItemImage 같은 OneToMany(컬렉션) 관계에서 페이징 처리를 할 때 Fetch Join을 사용하면, 모든 데이터를 메모리에 올린 후 페이징을 처리하는 문제가 발생할 수 있다. (Out Of Memory 위험) 따라서 이 경우에는 Fetch Join 대신 Batch Size 설정을 통해 IN 쿼리로 묶어서 조회하는 방식을 선택했다.
`application.yml`
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100 # ✅ 주요 설정
1.3. 결과 확인
설정 적용 후 다시 테스트한 결과, 기존 21방이었던 쿼리가 단 2방으로 획기적으로 줄어든 것을 확인할 수 있다.
- 1번: Item 목록 조회
- 2번: 조회된 Item들의 ID를 IN 절로 묶어 Image 목록 조회

2. 채팅방 목록 조회 API 최적화
상품 목록뿐만 아니라, 채팅방 목록을 조회할 때도 불필요한 쿼리가 다수 발생하는 것을 확인했다. (기존 약 6회 이상의 쿼리 발생)

2.1. 원인 분석
기존 ChatRoomRepository의 조회 메서드는 단순히 ChatRoom 엔티티만 조회하고 있었다.
@Query("""
SELECT cr
FROM ChatRoom cr
WHERE cr.buyer.id = :memberId OR cr.seller.id = :memberId
""")
List<ChatRoom> findMyChatRooms(@Param("memberId") Long memberId);
ChatRoom을 조회한 후, 연관된 Item(상품), Seller(판매자), Buyer(구매자) 정보를 참조할 때마다 지연 로딩으로 인해 추가 쿼리가 발생하고 있었다.
2.2. 해결: JOIN FETCH 적용
ChatRoom 입장에서 Item, Seller, Buyer는 모두 ManyToOne(N:1) 관계이다. 이 경우 데이터 뻥튀기(Cartesian Product) 문제가 발생하지 않으므로, JOIN FETCH를 사용하여 한 번의 쿼리로 연관 데이터를 모두 가져오는 것이 가장 효율적이다.
@Query("""
SELECT cr
FROM ChatRoom cr
JOIN FETCH cr.item i
JOIN FETCH cr.seller s
JOIN FETCH cr.buyer b
WHERE b.id = :memberId OR s.id = :memberId
""")
List<ChatRoom> findMyChatRooms(@Param("memberId") Long memberId);
2.3. 결과 확인
JOIN FETCH 적용 후, 채팅방 목록과 관련된 모든 데이터를 단 1개의 쿼리로 조회할 수 있게 되었다.

💡 요약
- OneToMany 컬렉션 페이징 조회 (Item → Images): default_batch_fetch_size 설정을 통해 IN 쿼리로 최적화 (21회 → 2회)
- ManyToOne 단일 관계 조회 (ChatRoom → Item/Member): JOIN FETCH를 사용하여 즉시 로딩으로 최적화 (N회 → 1회)
'Project > Secondhand Market' 카테고리의 다른 글
| [7] 성능 최적화 - Redis(캐싱) (0) | 2026.01.27 |
|---|---|
| [6] 성능 최적화(4): Async 적용 (0) | 2026.01.25 |
| [4] 성능 최적화(2): Lock (0) | 2026.01.25 |
| [3] 성능 최적화(1): 복합 Index & FullText Index (1) | 2026.01.21 |
| [2] 프로젝트 모니터링 설정과 테스트 환경 구축 (1) | 2026.01.10 |
