본문 바로가기

오류일기

hikaripool-1 - thread starvation or clock leap detected 원인, 해결법

현상

AWS의 EC2 인스턴스에서 스프링부트 jar파일을 실행시켰을때 종종 hikaripool-1 - thread starvation or clock leap detected 다음과 같은 오류가 발행하며 죽어버리는 현상 발견하였습니다.

환경

  • JDK 17
  • SpringBoot: 3.1.1
  • (dev)mysql: 8.0.32
  • (local)h2
  • JPA: 3.1.2

원인

❗관련 자료를 찾아보고 정리한 내용이므로 부정확한 내용이 섞여있을 수 있습니다.

스프링부트에는 다음과 같이 @PostConstructor를 통해서 더미 데이터를 save하는 코드를 작성하였고 이 코드가 담긴 jar파일을 ec2에서 실행시켰을떄 hikaripool-1 - thread starvation or clock leap detected 과 같은 로그를 찍으면서 서버의 cpu사용률 100%를 찍고 OutOfMemory를 내며 서버가 죽었습니다.

@PostConstruct
    public void init(){
        log.info("MakeUser init");

        User testUser1 = User.builder()
                .authority("ROLE_ADMIN")
                .name("testUserName1")
                .code("testUserCode1")
                .login_passwd(passwordEncoder.encode("testUserLoginPasswd1"))
                .phoneNumber("111-1111-1111")
                .rank("STAFF")
                .occupation("testUserOccupation1")
                .FCMToken("testUserFCMToken1")
                .build();

        User testUser2 = User.builder()
                .authority("ROLE_ADMIN")
                .name("testUserName2")
                .code("testUserCode2")
                .login_passwd(passwordEncoder.encode("testUserLoginPasswd2"))
                .phoneNumber("222-2222-2222")
                .rank("STAFF")
                .occupation("testUserOccupation2")
                .FCMToken("testUserFCMToken2")
                .build();

        User testUser3 = User.builder()
                .authority("ROLE_ADMIN")
                .name("testUserName3")
                .code("testUserCode3")
                .login_passwd(passwordEncoder.encode("testUserLoginPasswd3"))
                .phoneNumber("333-3333-3333")
                .rank("STAFF")
                .occupation("testUserOccupation3")
                .FCMToken("testUserFCMToken3")
                .build();

        userRepository.saveAll(java.util.List.of(testUser1, testUser2, testUser3));
    }

다음과 같은 오류가 나는 근본적인 원인은 JPA가 DB와 연결하는 동작 방식과 관련이 있습니다.

  • JPA는 DB Connection Pool의 크기를 정해서 미리 만들어 사용하고 있습니다. (기본 값 10)
  • JPA에서 엔티티의 키값을 설정할때 다음과 같이 @GeneratedValue의 옵션을 Auto 로 할 경우 JPA에서 자동으로 id값을 매핑해줍니다.
  • . . @Id @GeneratedValue(strategy = GenerationType.AUTO) . .
  • 이때 어떤 DB를 사용하느냐에 따라서 매핑되는 방식이 다릅니다.
  • 이번 상황같은 경우 로컬에서 사용하는 DB는 h2이고 dev용으로 사용하는 DB는 mysql이였습니다. h2와 mysql에서 JPA의 @GeneratedValue(strategy = GenerationType.AUTO) 를 각각 다음과 같은 방식으로 수행합니다.
    • h2는 내부의 시퀸스를 생성하고 해당 시퀸스에서 순차적으로 값을 가져와서 PK에 값을 할당해줍니다.ex) h2
    • mysql은 내부의 시퀸스용 테이블을 만들어서 값을 하나씩 가져옵니다.ex)mysql
  • JPA에서 DB로 값을 가져오기 위해서는 테이블당 커넥션을 맺어야 한다.
  • 만약 테이블에 save를 할때 h2 DB를 사용한다면 시퀸스를 통해서 id값을 생성해주기에 DB connection 스레드의 수는 하나로 충분하지만 mysql의 경우 테이블에 id값을 다른 테이블에서 가져와 생성하기에 DB connection 스레드의 수는 두개가 필요합니다.
  • 이때 만약 mysql과 연결되어있는 JPA에서 id생성전략을 Auto로 설정한 테이블에 insert를 시도한다면 충분히 DeadLock과 thread starvation 문제가 발생할수 있습니다.

ex) h2
ex) mysql

고치는 법

  • 스레드 풀의 크기 때문에 발생하는 문제이므로 근본적인 원인인 스레드의 수를 늘리면 해결되는 문제 입니다.
  • 그러나 근거 없이 스레드를 늘리면 서버에도 부담이 가기 때문에 HikariCP에서 제공하는 최적의 스레드 풀의 개수를 정하는 방법에 따라서 스레드 풀의 크기를 조정했습니다.
  • JPA가 사용하는 Hikari cp pool의 최대 스레드 수를 기존 10개에서 101개로 늘렸으며
  • tomcat server가 가지고 있는 스레드의 수는 기존 200개 에서 100개로 줄였습니다.

hikaricp의 스레드 풀 사이즈 설정 코드
tomcat server의 스레드 풀 사이즈 설정 코드

참고

 

Spring Data JPA Transaction Propagation, EntityManager, PersistContext에 관한 고찰

https://velog.io/@mbsik6082/Thread-starvation-or-clock-leap-detected-Dead-Lock-hikari-%EC%98%A4%EB%A5%98이 글을 작성하게 된 이유는 친구와 면접 준비를 하다가 내가 트러블슈팅한

velog.io

 

About Pool Sizing

光 HikariCP・A solid, high-performance, JDBC connection pool at last. - brettwooldridge/HikariCP

github.com