채팅방 목록 업데이트 기능을 SSE로 구현한 후 두가지 에러가 발생했다.
- IOException : Broken Pipe
- Connection is not available, request timed out after 30004ms
해당 에러가 발생한 후 추가해둔 에러 로그 코드를 통해 원인을 파악했다.
먼저 문제를 파악한 결과, SSE를 사용할 때 HTTP 호출이 끝나고도 DB Connection이 종료되지 않아 발생한 문제였다. JPA 사용시에 SSE는 객체가 만료될 때까지 지속적으로 연결을 유지하기 때문에 DB Connection 리소스가 고갈되었고, 이로 인해 두 가지 에러가 발생한 것이다.
Broken Pipe 에러는 데이터를 처리할 수 있는 대상이 없어서 발생하며, Connection is not available 에러는 DB의 타임아웃 시간 내에 데이터 처리를 완료하지 못하면 발생한다.
첫 번째 해결 방법으로는 객체의 만료 시간을 짧게 설정하는 것을 고려해보았다. 객체가 만료되기 전까지 DB 커넥션이 열려 있어 문제가 발생한 것이기 때문에 만료 시간을 줄여 해결할 수 있을 것이라고 생각했기 때문이다.
그러나 실제로 서비스를 사용할 때, 경우에 따라 에러발생까지 1분도 안걸렸던 걸 생각하면 시간을 크게 줄이지 않으면 문제를 해결할 수 없다는 생각이 들었고, 실제로도 시도해본 결과 문제가 해결되지 않았다.
먼저 만료 시간을 분단위로 설정했을 때는 에러 발생 빈도가 줄긴 했지만 여전히 에러가 발생했다. 그리고 초단위로 설정했을 때는 에러가 발생하지는 않았으나, 재연결이 자주되어 리소스 낭비로 이어져서 SSE를 사용하는 목적에 맞지 않았다.
두번째 해결방법으로는 OSIV를 false로 설정하는걸 고려해보았다.
OSIV란 영속성 컨텍스트를 뷰까지 열어두는 기능이다. (JPA는 Open EntityManager In View라고 하지만 관례상 OSIV라고 부른다.)
기본적으로 스프링에서 OSIV 값은 true로 설정되어 있다. OSIV 값이 true인 경우 트랜잭션 시작 시 영속성 컨텍스트가 DB 커넥션과 함께 생성되며, API 응답 완료까지 유지된다. 이로 인해 지연 로딩된 프록시 객체 초기화 등의 작업이 가능하다.
하지만 OSIV 값이 true인 경우 다음과 같은 문제점들이 있다.
실시간 트래픽이 중요한 애플리케이션에서는 Connection 부족 현상 발생 가능
Multi Datasource를 사용하는 경우에는 처음 맺은 DB Connection을 계속 가져가기 때문에 DB Connection 변경 어려움
OSIV 값을 false로 변경하면 트랜잭션이 종료될 때 영속성 컨텍스트와 DB 커넥션도 함께 닫혀 리소스 낭비를 최소화할 수 있다. 그러나 모든 지연 로딩 작업은 트랜잭션 내부에서 처리해야 한다. 따라서 기존의 많은 지연 로딩 코드들을 트랜잭션 내부로 이동시켜야 하는 단점과 view template에서 지연 로딩 동작하지 않는 점에 주의해야 한다.
또한 R(CRUD 중 R) 작업의 경우 서비스 레이어에서 Lazy Loading을 사용하여 엔티티 조작시 예외(Exception)가 발생하므로 해당 메서드에 @Transactional(readOnly = true) 어노테이션 추가하여 해결할 수 있다.
이번 문제는 SSE 통신 중 JPA를 사용하면서 OSIV가 true였기 때문에 발생한 것이다. 채팅 목록을 확인할 때마다 HTTP Connection과 DB Connection이 연결되는데, 계속 유지되면서 DB Connection 리소스 고갈 현상이 생겼기 때문이다.
따라서 문제를 해결하기 위해 OSIV를 false로 변경하여 영속성 컨텍스트 생존범위를 트랜잭션 범위 내부로 제한하는 방법을 선택하였다.