Notion에서 작성 된 글입니다. 템플릿이 깨진다면 Notion을 확인해주세요.
[DB] HikariPool-1 - Connection is not available, request timed out after… | Notion
들어가기 앞서..
hail-buttercup-c86.notion.site
들어가기 앞서..
📄 티켓 이벤트 성능 테스트 과정에서 발생한 트러블 슈팅입니다.
문제 상황
o.h.engine.jdbc.spi.SqlExceptionHelper : HikariPool-1 - Connection is not available, request timed out after 30001ms.
c.p.t.g.e.GlobalExceptionHandler : Exception : org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction
.m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction]
org.hibernate.SQL :
애플리케이션이 Hikari 풀에서 DB 커넥션을 빌리려 했는데, connection-timeout동안 한 개도 못 받아서 타임아웃났다는 뜻이다.
즉 풀 고갈인데, DB Connection pool을 200로 지정해두었지만, 한 번에 요청을 1000개를 보내게 되어 발생하는 에러였던 것이다.
- 오류 별 해석
- HikariPool-1 - Connection is not available, request timed out after 30001ms.
→ 스프링의 예외 처리기가 컨트롤러 단에서 이 예외를 처리(혹은 500으로 변환) 완료했다는 로그. - CannotCreateTransactionException: Could not open JPA EntityManager for transaction
→ 위 타임아웃 때문에 엔티티매니저가 커넥션을 못 얻어 트랜잭션 시작 실패. 스프링이 예외를 감싸서 던짐. - Resolved [CannotCreateTransactionException ...]
→ Hikari 풀에서 DB 커넥션을 30초 기다렸지만 못 빌려서 타임아웃. (풀 고갈/긴 트랜잭션/락/느린 쿼리/DB 한도·장애 등으로 커넥션이 안 돌아옴)
- HikariPool-1 - Connection is not available, request timed out after 30001ms.
해결 과정
해결과정1. connection-timeout 재설정
타임아웃 시간을 늘려보자.
위의 에러는 현재 커넥션을 빌리려고 하는데 대기시간 30초동안 받지 못해서 발생하는 에러였다.
단순하게 타임아웃 시간을 30초에서 300초로 늘려보았다.
수정 전
spring:
datasource:
hikari:
maximum-pool-size: 1000
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 60000
max-lifetime: 540000
validation-timeout: 5000
수정 후
spring:
datasource:
hikari:
maximum-pool-size: 1000
minimum-idle: 10
connection-timeout: 300000
idle-timeout: 60000
max-lifetime: 540000
validation-timeout: 5000
결과
타임아웃 시간을 늘려주니 커넥션이 부족해도 대기하는 시간이 길어져 오류없이 처리되는 것을 확인할 수 있었다.
하지만 이 방법도 요청수를 높였더니 특정 구간에서 작업이 멈춘 것 처럼 보이는 현상이 있었다.
커넥션 대기 시간을 늘리는 것보다, 커넥션 풀 자체를 늘릴 수 있는 방법은 없을까? 늘릴 수 있다면 성능에 영향이가지않을까?
커넥션 타임아웃시간을 3초로 두고 다시 에러를 해결해보고자 다른 방안을 생각하게 되었다.
커넥션 타임 아웃 시간을 3초로 둔 뒤 다시 1000명을 요청하니, DB와 연결되지 못하고 예외가 발생하여 오류량이 엄청났다.
이는 모니터링을 살펴보면 더 심각하게 다가온다.
무려 오류율이 90.2%이다.
다른 방법으로 오류를 해결해보자.
해결방안2. Redis도입
Redis로 락을 관리해보자.
DB 커넥션 풀 사이즈를 30으로 뒀었다.
현재 필자의 서비스 코드는 비관적 락을 적용하여 동시에 30개의 스레드가 요청을 처리하려고 해도, 락을 흭득한 1개의 요청만이 요청을 처리하고 나머지 29개의 스레드는 DB 연결을 유지한 채 대기한다.
즉, 작업이 멈춰있음에도 불구하고, DB 연결을 계속 붙잡고 늘어지고 있는 것이였다.
더불어 이후에는 풀 고갈로 인해 새로운 요청은 처리 시작조차 못하고 타임아웃 예외 발생한다.
DB 락 대신 Redis 분산 락을 사용해, 대기를 DB 밖으로 이동시켜보면 어떨까?
Redis로 락을 관리해, 락을 흭득한 요청만 DB에 연결하여 요청을 처리하고, 락을 흭득하지 못한 요청은 DB 커넥션을 점유하지 않은 채, Redis에서 대기하도록 하면 DB 풀 고갈을 방지할 수 있지 않을까?
결과
Redis 분산 락 적용 방법은 아래의 포스팅을 참고하면 적용할 수 있다.
[ Spring ] Redis의 분산 락을 적용하고 Pub/Sub로 동시성 문제를 해결하자.
들어가기 앞서 . . .프로젝트에서 동시성 문제를 해결하기 위해 락에 대해서 공부하고, 또 성능 개선을 위해 고민까지 해보았다.하지만 결국 락을 걸고 동시성을 해결한다는 것은, 하나의 요청을
dev-haen.tistory.com
1000개의 요청을 동시에 보내보자. (tps 101, 표본수 1000)
10000개의 요청을 동시에 보내보자.
에러가 뜨지않고 요청을 잘 보내는 것을 확인할 수 있었다.
마무리하며…
Redis 분산 락을 적용하여 확장성을 고려한 락 환경을 구축해보았다. 또한, 비동기 처리를 통해 성능을 몇 배 이상으로 높일 수 있었다.
하지만 더 공부를 해보니, pub/sub는 내가 구현하고자 하는 서비스와 방향이 일치하지 않는 것 같았다. Redis를 잘 몰라서 비동기로 구현해보자! 해서 pub/sub를 적용해보았지만, pub/sub은 락 불공정락으로, 순서가 보장되지 않는다.
필자가 락을 적용하고자 하는 기능은 선착순과 같은 이벤트 기능을 제공하고 있기 때문에, 이런 불공정락과는 어울리지 않는다.
또한 pub/sub는 특정상황에서 “기아현상”이 나타날 가능성이 있으며 데이터 유실 시, 금액 반영이 제대로 되지 않을 가능성이 존재해, 데이터 유실이 중요치않은 실시간 알림등에 쓰이는 것 같았다.
찾아보니 비동기로 순서를 보장해주는 Redis stream, RabbitMQ, kafka등 여러가지 비동기 큐가 있었다.
또한 자바에서 직접 큐를 구현해서 순서를 보장해줄수도 있을 것 같다. 한 번 찾아보고 다른 방법을 적용해보자.
레퍼런스
'Spring > Error' 카테고리의 다른 글
[ 트러블슈팅 ]데이터 정합성 이슈: 커밋 시점으로 인해 발생한 동시성 문제 (0) | 2025.09.30 |
---|---|
[ 트러블슈팅 ] 낙관적 락 동시성 처리 에러 (0) | 2025.09.19 |
[트러블슈팅]io.jsonwebtoken.ExpiredJwtException: JWT expired 564427 (0) | 2025.03.24 |
[트러블 슈팅] Authentication Principal is not of type CustomUserDetails. Actual type: java.long.String (0) | 2025.03.01 |
[트러블슈팅] application.yml에서 oauth2 설정이 적용되지 않는 이슈 (0) | 2025.02.12 |