ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • HikariCP, 일단 connection 맺어 본다
    Database 2022. 11. 16. 23:18
    반응형

    1) 서론

    이번 글에서는 HikariCP의 설정에 대해서 공부한 것을 공유드리려고 합니다.

    이미 설정을 해보시고 테스트해본 분께는 쉬운 내용이므로 읽지 않으셔도 좋습니다.


    2) 아이디어 출발점

    중요한 새로운 모바일 애플리케이션 출시를 앞두고 있었습니다.

    • 사용자들은 업데이트 후 재로그인이 필수인 상황
    • 대대적인 공지로 인해 오전 평소보다 많은 요청 발생 예상
    • 매우 제한된 IDC 레거시 인증 담당 서버의 담당자는 하필 주니어인 나...


    배포를 앞두고 걱정이 되어 괜히 이래저래 문제가 없을지 고민해봅니다.

    이미 계약된 IDC의 물리적인 하드웨어 스펙은 현재 개선할 수 없습니다. 그래서 애플리케이션과 DB의 설정을 확인해보는데요.

    • 혹시 N+1 쿼리가 발생할 수 있는지 확인
    • DB connection 부족하지 않은지 확인


    평소의 개선을 통해 N+1 쿼리가 발생하는 부분은 없어 보입니다. ORM을 사용하며 N+1 문제로 인해 장애를 경험했었던 적이 있어, 평소에도 N+1은 매우 경계하며 사용했습니다.

    두 번째인 Spring 서버 애플리케이션의 HikariCP 설정을 확인합니다.

    $ show global status like 'threads_connected';
    
    +-------------------+-------+
    | Variable_name     | Value |
    +-------------------+-------+
    | Threads_connected | 151   |
    +-------------------+-------+
    • 서버에서 현재 사용 중인 커넥션 150개 (현재 프로세스 1개 제외)
    spring:
      datasource:
        hikari:
          maximum-pool-size: 150
    • 스프링부트 maximum-pool-size 150 설정


    maximumPoolSize 150개 설정했는데, 이미 150개를 사용하고 있습니다.

    CPU나 메모리의 사용량에 아무런 이상이 없습니다. 왜 이렇게 connection이 증가되어 있는지 의문입니다.
    하지만 해당 부분을 잘 모르고, 왠지 모를 불안감에 증가시켜 봅니다.

    spring:
      datasource:
        hikari:
          maximum-pool-size: 200
    • maximumPoolSize 200으로 증가
    mysql> show global status like 'threads_connected';
    
    +-------------------+-------+
    | Variable_name     | Value |
    +-------------------+-------+
    | Threads_connected | 201   |
    +-------------------+-------+
    • 여전히 200개까지 사용 중입니다


    maximumPoolSize를 증가시켰지만 여전히 최대 개수만큼 사용 중입니다.

    무언가 이상합니다.

    저기서 나오는 connection이 정말로 다 사용 중인 것은 아닐 것 같다는 생각이 듭니다. 의심이 되어 현재 동작 중인 프로세스를 확인해봅니다.

    mysql> SHOW PROCESSLIST;
    +-----+-----------------+------------------+-----------------+---------+------+------------------------+------------------+
    | Id  | User            | Host             | db              | Command | Time | State                  | Info             |
    +-----+-----------------+------------------+-----------------+---------+------+------------------------+------------------+
    |  16 | root            | localhost:59900 | connection_test | Sleep   |  820 |                        | NULL             |
    |  17 | root            | localhost:59912 | connection_test | Sleep   |  821 |                        | NULL             |
    |  18 | root            | localhost:59920 | connection_test | Sleep   |  821 |                        | NULL             |
    |  19 | root            | localhost:59934 | connection_test | Sleep   |  820 |                        | NULL             |
    |  20 | root            | localhost:59936 | connection_test | Sleep   |  820 |                        | NULL             |
    |  21 | root            | localhost:59940 | connection_test | Sleep   |  820 |                        | NULL             |
    |  22 | root            | localhost:59952 | connection_test | Sleep   |  820 |                        | NULL             |
    |  23 | root            | localhost:59956 | connection_test | Sleep   |  820 |                        | NULL             |                      | NULL             |
    • 대부분의 connection들은 sleep 상태


    현재 생성되어 있는 connection들은 대부분 sleep 상태인 것을 확인할 수 있습니다. 즉 connection을 맺었지만, 실제로 무언가 동작을 하고 있는 상태는 아닙니다.

    왜 동작하지 않는 connection을 최대치로 유지하는 걸까요?


    3) '최대' 사이즈인데 '최소'와 동일하게 동작하네요?

    maximumPoolSize 단어만 봤을 때 막연히 아래와 같이 동작할 것이라 예상했습니다.

    • 평소에는 최소 사이즈 유지
    • 요청이 많이 들어왔을 때 최대 사이즈까지 증가


    하지만 실제로는 애플리케이션 빌드하며 최대 사이즈만큼 connection을 생성하여 유지합니다. 비록 사용하지 않는 idle 상태일지라도요.

    어떻게 동작하는 걸까요?

    설정 중 minimumIdle라는 것이 존재하는데요. connection pool에 최소한으로 유지할 connection 수를 정의합니다. 즉 현재 사용하지 않더라도 idle 상태로 계속 유지합니다.

    그렇다면 minimumIdle의 기본 값은 어떻게 될까요?

    minimumIdle의 기본 설정은 maximumPoolSize와 동일한 값으로 설정됩니다.

    • '최소' 유지 connection = '최대' connection
    • 즉 사용하지 않더라도 '최대' connection 유지

    3. 1) 최소 유지 커넥션 설정하기

    spring:
      datasource:
        hikari:
          maximum-pool-size: 150
          minimum-idle: 50
    • minimumIdle 50 설정
    mysql> show global status like 'threads_connected';
    +-------------------+-------+
    | Variable_name     | Value |
    +-------------------+-------+
    | Threads_connected | 51    |
    +-------------------+-------+
    • connection이 사용되지 않을 때 minimumIdle 만큼만 유지


    minimumIdle 설정 후 현재 DB connection은 50개가 유지되고 있습니다.

    HikariCP 공식 문서에 의하면 minimumIdle은 maximumPoolSize와 동일한 수 설정을 매우 추천한다고 합니다.

    connection 수가 많이 필요한 애플리케이션의 경우에는 대용량 요청이 자주 발생한다고 가정할 수 있습니다. 이러한 경우에는 불필요한 connection 생성/해제에 비용을 사용하는 것보다는 생성 후 계속 사용하는 것이 효율적이라고 생각합니다.

    또한 요청이 자주 발생하는 경우에는 idle 상태로 오랫동안 머무르는 connection이 많지 않을 것이기 때문입니다. 당연히 생성된 connection이 idle로 가기 전에 계속해서 사용할 확률이 높을 것 같습니다.

    이러한 이유로 인해서 최소와 최대 connection 수를 동일하게 유지한다면 성능상 이점을 얻을 수 있다고 생각합니다.


    4) 만약 '최대'까지 늘어났다면, 언제 '최소' 커넥션으로 줄어드는 거죠?

    Jmeter를 이용해서 아래와 같이 우선 요청 보냅니다.

    • TPS 500 설정
    • 10번 반복
    • minimumIdle 50
    • maximumPoolSize 150
    mysql> SHOW PROCESSLIST;
    +-----+-----------------+------------------+-----------------+---------+------+------------------------+------------------+
    | Id  | User            | Host             | db              | Command | Time | State                  | Info             |
    +-----+-----------------+------------------+-----------------+---------+------+------------------------+------------------+
    | 368 | root            | localhost:62432 | connection_test | Sleep   |  896 |                        | NULL             |
    | 530 | root            | localhost:61740 | connection_test | Sleep   |  117 |                        | NULL             |
    | 531 | root            | localhost:61756 | connection_test | Sleep   |  117 |                        | NULL             |
    | 532 | root            | localhost:61762 | connection_test | Sleep   |  117 |                        | NULL             |
    | 533 | root            | localhost:61776 | connection_test | Sleep   |  117 |                        | NULL             |                   | NULL             |
    mysql> show global status like 'threads_connected';
    
    +-------------------+-------+
    | Variable_name     | Value |
    +-------------------+-------+
    | Threads_connected | 151   |
    +-------------------+-------+
    • 다량의 요청으로 maxPoolSize만큼 connection 증가
    • 요청 종료 후 idle 상태인 connection 150개 유지


    모든 요청이 끝난 후 connection들은 idle 상태가 되었습니다. minimumIdle을 50으로 설정했지만, 여전히 150개의 connection을 유지하고 있습니다.

    왜 그럴까요?

    4. 1) 미사용 커넥션 종료 설정

    이때 중요한 설정은 idleTimeout 입니다. 기본 값은 10분인데요. 즉 사용하지 않는 idle 상태의 connection이라도 10분 동안은 계속해서 생성된 상태로 유지됩니다.

    connection 해제를 테스트하기 위해 최솟값인 10초 설정합니다.

    spring:
      datasource:
        hikari:
          maximum-pool-size: 150
          minimum-idle: 50
          idle-timeout: 10000
    • minimumIdle 50
    • idleTimeout 10초
    • 10초 후 connection close 로그 발생
    mysql> show global status like 'threads_connected';
    
    +-------------------+-------+
    | Variable_name     | Value |
    +-------------------+-------+
    | Threads_connected | 51    |
    +-------------------+-------+
    • 생성되어있는 connection 51개로 감소

    5) 결론

    그동안 무심코 사용했던 HikariCP 설정들이 잘 못되었고, 동작하는 방식을 이해 못 했다는 생각이 듭니다.

    특히 maxLifeTime, idleTimeout 같은 것은 이름만 보고 당연히 그 시간에 종료된다고 이해하고 있었습니다. 실제로 connection이 어떻게 동작하는지 테스트해보지 않고 막연히 그럴 것이다라고 이해했었습니다.

    간단한 설정만으로도 성능적으로 큰 영향을 미칠 수 있다는 점을 배웠고, DB에 대해서 조금 더 깊게 공부를 해야겠다고 생각하는 계기가 되었습니다.


    6) 참고 문서

    HikariCP 공식 문서

    반응형

    'Database' 카테고리의 다른 글

    No Fedex, Yes Index  (0) 2022.04.11
    외래키로 참조 중인 컬럼 수정하기  (0) 2021.07.04

    댓글

Designed by Tistory.