OSIV (Open Session In View)
- JPA/Hibernate에서 영속성 컨텍스트(Session)를 View 렌더링 시점까지 열어두는 기능
- Spring Boot에서는 기본값이 true
OSIV ON -> DB 커넥션을 API 응답이 완전히 끝날 때까지 유지
OSIV OFF -> 트랜잭션이 끝나면 즉시 커넥션 반환
끄는 것이 좋은 이유
- 커넥션 점유 시간 단축 : 커넥션 풀 효율 증가
- 동시 처리량 향상 : 병목 발생 예방
- 명확한 트랜잭션 경계 : 지연 로딩이 어디서든 발생할 수 있는 불명확함 제거
끌 때의 단점(TRADEOFF)
- 지연 로딩 사용 시 LazyInitializationException 발생 가능
- Fetch Join이나 DTO 변환을 명시적으로 처리해야 함
KEY TAKEAWAY
- 트래픽이 많은 서비스에서는 OSIV를 끄고(false), 필요한 데이터는 트랜잭션 안에서 미리 로딩하는 것이 성능상 유리
N+1 문제
정의 : 연관된 데이터를 조회할 때 쿼리가 N+1번 실행되는 성능 문제
예) 1번의 메인 쿼리 + N개 데이터마다 추가 쿼리 발생
// 게시글 10개 조회
List<Post> posts = postRepository.findAll(); //1번 쿼리
// 각 게시글의 작성자 조회
for(Post post:posts) {
String author = post.getUser().getName();
}
// 총 11번(1 + 10번)의 쿼리가 실행
발생 원인
- 지연 로딩 (Lazy Loading) 설정된 연관 엔티티에 접근할 때
- 반복문 안에서 연관 데이터를 조회할 때
해결 방법
핵심 : 필요한 데이터를 미리 한 번에 가져와서 쿼리 횟수를 줄이는 것
1. Fetch Join
@Query("SELECT p FROM Post p JOIN FETCH p.user"); // Post들을 가져올 때 거기 딸린 user도 같이 가져와라
List<Post> findAllWithUser();
// 1번의 JOIN 쿼리로 해결 가능
2. @EntityGraph
@EntityGraph(attributePaths = {"user"})
List<Post> findAll();
// 필드명만 {""}안에 잘 적어주면 됨
3. Batch Size
@BatchSize(size = 100)
@OneToMany
// N개를 100개씩 묶어서 조회
// N:1 관계에서 1쪽에 있음
Lazy vs Eager
Lazy를 더 선호하는 이유
1. 불필요한 데이터 로딩 방지
- Eager : 연관 데이터를 항상 조회
- Lazy : 실제 사용할 때만 조회
2. 초기 쿼리 성능
- Eager : 처음부터 JOIN이 많아져 느려질 수 있다.
- Lazy : 필요한 것만 가져와서 가볍다.
3. 유연한 최적화
- Eager : 항상 JOIN 강제
- Lazy : 상황에 따라 Fetch Join 선택 가능
Lazy의 단점
1. 페이징 불가
2. 중복 데이터
- OneToMany 관계에서 부모 엔티티 중복 발생
- DISTINCT 필요
3. 여러 컬렉션 Fetch Join 불가
- 동시 Fetch Join 시도 시 MultipleBagFetchException 발생 가능
Lazy의 적정 사용시기
- ManyToTone, OneToOne : 안전하게 사용 가능
- OneToMany : 페이징 없을 때만, DISTINCT를 추가하여 가능