API 응답 450ms → 25ms (18배 개선)
계층형 문서 조회 N+1 → Fetch Join + In-Memory 트리
Problem
- •실측 응답시간 450ms, 사이드바 열 때 뚜렷한 지연 체감
- •쿼리 로그 확인 후 원인 특정
Approach
@BatchSize도 검토했지만, 사이드바 트리는 전체 문서가 항상 필요해 부분 로딩의 이점이 없었습니다. Fetch Join으로 전체 로드 후 DocumentTreeResponse.from()으로 필요 필드만 DTO에 매핑해 엔티티 그래프 탐색을 차단했습니다.스크리브닝 모드(무한 스크롤)는 Pageable이 필요해 Fetch Join 시 메모리 경고가 발생, 네이밍 쿼리로 분리해 해결했습니다.Result
@Query("SELECT d FROM Document d LEFT JOIN FETCH d.parent WHERE d.project = :project ORDER BY d.order ASC")
List<Document> findByProjectWithParent(@Param("project") Project project);
// DocumentService.java — HashMap으로 O(n) 트리 조립
private List<DocumentTreeResponse> buildTreeInMemory(List<Document> documents) {
Map<UUID, DocumentTreeResponse> dtoMap = new HashMap<>();
List<DocumentTreeResponse> roots = new ArrayList<>();
for (Document doc : documents) {
dtoMap.put(doc.getId(), DocumentTreeResponse.from(doc));
}
for (Document doc : documents) {
DocumentTreeResponse dto = dtoMap.get(doc.getId());
if (doc.getParent() == null) {
roots.add(dto); // ← 루트 노드
} else {
dtoMap.get(doc.getParent().getId()).getChildren().add(dto); // ← O(1) 연결
}
}
return roots;
}