[Hikari] Hikari DeadLock 해결하기

su_y2on·2022년 7월 2일
1
post-thumbnail

Hikari DeadLock 해결하기



성능테스트 중 마주친 Hikari Timeout..

최근 서버 성능테스트를 진행하던도중 이상한 일이 일어났습니다.
예약생성을 하는 요청을 시험삼아 10명의 User가 각각 1번씩 총 10번을 보내도록 테스트를 마치고 조금씩 늘려가기 시작했습니다.

그런데 User수를 100명으로 올리자마자 엄청나게 실패가 떴습니다.

왜지왜지왜지???? 서버를 확인하니 Hikaripool에서 timeout이 났습니다.. 현재 hikariPool의 maxsize는 10으로 설정되어있습니다. 그리고 timeout은 30초이상 기다려야지만 터집니다. 결국 10개의 풀을 가지고 100개의 요청이 처리하는데에 원활하지 않았다는 것인데 이해가 가지 않았습니다. (처리속도가 그렇게 늦을리가 없는데...)




좀 더 자세히 파악해보자

좀더 HikariPool의 상황을 자세히 보고싶었습니다. 그래서 아래와 같은 로그설정을 application.yaml에 추가해주었습니다.

logging:
  level:
    com.zaxxer.hikari.HikariConfig: DEBUG
    com.zaxxer.hikari: TRACE

로그를 확인해보니 10개의 connection중 10개가 모두 사용되고 있었습니다. 그리고 30초 이상 놓치를 않는.. 따라서 뒤에 기다리던 요청들이 터지면서 SQL Error를 뱉게 되는 것 같습니다.

로그가 찍힌 것으로 봐서는 처리속도가 늦는다기보다는 10개가 모두 물려서 놓치를 않는 상황에 가까웠습니다. 이게 수업시간에 이론적으로만 배우던 DeadLock인가 싶었습니다.




DeadLock

DeadLock은 연관관계가 생겨서 서로가 서로를 필요로하는 상태가 되어야합니다. 하지만 예약 생성시에 날라가는 쿼리들은 전혀 연관관계라고는 찾아볼 수 없었습니다..

그러다가 우형기술블로그에서 비슷한 문제로 트러블슈팅을 한 기록을 보고 단서를 얻을 수 있었습니다....(이래서 실제로 어떤 쿼리간 날라가지는지 파악하는게 중요한가봐요..)

결론만 말하자면 @GeneratedValue(strategy = GenerationType.AUTO)전략을 사용하면서 reservation이 테이블 전략으로 관리되고 있었기 때문입니다.



AUTO전략에 대한 오해

데이터베이스는 MYSQL을 사용하기때문에 AUTO면 당연히 IDENTITY(auto_increment)아닌가 싶었지만 이는 초반에 DB세팅시에 확인한바로 곧 포스팅을 올리겠습니다. 간단히 말하자면 앞서 말한 것처럼 알고계신분들이 많겠지만 hibernate버전이 올라가면서 AUTO전략이 택하는 방식이 달라졌습니다.
Spring Boot도 버전이 올라가며 자연스럽게 변경된 AUTO전략을 반영했습니다.

따라서 MYSQL은 TABLE전략을 사용한다고 보시면 됩니다. DB를 보시면 hibernate_sequence라는 테이블이 생성되어있을 것입니다.




이제 이게 어떻게 deadlock을 발생시켰는지를 살펴보면 reservation을 생성하기 위한 쿼리문들 사이에서 아래와 같은 쿼리를 발견할 수 있습니다. 즉 예약을 생성하기전에 id를 가져오기위해 hibernate_sequence table에서 값을 읽어오고 update를 합니다. 이는 subTransaction을 발생시킵니다.

Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?

즉 max hikariPool size를 3으로 설정했다고 가정하고보면 아래와 같은 상황이 발생할 수 있습니다. sub쿼리들을 각각 예약생성쿼리가 발생시키고 sub쿼리가 완료되지를 않으니 예약생성쿼리는 계속해서 connection을 차지하게 되는 것이죠. 이러다 30초가 흐르고 timeout이 되는 것 입니다.




해결책

해결책은 여러가지가 있습니다. 먼저 저희팀은 애초에 Mysql IDENTITY전략으로 맞추기로 했었지만 reservation파트를 개발하던 분이 소통문제를 이를 알지 못하고 AUTO전략을 사용해버렸기 때문에 IDENTITY전략으로 바꿔주며 subTransaction이 날라가지 않도록 해서 해결했습니다.

만약 IDENTITY전략을 사용할 수 없다면 maxsize를 늘려주거나 SequenceGenerator에 대한 Pooled-lotl optimizer 적용해서 해결할 수 있다고 합니다. 하지만 전자보다는 후자가 나은 것 같네요. maxsize를 계속늘려주다보면 효율성문제가 생기기때문입니다. 이부분은 앞서 소개했던 우형기술블로그에 자세히 설명되어있으니 참고해보시면 좋을 것 같네요~





방지

애초에 이런상황을 방지하기 위해서는 일단 실제 날라가는 쿼리들을 주의깊게 볼필요가 있습니다. 그리고 MaxSize를 1로 하고 테스트를 했을 때 문제가 생기면 의존성이 있다는 것이니 그렇게 미리 DeadLock을 예상해 볼 수도 있다고 합니다.



마치며..

IDENTITY전략을 사용한 것이 이런점까지 고려한 것은 아니어서 운좋게도 팀원의 실수로 Deadlock을 공부할 수 있었던 계기가 되었던 것 같습니다. 사실 Deadlock을 이론으로만 배워봐서 그렇구나 하고 넘기는 경우가 많았습니다. 그런데 이렇게 직접 직면하고 해결해보니 이해가 쏙쏙 되었습니다ㅎㅎ 또한 ConnectionPool의 동작방식도 다시한번 공부해볼 수 있는 기회가 되어 뿌듯하네요!!

0개의 댓글