⚠️ Could not open JPA EntityManager for transaction

이은미·2024년 4월 29일
0

SSE 알림을 로컬에서 마무리 하고 테스트를 한 뒤 서버에 올렸다.
서버에 올린 후 10분 후 쯤 FE에서 서버가 너무 느리다고 하셨다. 확인해보니 swagger의 반응속도도 느렸고 403등의 에러가 뜨면서 되지 않았다.
타이밍이 내가 머지한 뒤여서 내가 한 머지가 문제인 것 같은데 코드 자체에는 문제가 없어 보였다.

스웨거에서 Could not open JPA EntityManager for transaction 라는 에러가 떴다.
이 에러는 Spring 프레임워크에서 JPA EntityManager를 트랜잭션 처리를 위해 열 수 없을 때 발생하고 데이터베이스 연결이 실패했거나 JPA 설정이 잘못되었을 때 발생할 수 있다.

처음에는 DB 관련 에러인가 했다.
Connection Pool 에 충분한 Connection 이 없거나, 빌려간 Connection 이 반납되지 않고 계속 사용 중일때.
1. Connection Pool 사이즈 늘리기
커넥션 풀 사이즈가 너무 작게 설정되어 있다면 늘려줄 수 있습니다. 서비스의 크기에 따라 다르겠지만 너무 크게 설정하는 것도 리소스 낭비이기 때문에 적절한 사이즈를 정해야 합니다.
2. Connection Timeout 시간 늘리기
커넥션을 맺기 위해 기다리는 시간을 늘려서 에러 메시지를 해결할 수도 있습니다.

하지만 SSE에서 비동기 처리가 되지 않아서 생긴 에러였다.
SSE 알림으로 클라이언트와 서버 간에 연결이 열려있어야 했고 그로인해 계속 자원을 점유하고 있는 상황이었다. SSE가 서버 자원을 계속적으로 점유하고 있어서 데이터베이스 작업을 처리할 수 없어 데이터베이스 조회가 불가능하고 이로 인해 에러가 지속적으로 발생했다.

지속적으로 연결을 하는 SSE의 subscribe 부분에 비동기 처리를 해야한다.

비동기 처리를 하기 위해서는 두가지 방법이 있다.

  1. @Async 사용
  • @EnableAsync 어노테이션 추가: 스프링 부트 애플리케이션의 메인 클래스나 구성 클래스에 @EnableAsync 어노테이션을 추가하기
  • 비동기로 실행할 메서드에 @Async 어노테이션 추가: 비동기로 실행할 메서드에 @Async 어노테이션을 추가하기
@Service
@EnableAsync
public class NotificationService {

 @Async
 public CompletableFuture<SseEmitter> subscribe(Long userId) {
 ...
 }

-> 이 방법을 사용하지 않은 이유는 반환값이 void 혹은 CompletableFuture 타입이어야 했다. 그럴바엔 두번째 방법을 쓰는게 더 간단하다고 생각하고 2번으로 작성했다.

  1. CompletableFuture 사용
    이 방법은 @Async 보다 더 명시적인 제어를 제공한다.
@Service
public class NotificationService {

	public CompletableFuture<SseEmitter> subscribe(Long userId) {

        return CompletableFuture.supplyAsync(() -> {
			...
            return sseEmitter; 
        }

&
CompletableFuture.supplyAsync()
이 메서드는 기본적으로 ForkJoinPool의 common pool을 사용하여 작업을 실행한다. 기본적으로 애플리케이션의 다른 비동기 작업과 같은 스레드 풀을 공유해서 이런 작업은 애플리케이션의 주요 스레드 풀에 부담을 줄 수 있다.이를 방지하기 위해 새로운 Executor를 정의하고 supplyAsync() 메서드에 전달하여 사용하는 것이 좋다고 하여 이 부분을 추가했다.

@Service
public class NotificationService {

	public CompletableFuture<SseEmitter> subscribe(Long userId) {
    
		Executor executor = Executors.newCachedThreadPool();
        
        return CompletableFuture.supplyAsync(() -> {
        ...
        return sseEmitter;
        }, executor);
    }

이렇게 수정하면 새로운 Executor를 사용하여 작업을 실행하게 된다. ForkJoinPool의 common pool을 사용하는 것보다 애플리케이션의 다른 비동기 작업과 격리되어 성능 문제를 방지할 수 있다.

이렇게 비동기로 처리하면 요청을 처리하는 동안 다른 요청도 처리할 수 있으며, 애플리케이션의 응답 시간을 최적화할 수 있다. 수정 후 서버에 올리니 다시 서버가 잘 돌아갔다!

주원님 재성님 경민님이 없었으면 SSE 오류인것도 몰랐을것 같다. 같이 해결해나가니까 뚝딱 해결 할 수 있었다! 역시 집단지성의 힘인가?! ㅋㅋㅋ 다들 너무 잘하셔서 그런것 같다!
어떻게 이렇게 잘 찾을 수 있을까? -> 오류 코드를 잘 분석하고 많이 보는것! 앞으로 GPT 말고 하나씩 뜯어보고 오류코드를 보는 눈을 길러야겠다!

https://velog.io/@jeparkk/Spring-SSE-구축과-Exception-Could-not-open-JPA-EntityManager-for-transaction-에러-해결-코루틴-적용하기
https://velog.io/@kdohyeon/Error-Could-not-open-JPA-EntityManager-for-transaction

profile
파이팅 해야지

0개의 댓글