1. 서론
요즘 대부분 Spring Boot JPA를 많이 사용하고 추가로
SseEmiters로 실시간 알림기능 구현을 하려고 하면 대다수
IOException : Broken Pipe, Connection is not available, request timed out after 30004ms
이런 에러가 발생할 것입니다.
원인은
JPA의 영속성 컨텍스트, OSIV가 true로 설정되서 생기는 문제입니다.
이 글에서는 OSIV가 뭔지 정확히 왜 이런 에러가 생기는 건지 알아보고자 합니다.
2. 프록시 객체와 지연로딩
2 - 1. 프록시란?
프록시는 실제 클래스를 상속 받아서 만들어진 가짜 객체라고 할 수 있습니다.
이러한 프록시 객체는 실제 객채의 참조를 보관하는 특징을 가지고 있어 프록시 객체를 통해서 메서드를 호출하면
실제 객체의 메서드를 호출합니다.
2 - 2. 지연로딩이란?
JPA로 연관관계(1:N 등)의 DB를 조회할 때 데이터를 가져오는 방식이 두 가지가 있습니다.
- FetchType.LAZY : 지연로딩
- 데이터를 조회할 때 자신과 연관된 데이터를 실제로 사용할 때 조회하는 방식
- FetchType.EAGER : 즉시로딩
- 데이터를 조회할 때 연관된 데이터를 한 번에 불러오는 방식
그렇다면 지연로딩이 왜 필요할까요?
프로젝트가 커지면 그에 따라 연관관계 데이터들도 많아집니다.
만약 Member테이블의 연관된 데이터베이스가 20개인데 나는 member의 name 컬럼값만 필요한상황에서
연결방식이 다 EAGER로 되어있다면?
Member 엔티티 하나만 조회해도 20개의 엔티티가 더 조회되는 문제가 발생합니다.
이것을 보통 N + 1 문제라고 합니다.
3. OSIV (Open-Session-In-View)
OSIV는 Open-Session-In-View의 약자로 관례상 OSIV라고 합니다.
spring.jap.open-in-view의 기본값 true 이며 JPA를 사용한 상태에서 서버를 실행시키면 다음과 같은 warn 경고메시지가 뜹니다.
WARN 13668 --- [ 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
위에 나온 메시지처럼 OSIV가 true일 경우 영속성 컨텍스트가 트랜잭션 범위를 넘어서 레이어까지 살아있습니다.
Spring MVC패턴의 이라면 Cotroller에서 JPA를 사용하지 않아도 응답이 끝날때까지 살아있다는거죠.
위에 지연로딩과 덧붙히면
Member엔티티가 Service단에서 수정이 끝났지만, Controller단에서도 영속 상태이므로
연관된 다른 엔티티도 필요하다면 Controller단에서도 조회할 수 있습니다.
그런데 이 전략은 너무 오랜시간동안 데이터베이스 커넥션 리소스를 사용하기 때문에, 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자랄 수 있고, 이것은 결국 장애로 이어집니다.
예를 들어서 Controller에서 외부 API를 호출하면 외부 API 대기 시간만큼 커넥션 리소스를 반횐하지 못하고, 유지해야합니다.
그럼 OSIV를 false로 끄면 어떻게 될까요?
트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환합니다.
따라서 커넥션 리소스를 낭비하지 않게됩니다.
다만 이렇게 되면 모든 지연로딩을 트랜잭션 안에서 처리해야 합니다.
따라서 지금까지 작성한 많은 지연 로딩 코드들의 기능들이 있으면 모두 트랜잭션 안으로 넣어야합니다.
그렇다면 OSIV가 켜져있을 때 SSE에서 에러가 뜨는 이유가 무엇일까요?
실시간 알림을 받아오기 위한 SSE 통신은 클라이언트의 요청이 계속해서 연결된 상태이기 때문에
커넥션을 반환하지 않습니다.
즉, SSE에 연결된 사용자가 10명이 넘어가서 커넥션이 한번이라도 연결이 되면
기본으로 생성된 커넥션 10개가 고갈이 되어 더 이상 사용할 connection이 없어서 데이터를 받아오지 못하는 문제가 있습니다.
4. 결론
- OISV를 켠다?
- 실시간 알림기능에서 데이터베이스와 연결하는 것이 어렵습니다.
- OSIV를 끈다?
- 지연로딩으로 되어있는 엔티티가 controller단에서 사용되는 코드들이 있으면 전부 바꿔야합니다.
- 지연 로딩을 전부 즉시로딩으로 바뀌어도 되지만 그렇게되면 성능이 낮아집니다.