Tomcat Thread Pool max size, DBCP maximumPoolSize를 조정하여 JMeter를 통해 테스트를 해보던 중 다음과 같은 예외가 발생하며 요청이 정상적으로 처리되지 못하는 문제가 발생했다.
org.springframework.dao.DataAccessResourceFailureException: unable to obtain isolated JDBC connection; nested exception is org.hibernate.exception.JDBCConnectionException: unable to obtain isolated JDBC connection
at
Caused by: java.sql.SQLTransientConnectionException: HikariCP - Connection is not available, request timed out after 30006ms.
해당 사항과 같은 문제를 찾아보면서 우아한 형제들의 기술 블로그에서 Thread Pool size가 DBCP size보다 크고 하나의 스레드에서 여러 커넥션을 사용하게 되면 발생할 수 있는 문제임을 알게 되었습니다.
server:
tomcat:
threads:
max: 30
min-spare: 20
accept-count: 300
port: 8080
hikari:
connectionTimeout: 30000
maximumPoolSize: 20
maxLifetime: 295000 # db wait_timeout 보다 짧게 유지
poolName: HikariCP
readOnly: false
connectionTestQuery: SELECT 1
Thread pool size를 DBCP size보다 크게 하는것이 당연히 맞는 방법이 생각했습니다. 그 이유는 다음과 같습니다.
하지만 EntityManager의 persist를 호출하여 테이블에 새로운 데이터를 insert하는 경우 Connection이 두개 생성 되는 경우가 있기 때문에 해당 가정이 성립하지 않았고 데드락이 발생한 것입니다.
@GeneratedValue(strategy = GenerationType.AUTO)와 Mysql을 같이 사용하는 경우 ID의 Type이 long 타입이고, hibernate.id_new_generator_mappings 값이true (default true)이기 때문에 ID 필드에 대한 Generator는 내부적으로 SequenceStyleGenerator를 사용하게 됩니다.
하지만 MySQL에서는 Oracle처럼 Sequence라는게 존재하지 않기 때문에 hibernate_sequence라는 테이블을 생성하고, 테이블에 단일 로우로 된 id값을 계속 update하며 sequence처럼 관리합니다.
select next_val as id_val from hibernate_sequence for update
따라서 30개의 스레드 풀이 insert 쿼리를 동시에 보내기 위해서는 적어도 31개의 DBCP가 필요하게 됩니다. 그렇지 않으면 데드락이 걸릴 가능성이 있습니다. 하지만 저의 경우 20개로 DBCP size를 두었기 때문에 데드락이 걸리면서 connection을 얻지 못하고 timeout이 발생한 것입니다.
GenerationType.Identity는 auto_increment를 사용하기 때문에 별도의 커넥션을 추가로 필요로 하지 않습니다.
우아한 형제들에서는 Id값을 다른 벤더에서도 사용하여 일정하게 유지시키기 위해 GenerationType.AUTO를 사용하지는 못했다고 합니다.
하지만, 제 프로젝트에서는 Id값이 변해도 상관이 없습니다. 별도의 UUID column이 존재하기 때문입니다. 따라서 이 해결법을 활용하기로 결정했습니다.
이 경우도 문제를 해결할 수 있지만 보통 application에 요청이 들어오면 insert보다는 select 등의 쿼리가 많고 아예 connection 을 사용하지 않는 요청도 있을 수 있습니다. 따라서 DBCP를 thread pool size보다 너무 크게 하면 idle connection이 늘어날 수 있고 반대로 약간 더 크게 하면 deadlock이 걸리지는 않지만 대기 시간은 길어질 것입니다. 효율성 측면에서 trade-off가 발생한다는 한계점이 있습니다.
https://velog.io/@xogml951/DBCP-Dead-Lock-Thread-Dump%EB%A1%9C-%ED%99%95%EC%9D%B8