
제목: "HikariCP Dead lock에서 벗어나기 (이론편)"
작성자: techblog.woowahan(이재훈)
작성자 수정일: 2020년2월6일
링크: https://techblog.woowahan.com/2664
작성일: 2022년8월16일
Connection connection = null;
PreparedStatement preparedStatement = null
try {
connection = hikariDataSource.getConnection();
preparedStatement = connection.preparedStatement(sql);
preparedStatement.executeQuery();
} catch(Throwable e) {
throw new RuntimeException(e);
} finally {
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close(); // 여기서 connection pool에 반납됩니다.
}
}
이제 hikariDataSource.getConnection() 부터 connection.close() 까지 내부 동작을 살펴 보자.
HikariPool에서는 Connection 객체를 한번 wrapping한 PoolEntry라는 Type으로 내부적으로 Connection을 관리한다. 아래 글에서는 이해를 편하게 하도록 Connection 이라는 용어로 언급하겠다.
HikariCP에서 getConnection() 로직은 총 3단계 를 통해 Connection을 리턴하고 있다.
HikariCP에서는 내부적으로 ConcurrentBag 이라는 구조체를 이용하여 Connection 을 관리한다
HikariPool.getConnection()-> ConcurrentBag.borrow() 라는 메서드를 통해 사용 가능한(idle) Connection을 리턴하도록 되어있다.
전지적 개발자 시점으로 하나의 Thread가 Hikari Pool에 Connection을 요청하는 과정을 각본으로 보자


플로우 차트로 보면


HikariCP 에서 얻은 Connection은 (ProxyConnection) Connection.close()를 하게 되면 HikariPool에 반납이 된다.
HikariPool에서 얻은 Connection은 ProxyConnection 타입이다.)정상적으로 transaction이 commit 되거나, 에러로 인해 rollback이 호출 되면 connection.close()가 호출되어 Connection을 Pool에 반납g한다.
getConnection과 마찬가지로 connection.close() -> concurrentBag.requite()이 실행되며 Connection이 반납된다.

플로우 차트로 보면


Repository.save(entity) 라는 insert query를 실행하기 위해 Transaction을 시작한다.Root Transaction용 Connection을 하나 가져옵니다. (PoolStats : total=1, active=1, idle=0, waiting=0)Transaction을 시작하였고 Repository.save를 하기 위해 Connection이 하나 더 필요하다고 가정해보자.Thread-1은 Hikari Pool에 Connection을 요청한다.SQLTransientConnectionException으로 인해 Sub Transaction이 Rollback 된다.Sub Transaction의 Rollback으로 인해 Root Transaction이 rollbackOnly = true가 되며 Root Transaction이 롤백 된다. 이렇게 Thread 내에서 하나의 Task를 수행하는데 필요한 Connection 개수가 모자르게 되면 DeadLock 상태에 빠져 Insert Query를 실행할 수 없게 된다.