본문 바로가기
IT/Spring

HikariCP 데드락 이슈

by 봉즙 2024. 11. 14.

HikariCP 데드락 이슈와 해결 방법

JMeter로 스트레스 테스트를 진행하던 중 아래와 같은 에러가 발생했다.

Connection is not available, request timed out after 30000ms.

@Entity
data class Domain(
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    val id: Long? = null,
    val name: String? = null,
)

@Service
class DomainService(
    val repository: DomainRepository
) {
    @Transactional
    fun save(domain: Domain): Domain {
        return repository.save(domain)
    }
}

@SpringBootTest
class DomainServiceTest {

    @Autowired
    lateinit var domainService: DomainService

    @Test
    fun save() {
        // given
        val domain = Domain(name = "test")

        // when
        val result = domainService.save(domain)

        // then
        assertNotNull(result.id)
    }
}

에러 발생 시점을 찾기 위해 커넥션 풀을 1개로 설정한 후 테스트를 다시 실행해 보면 동일한 에러가 발생한다.

2024-11-14T21:43:11.571+09:00  WARN 34576 --- [demo] [    Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 0, SQLState: null
2024-11-14T21:43:11.571+09:00 ERROR 34576 --- [demo] [    Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper   : HikariPool-1 - Connection is not available, request timed out after 30005ms (total=1, active=1, idle=0, waiting=0)

unable to obtain isolated JDBC connection [HikariPool-1 - Connection is not available, request timed out after 30005ms (total=1, active=1, idle=0, waiting=0)] [n/a]
org.springframework.dao.DataAccessResourceFailureException: unable to obtain isolated JDBC connection [HikariPool-1 - Connection is not available, request timed out after 30005ms (total=1, active=1, idle=0, waiting=0)] [n/a]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:274)

connection pool 이 부족하여 데드락이 발생하게 되는데 이를 pool locking 이라고 하며 아래와 같은 해결 방법을 제시한다.

pool size = Tn x (Cm - 1) + 1
  • Tn: 스레드 수
  • Cm: 서버의 코어 수
spring:
  datasource:
    hikari:
      minimum-idle: 10
      maximum-pool-size: 10
      idle-timeout: 600000
      connection-timeout: 30000 
  • minimum-idle: 풀에서 유지할 최소 유휴 연결 수, 이 값이 작으면 유휴 연결 수가 줄어들며, 큰 값으로 설정할 경우 연결 풀이 필요한 순간 더 많은 연결을 미리 생성해두어 성능을 높일 수 있다.
  • maximum-pool-size: 풀의 최대 크기이며, 동시에 사용할 수 있는 최대 연결 수를 의미, 일반적으로 시스템 성능에 따라 결정해야 하며, 예상 동시 스레드 수와 서버 코어 수를 고려하여 설정.
  • idle-timeout: 유휴 상태의 연결이 풀에서 제거되기까지의 시간(밀리초)로 minimum-idle 이하로 내려가지 않으며, 연결이 유휴 상태로 유지될 수 있는 최대 시간
  • connection-timeout: 커넥션을 가져오기 위해 대기할 최대 시간(밀리초)로 이 시간 내에 사용 가능한 연결을 얻지 못하면 SQLException이 발생

maximum-pool-size 를 공식에 맞게 설정한 후 connection timeout 을 테스트를 통해 적절 한 시간을 설정하여 최적화 할 수 있다.

위의 상황에서는 다른 해결책이 존재한다.

정상적으로 동작하는 상황에서는 아래와 같은 쿼리 로그가 나타난다.

Hibernate: select next_val as id_val from domain_seq for update
Hibernate: update domain_seq set next_val= ? where next_val=?
Hibernate: insert into domain (name,id) values (?,?)

여기서 for update 쿼리는 조회한 행에 락을 걸어 현재 트랜잭션이 끝나기 전까지 다른 세션의 접근을 막아 PK가 중복 생성되는 것을 방지하게 된다.

이 과정에서 2개의 커넥션 풀이 사용되게 되는데, 이를 피하려면 GenerationType.IDENTITY를 사용하는 것이 좋다. GenerationType.IDENTITY를 설정하면 MySQL이 자동으로 ID 값을 증가시키므로, FOR UPDATE 락을 피할 수 있어 커넥션 풀이 과도하게 사용되는 문제를 완화할 수 있다.

 

참고

https://techblog.woowahan.com/2664/

 

HikariCP Dead lock에서 벗어나기 (이론편) | 우아한형제들 기술블로그

안녕하세요! 공통시스템개발팀에서 메세지 플랫폼 개발을 하고 있는 이재훈입니다. 메세지 플랫폼 운영 장애를 바탕으로 HikariCP에서 Dead lock이 발생할 수 있는 case와 Dead lock을 회피할 수 있는 max

techblog.woowahan.com

https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing#pool-locking

 

About Pool Sizing

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

github.com

 

'IT > Spring' 카테고리의 다른 글

chunk vs tasklet 차이  (1) 2024.11.18
Spring Batch Writer 성능 비교  (0) 2024.11.13
Spring Boot 3.2 변경점  (3) 2023.11.24
스프링의 트랜잭션  (0) 2023.10.12
Spring AOP 분석  (0) 2023.10.10

댓글