제목: "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를 실행할 수 없게 된다.