본문 바로가기

프로젝트/부기온앤온

HikariCP가 Pending되는 현상과 해결과정

개발과정에서 마주한 문제 하나를 소개하려고 합니다.

저희 프로젝트의 구조는 Spring MVC를 메인 WAS로 두고 사용합니다.

핵심 비즈니스 로직과 거리가 멀거나 WAS에게 부담이 되는 기능 및 서비스의 다른 프로젝트에서 사용이 가능한 것들은 외부에 따로 서버를 두고 사용하고 있습니다. 그 중에 푸시 알람과 같은 서비스는 AWS Lambda + Gateway를 통해서 처리하고 있습니다.

게시글 추가와 맨션 요청이 같이 POST /posts로 들어오는 경우를 예시로 들어보며 흐름을 이해해보도록 하겠습니다

  1. 유저가 글 내용과 A유저에게 맨션을 하며 글을 추가한다 (POST / posts 요청)
  2. WAS는 해당 요청을 받고 글을 추가한다
  3. 마지막에 Controller 계층에서 AWS Gateway에게 A유저의 맨션 푸시알람을 요청한다.
  4. Lambda가 Gateway의 요청을 받고 처리한다.

이 상황에서 성능의 테스트를 위해서 100개의 API호출을 동시에 진행해보았습니다.

그 결과 다음과 같은 에러가 발생하였습니다.

DB 쿼리를 하기 전에, Connection Pool에서 스레드를 가져와야하는데 30초 동안 대기해도 받을 수 없어서 SQLTransientConnectionException이 발생하였습니다.

이 원인을 살펴보니 AWS Lambda 쪽에서 API 요청을 잘못 하여 타임아웃이 걸렸습니다.

람다의 요청이 느려서 Connection을 받지 못해서 생긴 원인입니다. 즉, WAS의 문제라기 보다 외부 서버의 문제로 인해서 발생한 문제로 보입니다. 람다가 Firebase API 요청을 적절하게 호출하여 해결하는 것으로 간단하게 끝낼 수도 있습니다.

 

문제가 해결된 것처럼 보입니다. 하지만, 이 문제를 통해서 API가 호출되는 동안 커넥션이 반환되지 않는 사실을 찾았습니다. 이렇게 되면 외부 서버에서 장애가 발생하면 맨션을 위한 API에 영향을 줄 뿐만아니라 다른 API도 영향을 받게 됩니다. 맨션이 안되는것은 외부 API가 문제가 있기 때문인 것은 이해가 됩니다. 하지만, 다른 API에 영향을 주는 것은 바람직하지 못합니다.

 

Service 계층에서 글 내용을 추가했고, 컨트롤러에서 API를 호출하는데 이 계층까지 커넥션을 가지고 있어서 문제가 발생합니다. 만약, Service 계층까지만 (정확히는 @Transactional) 커넥션을 사용하고 바로 반납하게 된다면 Controller에서 호출한 외부 API의 문제가 발생해도 커넥션과 무관하게 됩니다.

지금은 API의 결과가 올 때까지 연결을 가지고 있는 상태입니다. 우리 서버의 목숨을 외부 서버가 쥐고 있는 듯 합니다.

 

해결방법 1- Open Session in view

여기서 Open-Session-In-View(OSIV)라는 JPA의 옵션을 이용할 수 있습니다.

JPA의 영속성 컨텍스트에서 엔티티를 관리하기 위해서 커넥션을 가지고 있어야합니다.

OSIV옵션을 키게 되면 영속성 컨텍스트의 생존 범위가 Controller / View 수준까지 내려가게 됩니다. 만약 View쪽에서 지연로딩을 하게 되면 연결된 커넥션을 통해서 쿼리가 발생하게 됩니다. 해당 옵션이 default로 켜 있기 때문에 트랜젝션이 끝나고 Service의 결과가 Controller로 리턴되어도 커넥션이 반환되지 않았던 것입니다.

해당 옵션을 끄게 되면 Service 계층(@Transactional)범위 까지만 커넥션을 유지합니다. 즉, Controller 계층에서는 커넥션이 존재하지 않습니다.

그라파나의 대시보드를 보면 12시 56분에 pending이 다수 발생하며 Connection Time out이 발생한 것을 볼 수 있습니다.

 

이번에는 OSIV를 off하여 Service 계층에서 커넥션을 반환해서 테스트해보도록 하겠습니다.

14시 08분에 테스트를 진행하였습니다. Connection Pool이 매우 안정적입니다. API의 상태코드도 모두 200입니다.

이와 같이 OSIV를 이용해서 영속성 컨텍스트의 범위를 조절할 수 있습니다. 즉, 커넥션을 되돌리는 타이밍을 정할 수 있습니다. 하지만, OSIV를 이용할 경우 조심해야합니다. Controller 이후의 레벨에서 지연로딩을 할 수 없기 때문에 NPE가 발생할 수 있습니다.

가급적 OSIV를 끄는게 좋습니다. 왜냐면 커넥션의 범위는 가급적 짧게 가져가서 Connection pool를 안정적으로 관리할 수 있기 때문입니다. 또한 외부 서버가 장애가 발생해도 커넥션 풀과 무관해집니다.

 

해결방법 2 - 비동기 API 호출

운영 환경에 따라서 OSIV를 끄기 어려울 수도 있습니다. 예로들어, 템플릿 엔진에서 이미 지연로딩을 사용하고 있는 경우가 있습니다.

이렇게 끄기 어려운 경우, 현재 요청한 API를 비동기적으로 호출하여 처리할 수 있습니다.

하지만, 모든 API를 비동기적으로 처리하기 어려울 수도 있습니다. 푸시알람 같은 경우에는 알람이 간헐적으로 발생하지 않아도 메인 서비스에는 큰 영향이 없습니다. 다만, 핵심 비즈니스 로직이라면 동기적으로 API를 호출하고 그 결과 에러의 핸들링이 필요할 수 있습니다. 이러한 경우에는 동기적으로 사용해야합니다.

이 해결방법은 API호출이 비동기적으로 사용한다는 가정하에 가능한 문제입니다.

 

이러한 방법 이외도 커넥션 풀 사이즈를 조절할 수 있지만, 근본적인 해결책과 거리가 있어보입니다. 이번 문제에서 중요한 것은 특정 API에서 사용하는 외부 API가 장애가 나서 시간이 오래 소요되면 서버 전체의 커넥션 풀이 마를 수 있다는 것입니다.

해결방법1을 통해서 해결하는 것이 바람직하다고 생각합니다.

'프로젝트 > 부기온앤온' 카테고리의 다른 글

인덱스를 이용한 쿼리 튜닝  (0) 2023.04.07
부기온앤온 프로젝트 회고  (0) 2023.04.06