DB Connection Pool과 HikariCP에 대해 알아보자

토깽이·2022년 5월 6일
0

Spring boot 실행하며 무심코 사용했던 HikariCP...
커넥션 풀이라는 것을 어렴풋이 알고는 있었지만 원리를 깊게 파고든 적은 없었다.(일단 빨리 개발하는게 더 중요했기 때문에...) 웹서핑 중 우연히 재미있고 멋진 글을 발견했는데 HikariCP에서 무시무시한 데드락이 발생할 수 있다는 것을 알게 되었다. 알아두면 언젠가 반드시 필요할 일이 있을 것 같아, 나름대로 이해한 내용을 정리해 보려고 한다:D

1. DB Connection pool이 뭔가요?

WAS와 DB 사이의 연결을 미리 생성해서 Pool에 보관한 후 재사용하여 데이터를 교환하는 방식입니다.

(WAS가 무엇인가요? https://codechasseur.tistory.com/25)

2. Connection pool은 왜 필요한가요?

WAS와 DB의 connection은 cost가 많이드는 작업입니다.

커넥션을 하기 위해 3-handshaking을 통해 소켓을 생성하고 3번 패킷을 주고 받는데, 쿼리 마다 이 작업을 반복하게된다면 네트워크 병목 현상을 유발할 수 있습니다.

그래서 이를 해결하기 위해, 미리 정해진 개수의 커넥션을 만들어놓고 Pool에 보관하고 관리하며 재사용하여 데이터를 교환합니다. 이러한 방식을 DB Connection pool 이라 하고 재사용을 한다는 점에서 반복적인 3-handshking을 방지하므로 빠른 통신이 가능해집니다.

3. Connection pool은 어떻게 동작하나요?

커넥션 획득을 위해 풀에 요청하고, 사용가능한 커넥션이 없으면 대기합니다.

WAS의 Thread가 Connection을 요청하면 Connection pool에서 어떠한 규칙에 따라 이용 가능한(Idle) connection을 찾아서 반환합니다. HikariCP의 경우, 이전에 사용한 적 있는 connection이 있다면 해당 connection을 반환합니다.

만일 사용 가능한 connection이 없다면, HandOffQueue라는 곳에서 대기하며 다른 Thread가 Connection을 반환하기를 Time out 시간까지 기다립니다.

그러니 만일 Connection pool의 크기가 작다면, HandOffQueue에서 대기하는 Thread가 많아지게되고 성능이 저하되며 시스템에 따라 어떤 경우에는 Dead lock이 발생할 수 있습니다. 이럴 때는 Connection pool을 키워주면 됩니다.

4. Connection pool 사이즈는 어느 정도로 해야할까요?

Connection pool을 늘릴수록 성능 및 Deadlock 문제는 피할 수 있겠지만 필요 이상의 메모리를 사용하게 되며, Disk I/O, Thread의 Context switching 등의 원인에 의해 증가에도 한계가 있습니다.

  • Disk I/O : 하드디스크의 데이터에 접근하기 위해서는 하드디스크 하나당 한 번의 I/O를 처리합니다. pool 크기를 아주 크게 늘려봤자 하드디스크 성능이 받쳐주지 못하면 blocking이 발생해 결국 한계가 있습니다.
  • Context switching : 멀티 스레드 환경에서 각 스레드는 parallel 하게 동작한다고 하지만 사실을 CPU의 속도가 매우 빨라서 그렇게 보일 뿐이고, 실제로는 한번에 하나의 Thread 작업만 처리할 수있습니다(concurrency). 따라서 Thread의 stack 영역 데이터를 로드 하는 등의 오버헤드로 인해 어느 시점부터는 성능적 증가를 기대하기 어렵습니다.

(단순하게 생각해봤을 때도 Connection pool을 너무 크게 잡으면, 불필요하게 잡아먹는 메모리도 그만큼 늘어나고, 비용이 발생하니 최대한 줄여야합니다.)

그러면 어느 정도가 적당할까요?

HikariCP에서는 Dead lock을 피하기 위한 최소 pool size를 정할 때, Thread의 갯수, simultaneous connection(동시에 필요한 커넥션)의 수를 이용한 공식을 소개합니다.

그러나 이 공식으로 얻은 수치는 절대적이지 않고 시스템에 따라 달라질 수 있기 때문에 (한 번의 작업에 여러번의 트렌젝션이 있는 경우 등) 테스팅을 시도하면서 경험적으로 선택해야 합니다.
더 자세한 내용은 좋은 글이 있어서 링크를 남기겠습니다.

4-1. Simultaneous connection (동시에 필요한 커넥션)

컬럼에 unique id를 부여할 때 무심코 사용하였던 @GenerationType.IDENTITY 혹은 @GeneratedValue(strategy = GenerationType.AUTO) 가 동작은 비슷하나 사용하는 커넥션 수가 다른 것이 인상깊어서 따로 모아보았습니다.

@GenerationType.IDENTITY : 한개의 커넥션이 필요합니다.

@GeneratedValue(strategy = GenerationType.AUTO) : 두 개의 커넥션이 필요합니다.

결론

  1. Connection pool은 서버와 DB의 connection을 재사용하여 연결에 드는 비용을 최소화하기 위한 방식입니다.
  2. Connection pool의 크기가 작으면 대기시간이 길어져 성능 저하가 발생하며, 크기가 너무 크면 메모리를 낭비하게 되며 Context switching 및 disk I/O 의 한계로 무한정 늘릴 수도 없습니다.
  3. Connection pool size를 정하기 위한 공식이 있지만, 성능 테스트를 통해 시스템에 맞는 적절한 사이즈를 찾아야합니다.

부족하지만 정리해보았고, 틀린 내용이 있다면 댓글로 알려주시길 바랍니다:D

0개의 댓글