본문으로 건너뛰기
개발 머꼬
개발 노트Spring
hohyeon.dev17

서비스 밖에서도 지연 로딩이 되던 이유 — open-in-view 기본값

  • #Engineering Note
  • #Performance
  • #Spring

문제 발생

LazyInitializationException을 피하려고 애쓰던 팀에서, 어느 날 컨트롤러가 엔티티를 그대로 반환해도 지연 로딩이 잘 동작하는 것을 발견했습니다. 편하다고 생각했는데 두 가지 증상이 따라왔습니다.

  • 목록 응답 하나에 쿼리가 수십 개 나감(N+1)
  • 부하가 오르면 커넥션 풀이 먼저 고갈됨

원인 분석

Spring Boot가 기본으로 켜두는 패턴입니다. Spring Boot 문서가 명시합니다 — 웹 애플리케이션을 실행 중이면 Spring Boot가 기본적으로 OpenEntityManagerInViewInterceptor를 등록하며, 이 동작을 원하지 않으면 spring.jpa.open-in-viewfalse로 설정해야 합니다.

그 인터셉터가 무엇을 하는지는 javadoc에 있습니다 — 요청의 전체 처리 동안 JPA EntityManager를 스레드에 바인딩하는 웹 요청 인터셉터. "Open EntityManager in View" 패턴을 위한 것으로, 원래 트랜잭션이 이미 완료되었음에도 웹 뷰에서 지연 로딩이 가능하게 한다.

편의의 대가는 두 가지입니다. 첫째, 트랜잭션 밖에서 쿼리가 나갑니다 — 뷰(또는 직렬화) 단계에서 프록시를 건드릴 때마다 추가 쿼리가 발생하고, 그게 목록 안에서 반복되면 N+1이 됩니다. 둘째, 커넥션 점유 시간이 요청 전체로 늘어납니다 — 응답 직렬화가 끝날 때까지 붙잡습니다.

해결 방안

  1. 끄고, 무엇이 깨지는지 확인합니다.
spring.jpa.open-in-view=false

끄는 순간 드러나는 LazyInitializationException은 새 버그가 아니라 원래 있던 설계 문제가 보이게 된 것입니다.

  1. 필요한 데이터를 트랜잭션 안에서 확정합니다. 서비스 계층에서 fetch join이나 @EntityGraph로 필요한 연관을 함께 조회합니다.
@EntityGraph(attributePaths = {"author", "tags"})
List<Post> findByStatus(Status status);
  1. 엔티티를 그대로 응답에 내보내지 않습니다. DTO로 변환하면 지연 로딩 시점이 트랜잭션 안으로 들어오고, 응답 스키마도 엔티티 변경과 분리됩니다.

  2. 끄기 전에 영향 범위를 재봅니다. 이미 이 동작에 기대고 있는 화면이 많다면 한 번에 끄는 것은 위험합니다. 엔드포인트 단위로 DTO 전환을 끝낸 뒤 마지막에 끄는 순서가 안전합니다.

  3. 쿼리 수를 눈에 보이게 만듭니다. 요청당 쿼리 수를 로그나 테스트로 확인하면, 이 설정이 켜져 있든 꺼져 있든 N+1이 조용히 자라지 않습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.