상황
필자는 데이터를 Kafka 시스템에 연계하는 프로젝트를 하고있었음
때문에 데이터가 kafka에 들어와야됨
서비스 운영은 22.08.01~22.08.31 까지 운영 중이였음
그러나 22.08.31 저녁 22시 이후로 데이터가 안들어오기 시작
08.30일 22:55~ 08.31일 17:00까지 데이터가 안들어옴
필자는 DBCP를 Hikari 를 사용중이다.
로그에서 Connection이 모두 idle 상태임을 확인했다.
여기서
idle
상태란 Connection이 연결은 되있지만 아무활동을 하지않고 있는유휴 상태
이다.
+그리고 추가적으로 Connection has passed maxlifetime
hikari 로그 가 확인되었다.
필자는 여기서 데이터를 파싱하는 모듈을 담당하였음.
간단한 로직을 설명하자면, DB에서 데이터를 select 해서
데이터를 가공한 뒤,
다시 DB에 데이터를 Insert 한다.
위의 로직은 하나의 스레드에서 동기적으로 작동한다.
Hikari는 Hikari의 maxlifetime를 초과한 커넥션들을 찾아서 커넥션을 닫아줘버린다. 그리고 다시 생성한다. 현재 maxlifetime은 따로 설정한값이 없어서 default값으로 적용되어있다.
여기서
maxlifetime
란 커넥션풀 에서의 커넥션들의 최대 유지 시간이다.
필자가 사용중인 DB의 wait_time 같은경우 60으로 설정되있었다.
여기서 DB 의
wait_time
또한 위와 같은 의미로 생각하면 된다.
Hikari maxlifetime이 DB의 wait_time 보다 크기 때문에 DB에서는 wait_time을 기다리다가 request가 안왔기때문에 커넥션을 종료했고 Hikari는 DB의 wait_time을 조금 초과해서 request를 보냈지만 이미 DB가 커넥션을 종료했기 때문에 이도저도 못하는 커넥션 누수상황으로 의심된다.
바로 여기다! 아래와 같이 공식문서에서는
히카리 maxlifetime 가이드 문서 내용
Hikari maxlifetime 값을 DB의 wait_time 값보다 짧게 가져가라. Hikari의 maxlifetime 디폴트는 30분이다.
We strongly recommend setting this value, and it should be several seconds shorter than any database or infrastructure imposed connection time limit.
A value of 0 indicates no maximum lifetime (infinite lifetime), subject of course to the idleTimeout setting.
Default: 1800000 (30 minutes)
DB의 wait_time 재설정 60(1분) -> 28800(8시간)
Hikari의 maxlifetime< DB의 wait_time으로 설정 Hikari의 maxlifetime default 값는 30분으로 적용되어있음
Hikari의 Valid 옵션을 적용하여 커넥션 생사여부 검증. 아래 자세히 정리
3-1.원래 connectionTestQuery를 적용하면 일정시간 마다 select 1 쿼리가 나가서 커넥션을 지속적으로 유지시켜주었지만 아래 HikariCP 개발자의 조언을 참고하여 적용하지 않았음.
HikariCp 메인 개발자의 커넥션 생존확인 방법 댓글
Let’s discuss.
Unless you’ve studied the internals of HikariCP you may not know it, but under load HikariCP mostly bypasses the connection validation check. If a connection has been used within the last 1000ms, HikariCP will bypass the validation check automatically in getConnection().
If connections are being used less frequently than that, it indicates one of two things:
The application is simply not that active in terms of transactions/sec, or The pool is sized too large for the application, such that connections frequently sit in the pool unused for more than 1000ms before being reused Having said that, with JDBC4 capable drivers, the cost of validation has largely dropped to sub-millisecond or single digit milliseconds. Wherever possible, validation queries should be avoided, allowing HikariCP to use Connection.isValid() instead.
Lastly, if there truly are use-cases where speed is paramount (over reliability) and yet transaction rates are very low, there are several alternative pools such as C3P0 available. We tend to think that either those use-cases are extremely rare, or can be effectively dealt with through driver and pool settings in their current form.`
HikariCP는 대부분 연결 유효성 체크를 건너 뛴다.
마지막 1000ms 이내에 연결이 사용 되었다면, HikariCP는 getConnection ()에서 유효성 검사를 자동으로 무시한다.
유효성 검사를 일정 시간마다 따박따박 하는게 성능 측면에서 비효율적이라 건너 뛰는 것으로 보인다.
SELECT 1과 같이 유효성 검사 쿼리를 직접 날리는 것보다 JDBC4를 지원한다면 Connection.isValid()가 호출되는게 성능 측면에서 더 효과적이다.
JDBC 구현체마다 다르지만 쿼리를 날리지 않고 Ping을 날리는 등으로 성능을 개선한다.
DB Connection Pool의 개수는 보통 스레드의 개수>=DB Connection Pool
이 이상적이다.
그러나 필자는 단일 스레드를 사용하고 있기 때문에 DB Connection Pool을 쓸데없이 낭비하고 있다.
이는 서버 재기동시 의미 없는 리소스 낭비가 될수 있기 때문에 1~2개로 개수를 수정토록 하자.
config.setMaximumPoolSize(31)
->config.setMaximumPoolSize(2)
단일 스레드이기때문에 커넥션 풀 개수를 수정해주었다.서버 가동시 불필요한 커넥션 풀의 생성은 필요없는 리소스의 낭비라고 생각한다.
valid 옵션 추가
를 통해서 쿼리가 아닌 ping 전달을 통한 커넥션 검증 처리를 해주었다.
tmi)
Connection Pool은 얼마가 적당할까?
최종적으로 CPU의 처리 효율과 디스크 처리 효율을 고려한 결과, ((core_count * 2) + effective_spindle_count) 공식을 통해 Connection의 개수를 추정할 수 있다고 알게 되었습니다.
현재 Agora 서비스의 MySQL 서버는 vCPU 2개와 SSD 1개를 가진 서버입니다. SSD를 사용하기 때문에 공식이 완벽하게 성립하지는 않지만 공식에 대입해보면 (2*2) + 1 이 되어 5개의 Connection Pool 크기를 도출해낼 수 있습니다. 그럼 Connection Pool의 개수를 5개로 지정한 후, 성능을 측정해볼까요?
히카리 옵션 정리
https://github.com/brettwooldridge/HikariCP
- 풀에서 커넥션을 얻어오기전까지 기다리는 최대 시간, 허용가능한 wait time을 초과시 SQLException이 발생한다.
- 설정가능한 최소 값 : 250
- valid 쿼리를 통해 커넥션이 유효한지 검사할 때 사용되는 timeout시간.(커넥션이 유효 검사 시 대기 시간을 지정)
- 이 값은 connectionTimeout보다 작아야 한다.
- 설정가능한 최소 값 : 250
- 풀에 유지시킬 수 있는 최대 커넥션 수.
- 풀의 커넥션 수가 옵션 값에 도달하게 되면 idle인 상태는 존재하지 않는다.
- 풀이 이 크기에 도달하고 유휴 커넥션이 없을 때 connectionTimeout이 지날 때까지 getConnection() 호출은 블록킹된다.
- pool에 일하지 않는 커넥션을 유지하는 시간.
- 이 옵션은 minimumIdle이 maximumPoolSize보다 작게 설정되어 있을 때만 적용된다.
- 이 옵션이 0이면 유휴 커넥션을 풀에서 제거하지 않는다.
- 설정가능한 최소 값 : 10000(10초)
- 유휴 커넥션의 최소 개수(아무런 일을 하지않아도 적어도 이 옵션 값의 size로 커넥션들을 유지해주는 설정이다.)
- default값이 유동적이기 때문에 최적의 성능과 응답성을 생각하면 구지 설정하지 않는게 좋을 것 같다.
- 커넥션의 최대 유지 시간. 이 시간이 지난 커넥션 중에서 사용중인 커넥션은 종료된 이후에 풀에서 제거한다.
- 갑자기 풀에서 많은 커넥션이 제거되는 것을 피하기 위해 negative attenuation을 적용해 점진적으로 제거한다.
- 이 값이 0이면 풀에서 제거하지 않지만 idleTimeout은 적용된다.
- 커넥션이 유효한지 검사할 때 사용할 쿼리를 지정한다.(보통 SELECT 1 로 설정 한다.)
- 드라이버가 JDBC4를 지원하면 이 프로퍼티를 설정하지 말자.(이 프로퍼티를 설정하지 않으면 JDBC4의 Conneciton.isValid()를 사용하여 유효성 검사 수행)
- JDBC4 드라이버를 지원하지않는 환경에서 이 값을 설정하지 않는다면 error레벨 로그 리턴.
- 커넥션이 누수 로그메시지가 나오기 전에 커넥션을 검사하여 pool에서 커넥션을 내보낼 수 있는 시간 설정. - 0으로 설정하면 누수 발견을 하지 않는다(leak detection 이용하지 않음). 허용하는 최소 값은 2000(2초)이다. - 설정가능한 최소 값 : 2000(2초)
DB time 옵션 정리
참고
https://pkgonan.github.io/2018/04/HikariCP-test-while-idle