Springboot

Thread starvation or clock leap detected, Dead Lock, hikari 오류 해결

25G 2023. 8. 16. 19:15
  • CPU에 우리 스프링 프로젝트의 Thread들이 올라가서 작업을 하게 되고, 그러다 한 Thread내의 별도 Transaction에서 추가적인 SQL문 실행이 필요해지면서Thread가 Connection을 더 필요로 하는 상황이 생겼다. 우리 스프링 부트 프로젝트에서 Thread가 Connection이 더 필요한 이유는 다음과 같다.

예시 상황

  • 트랜잭션 로그를 확인해보면 Connection이 몇개가 필요한지 계산해 볼 수 있다. 스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 사용한다고 한다. Service의 메소드가 사용될 때 Service의 트랜잭션이 시작하고, Service의 메소드에서 Repository를 사용할 때 Repository의 트랜잭션이 시작했다. 요청을 처리하는 한 개의 Thread에서 Service의 트랜잭션과 Repository의 트랜잭션 때문에 2개의 영속성 컨텍스트가 생겼고 2개의 영속성 컨텍스트를 위한 엔티티 매니저도 2개가 생겼으므로, Connection이 2개가 필요한 상황인 것이다.
    그러나 남은 Connection이 없는 상황이 발생해서 Thread는 작업을 끝내지도 못했고, Thread가 데드락에 걸렸다.
    이후로 오는 요청에 대한 작업이 유휴 Thread가 없어 NIO Connector가 계속 Thread를 생성했다.
    하지만, 남은 Connection이 없어서 Thread Starvation 상황으로 이어졌다. Thread가 추가로 생성되어야 해결되는게 아니라 Connection의 수를 늘려야 해결되는 상황이었다. 그래서 요청 작업이 발생하면 남은 Thread가 없고 NIO connector가 요청 작업을 위한 Thread를 계속 생성하는 상황이 반복하게 되었다.
    종합해보면, 기존 Thread가 작업을 못 끝내고 계속 CPU를 사용하고, 새로운 Thread들이 최대 개수까지 생성하게 되면서 스프링 부트 프로젝트의 CPU 사용량이 극단적으로 99.9%까지 올라가게 된 것이다.
    또한 Thread들이 Connection을 할당받지 못했으므로 스프링 부트 프로젝트는 아래에 첨부한 Thread Starvation 로그를 계속 출력하고, EC2서버는 CPU 99.9% 사용량으로 인해 인스턴스 상태 검사를 통과하지 못하고 죽어버린 것이다.
    해결 방법으로는 HikarilCP에서 제공하는 최적의 Maximum Pool Size를 구하는 공식을 사용했다.

공식

pool size = Tn x (Cm - 1) + 1

YML설정

spring.datasource.hikari.maximum-pool-size= N