본문 바로가기
🚀 데브 노트 (IT 지식 & 팁)

API가 3초간 멈춘다고요? 스프링 부트 OSIV(Open Session In View)의 배신과 최적화 가이드

by 데브프리 2026. 8. 5.

안녕하세요, 여러분의 든든한 IT 길잡이 데브프리입니다.

 

스프링 부트(Spring Boot)로 JPA 기반의 백엔드 프로젝트를 띄울 때마다, 콘솔 창에 노란색 경고(WARN) 로그가 거슬리게 찍히는 것을 본 적 있으신가요?

Plaintext

WARN 12345 --- [main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

초보 시절에는 이 경고를 대수롭지 않게 넘기곤 합니다. 서버가 죽는 것도 아니고, 기능도 아주 잘 동작하니까요. 하지만 실무에서 대규모 트래픽을 처리하는 시스템을 운영하다 보면, 이 경고를 무시한 대가로 끔찍한 API 지연(Delay)과 커넥션 풀 고갈 장애를 마주하게 됩니다.

 

오늘은 스프링 부트가 기본적으로 켜두고 시작하는 양날의 검, OSIV(Open Session In View)의 동작 원리를 객관적으로 분석하고, 실무에서 이를 언제 어떻게 제어해야 하는지 명쾌한 트러블슈팅 가이드를 제시해 드립니다.

 

1. OSIV(Open Session In View)란 무엇인가?

OSIV는 이름 그대로 '뷰(View, 혹은 Controller의 응답 단계)가 렌더링 될 때까지 영속성 컨텍스트(Session/EntityManager)를 열어둔다'는 뜻입니다. 2026년 현재 최신 Spring Boot 3.x 버전에서도 하위 호환성과 개발 편의성을 위해 기본값은 true로 설정되어 있습니다.

 

JPA를 사용하면 지연 로딩(Lazy Loading)이라는 강력한 기능을 쓰게 됩니다. 엔티티를 조회할 때 연관된 데이터(예: 회원의 작성 게시글 목록)를 처음부터 다 가져오지 않고, 실제 코드에서 user.getPosts()를 호출하는 순간 데이터베이스(DB)에 쿼리를 날려 가져오는 기능이죠.

 

이 지연 로딩이 동작하려면 DB와의 커넥션(영속성 컨텍스트)이 살아있어야 합니다.

OSIV가 켜져(true) 있으면, 클라이언트의 HTTP 요청이 들어와서 컨트롤러를 거쳐 다시 응답(JSON)이 나갈 때까지 DB 커넥션이 끈질기게 살아있습니다. 덕분에 개발자는 컨트롤러에서 편하게 지연 로딩을 호출할 수 있습니다.

 

2. 실전 트러블슈팅: OSIV가 쏘아 올린 장애의 재구성

편리해 보이는 OSIV가 실무에서는 왜 '성능 저하의 주범'으로 불릴까요? 백엔드 서버에서 외부 결제 API를 연동하는 시나리오를 생각해 봅시다.

  1. 사용자가 결제 요청 API를 호출합니다.
  2. 백엔드의 Service 레이어에서 DB에 접근해 사용자 정보를 조회합니다. (이 시점에 DB 커넥션을 획득합니다.)
  3. 외부 PG사(토스, 포트원 등)의 결제 승인 API를 호출합니다. (네트워크 지연으로 약 2초 소요)
  4. 결제 성공 후 DB에 주문 상태를 업데이트하고 API 응답을 반환합니다.

왜 서버가 멈췄을까?

OSIV가 켜져 있으면, 스프링 부트는 클라이언트에게 응답을 반환할 때까지 DB 커넥션을 반납하지 않습니다. 즉, 외부 결제 API를 기다리는 2초 내내 소중한 DB 커넥션을 꽉 쥐고 놓지 않는 것입니다.

 

만약 커넥션 풀(HikariCP)의 최대 사이즈가 10개인데, 동시에 10명의 사용자가 결제를 시도한다면? 10개의 커넥션이 모두 외부 API 응답을 기다리며 묶여버립니다. 11번째 사용자부터는 DB를 단 0.01초만 사용하는 가벼운 조회 요청조차 처리하지 못하고 커넥션 타임아웃(Timeout) 에러를 맞고 튕겨 나가게 됩니다.

OSIV 활성화 시 외부 API 대기로 인한 데이터베이스 커넥션 고갈 비유
OSIV가 활성화된 상태에서 외부 네트워크 통신처럼 오래 걸리는 작업을 수행하면, 해당 작업이 끝날 때까지 DB 커넥션을 반납하지 않아 시스템 전체에 심각한 병목을 유발합니다.

 

3. 베스트 프랙티스: OSIV 끄기와 LazyInitializationException 해결

대용량 트래픽을 감당하는 실무 백엔드 서버의 첫 번째 최적화 원칙은 OSIV를 끄는 것입니다. application.yml 파일에 아래 한 줄을 추가합니다.

YAML

spring:
  jpa:
    open-in-view: false # 💡 영속성 컨텍스트 생존 범위를 트랜잭션 내부로 제한

이렇게 끄고 나면, DB 커넥션은 @Transactional이 선언된 Service 레이어 안에서만 살아있고 컨트롤러로 넘어오는 순간 즉시 반납됩니다. 외부 API를 10초 동안 호출하더라도 DB 커넥션은 이미 반납되었으므로 다른 사용자의 요청을 원활하게 처리할 수 있죠.

트러블슈팅: 지연 로딩 에러의 대처법

OSIV를 끄고 서버를 실행하면, 기존에 컨트롤러에서 연관된 데이터를 꺼내 쓰던 코드들이 일제히 붉은 에러를 뿜어냅니다.

 

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

 

트랜잭션(Session)이 이미 끝났는데 뒤늦게 컨트롤러에서 지연 로딩을 시도했기 때문입니다. 이를 해결하는 객관적인 실무 프랙티스는 다음과 같습니다.

  • 해결책 1: 패치 조인(Fetch Join) 사용 가장 깔끔한 방법입니다. Repository에서 데이터를 가져올 때, 나중에 컨트롤러에서 필요할 연관 데이터까지 쿼리 한 번으로 미리 싹 다 가져옵니다.
    Java
    
    @Query("SELECT u FROM User u JOIN FETCH u.posts WHERE u.id = :id")
    Optional<User> findByIdWithPosts(@Param("id") Long id);
  • 해결책 2: 서비스 레이어에서 DTO로 변환하여 반환
    엔티티 객체 자체를 컨트롤러로 넘기는 것은 실무 안티 패턴입니다. @Transactional이 걸려있는 서비스 레이어 안에서 필요한 지연 로딩을 모두 수행한 뒤, 그 결과를 '안전한 DTO(Data Transfer Object)'에 꾹꾹 담아서 컨트롤러로 반환하세요. DTO는 DB와 전혀 상관없는 순수 자바 객체이므로 OSIV가 꺼져 있어도 아무 문제가 없습니다.

 

💡 [데브프리의 실무 꿀팁: OSIV를 껐는데도 서버가 뻗는다고요? '@Transactional'을 의심하세요!]

OSIV 설정을 false로 껐다고 해서 모든 병목이 마법처럼 사라지는 것은 아닙니다. 초보 개발자들이 가장 많이 하는 실수가 바로 외부 API(결제)를 호출하는 서비스 로직 전체를 하나의 @Transactional로 묶어버리는 것입니다.

OSIV를 끄면 뷰 렌더링 시점의 커넥션 물고 늘어지기는 막을 수 있지만, @Transactional이 시작되고 끝날 때까지는 여전히 DB 커넥션을 쥐고 있습니다. 즉, 트랜잭션 안에서 2초짜리 외부 결제 API를 호출하면 결국 2초 동안 커넥션이 묶이는 것은 똑같습니다!

이를 완벽하게 해결하려면 'DB를 수정하는 트랜잭션 로직'과 '외부 API 통신 로직'을 철저하게 분리(Facade 패턴 등 활용)하여, 외부 네트워크 통신 중에는 DB 커넥션을 절대 들고 있지 않도록 아키텍처를 세밀하게 쪼개야 한다는 점을 잊지 마세요!

 

4. 전문가의 확고한 결론 (Verdict)

OSIV (Open Session In View), 실무에 언제 켜두고 언제 꺼야 할까요?

  • 👍 OSIV를 켜둬도 좋은 타이밍 (유지 권장):
    트래픽이 많지 않은 사내 백오피스(Admin) 시스템이나, 런칭을 앞두고 미친 듯이 빠른 개발 속도가 필요한 초기 프로토타입(MVP) 단계에서는 켜두는 것이 유리합니다. 지연 로딩 설계를 깊게 고민할 필요 없이 컨트롤러와 뷰 템플릿(Thymeleaf 등) 어디서든 자유롭게 객체 그래프를 탐색할 수 있어 생산성이 극대화됩니다.
  • 🛑 OSIV를 반드시 꺼야 할 때 (비활성화 필수):
    사용자 트래픽이 집중되는 B2C 고객 대상 서비스이거나, 서비스 로직 내에 외부 API 호출(결제, 알림톡, 타 마이크로서비스 통신 등)이 포함된 경우라면 무조건 spring.jpa.open-in-view=false로 설정해야 합니다. DB 커넥션은 트랜잭션이 지속되는 아주 짧은 시간 동안만 대여하고 즉시 반환하는 것이 대용량 트래픽 처리의 절대적인 기본 원칙입니다.

스프링 부트가 띄우는 경고 로그에는 다 그럴만한 이유가 있습니다. OSIV의 생명 주기를 정확히 통제하고, 엔티티 대신 철저하게 DTO를 활용하는 아키텍처를 설계하신다면, 트래픽이 폭주해도 끄떡없는 탄탄한 백엔드 서버를 구축하실 수 있을 것입니다.

 

항상 팩트에 기반한 객관적인 실무 가이드로 여러분의 삽질 시간을 줄여드리겠습니다. 다음 포스팅에서도 개발자들에게 유용한 도구 최적화 팁으로 찾아뵙겠습니다!

 

👉 실서버 뻗기 전에 꼭 확인하세요! 내 서버 스펙에 맞는 최적의 DB 커넥션 풀 개수 계산기 [링크]