이 글은 우아한테크코스 백엔드 6기 냥인, 명오에 의해 작성되었습니다.

대규모 트래픽을 처리하기 위해서는 웹 애플리케이션의 성능을 최적화하는 것이 중요합니다.

서비스가 성장하는 상황을 연습하기 위해, 부하 테스트를 진행하며 톰캣과 HikariCP 설정을 조정한 경험을 공유합니다.

부하 테스트 도구 선정

크루루는 부하 테스트 도구로 k6를 선택했습니다. 저희가 비교한 도구는 제일 대중적으로 사용되는 3가지이고, 아래는 비교한 내용입니다.

비교 Jmeter K6 Locust
스크립트 언어 XML JS Python
성능 순위 3 1 2
결과 확인 실시간 모니터링 제약 그라파나로 확인 가능 웹으로 실시간 확인 가능

현재 이미 그라파나가 구축되어 있고, 다른 언어에 비해 비교적 JS가 익숙했기 때문에 성능이 제일 좋은 K6를 사용했습니다.

[성능 비교에 사용된 지표]

참고 자료: https://grafana.com/blog/2020/03/03/open-source-load-testing-tool-review/#testing-the-testing-tools


시스템 구성과 기본 설정

부하 테스트는 실제 운영 서버와 유사한 테스트 서버에서 진행하였습니다.

테스트 서버는 운영 서버의 인스턴스와 똑같은 스펙(t4g.small, 2코어 CPU, 30GiB RAM)의 인스턴스를 생성하여 사용했습니다.

DB는 운영 서버와 다르게 RDS가 아닌 t4g.micro 인스턴스를 하나 생성하여 사용하였습니다.

톰캣과 HikariCP 설정 모두 기본 값으로 시작했으며, 구체적인 설정 값은 아래와 같습니다.

  • Write DB HikariCP Maximum Pool Size = 10
  • Read DB HikariCP Maximum Pool Size = 10
  • Tomcat Max Thread Size = 200
  • Tomcat Max Connections = 8192
  • Tomcat Accept Count = 100

부하 테스트 시나리오

총 9가지 동작에 대한 시나리오를 작성했고, 동아리당 시나리오 수치는 팀원 초코칩이 활동하고 있는 동아리의 실제 수치를 참고했습니다.

시나리오 상세 수치
항목(동아리당 관리자) 비고
총 관리자수 10명 관리자 1인당 지원자** **48명 평가
서비스 사용 시간 2시간
총 지원자수 120명 지원자 1인당 최소 관리자 4명의 평가 필요
면접 전형 인원 40명 (서류 단계 불합격자수: 80명)
최종 합격자수 20명
항목(동아리당 지원자) 비고
총 지원자 수 120명 ~ 5000명 지원자가 늘어나면 시간당 요청이 늘어날 것으로 예상했지만, 관리자의 시간당 요청은 증가하지 않을 것으로 예상
마감 1시간 전 지원자 수 24명 ~ 1000명
항목
심사 기간이 겹치는 동아리 수(관리자 수에 곱할 값) 40개
모집 기간이 겹치는 동아리 수(지원자 수에 곱할 값) 10개

| 페이지(사용자 요청) | API | 2시간 동안 1인당 예상 호출 횟수 | 2시간 동안

총 예상 호출 횟수 TPS
로그인 - POST /login 1 10 0.0014
(0.00138889)
지원자 상세 조회 - GET process?dashboardId=123
  • GET evaluation?processId=12&applicantId=123
  • GET applicants/123/detail
  • GET applicants/123 | 768 | 7680 | 1.07 (1.06666667) | | 평가 등록 | - POST evalutaion
  • GET evaluations?processId=12&applicantId=123
  • GET process?dashboardId=123 | 144 | 1440 | 0.2 | | 불합격(단일) | - PATCH applicants/123/reject
  • GET process?dashboardId=123 | 16 | 160 | 0.022 (0.02222222) | | 프로세스 이동 | - PUT applicants/move-process/15
  • GET processes?dashboardId=123
  • GET applicants/1123 | 12 | 120 | 0.017 (0.01666667) | | 대시보드 조회 | - GET processes?dashboardId=123
  • GET dashboards?clubId=4 | 24 | 240 | 0.033 (0.03333333) | | 이메일 전송 | - POST emails/send | 0.3 | 3 (1차 불합격, 1차 합격, 2차 합격) | 0.0004 (0.00041667) | | TOTAL | | 965.3 | 9653 | 1.34 (1.34069444) |

| 페이지(사용자 요청) | API | 1시간 동안 최소 호출 횟수 | MIN TPS | 1시간 동안 최대

호출 횟수 MAX TPS
공고 조회 - GET applyform/1 120 0.033
(0.03333333) 5000 1.39
(1.38888889)
지원서 제출 - POST applyform/1/submit 24
0.007
(0.00666667) 1000 0.28
(0.27777778)
TOTAL 144 0.04 6000 1.67
\ 관리자 TPS 지원자 TPS TOTAL TPS
MIN 시나리오 1.34 * 40 = 53.6 0.04 * 10 = 0.4 54.0
MAX 시나리오 1.36 * 40 = 53.6 1.67 * 10 = 16.7 70.3

따라서, 목표 TPS를 54.0~70.3으로 설정했습니다.


테스트 설계

성능을 측정하는 데 있어, 테스트 성공률(=타임아웃 내 요청이 성공한 비율)을 핵심 지표로 삼았습니다. TPS는 최소로 설정한 상태로 측정하였습니다.

요청별 타임아웃 설정

지정된 시간 내에 응답이 없으면 바로 실패 처리하여 다른 요청에 영향을 주지 않도록 요청별 타임아웃을 설정하였습니다.

  • **모든 일반 요청에 대한 타임아웃을 500ms로 설정하였습니다. **

    사용자가 체감하기에 500ms를 초과하면 ‘느리다’고 인식할 가능성이 높다는 점을 고려하였습니다.

  • 이메일 전송은 타임아웃을 1초로 설정하였습니다.

    이메일 전송은 상대적으로 응답 시간이 긴 작업이기 때문에 비교적 여유 있는 타임아웃을 설정하였습니다.

초기 테스트 결과

Write DB, Read DB 모두 HikariCP 설정 값을 기본 값으로 두었을 때의 테스트 결과입니다.

       ✓ applyform loaded
     ✗ process moved
      ↳  92% — ✓ 13 / ✗ 1
     ✗ evaluation submitted
      ↳  99% — ✓ 175 / ✗ 1
     ✗ form submitted
      ↳  80% — ✓ 4 / ✗ 1
     ✗ process loaded
      ↳  97% — ✓ 869 / ✗ 18
     ✓ evaluation loaded
     ✓ applicant detail loaded
     ✗ dashboard loaded
      ↳  80% — ✓ 33 / ✗ 8
     ✓ applicant basic loaded
     ✗ login success
      ↳  75% — ✓ 3 / ✗ 1
     ✓ email sent

     checks.........................: 99.09% ✓ 3282      ✗ 30   
     ...
  

HikariCP 설정 값 조정

Read DB Connections

Write DB Connections

Read DB의 경우, 커넥션을 얻기 위해 대기 중(pending)인 요청이 발생하는 현상을 보고, Read DB의 커넥션 풀의 최대 크기를 10에서 15로 늘려 보았습니다.

그리고 Write DB의 경우 현재 사용 중(Active)인 커넥션 수에 비해 사용되지 않고 풀에서 대기 중(Idle)인 커넥션 수가 많다고 판단하여, Write DB의 커넥션 풀의 최대 크기를 5로 줄여 보았습니다.

       applyform loaded
     ✗ process moved
      ↳  92% — ✓ 13 / ✗ 1
     ✗ evaluation submitted
      ↳  99% — ✓ 175 / ✗ 1
     ✗ process loaded
      ↳  98% — ✓ 878 / ✗ 9
     ✗ form submitted
      ↳  80% — ✓ 4 / ✗ 1
     ✓ evaluation loaded
     ✓ applicant detail loaded
     ✗ dashboard loaded
      ↳  78% — ✓ 32 / ✗ 9
     ✓ applicant basic loaded
     ✗ login success
      ↳  50% — ✓ 2 / ✗ 2
     ✓ email sent

     checks.........................: 99.30% ✓ 3289      ✗ 23   
     ...
  

Read DB Connections

Write DB Connections

Read DB의 최대 활성 커넥션이 7개인 점을 감안했을 때, 커넥션 풀 크기를 10으로 줄여도 여유가 보장된다고 판단하였습니다.

이에 따라 커넥션 풀 최대 사이즈를 좀 더 타이트하게 잡아 10으로 줄이고, Write DB의 커넥션 풀 최대 크기를 5로 설정한 결과 중 일부입니다.

  `    ✓ evaluation submitted
     ✓ applyform loaded
     ✓ form submitted
     ✓ process moved
     ✓ evaluation loaded
     ✗ process loaded
      ↳  99% — ✓ 882 / ✗ 4
     ✓ applicant basic loaded
     ✓ applicant detail loaded
     ✓ dashboard loaded
     ✗ login success
      ↳  75% — ✓ 3 / ✗ 1
     ✓ email sent

     checks.........................: 99.84% ✓ 3305      ✗ 5    
     ...
  

초기 상태와 비교했을 때, 테스트 성공률이 향상된 것을 확인할 수 있습니다.

이후로는 Read DB의 커넥션 풀 최대 크기와 Write DB의 커넥션 풀 최대 크기를 하나씩 내려 보며 값을 측정하였습니다.

| Write DB HikariCP Maximum Pool Size | Read DB HikariCP

Maximum Pool Size 테스트 성공률 (%, 요청 수 최소치 기준)
5 9 99.32%
4 10 99.35%
4 9 99.27%

HikariCP 최종 설정 값

Read DB의 커넥션 풀 최대 크기 = 10
Write DB의 커넥션 풀 최대 크기 = 5

해당 설정값에서 가장 높은 테스트 성공률을 보인 것을 확인했습니다.

테스트의 신뢰성을 높이기 위해 동일한 설정으로 한 번 더 테스트를 진행했으며, 아래와 같이 여전히 가장 높은 성공률을 유지하는 결과를 얻었습니다.

       ✓ applyform loaded
     ✗ form submitted
      ↳  80% — ✓ 4 / ✗ 1
     ✗ process moved
      ↳  92% — ✓ 13 / ✗ 1
     ✗ evaluation submitted
      ↳  99% — ✓ 175 / ✗ 1
     ✗ process loaded
      ↳  99% — ✓ 867 / ✗ 4
     ✓ evaluation loaded
     ✓ applicant detail loaded
     ✗ dashboard loaded
      ↳  92% — ✓ 38 / ✗ 3
     ✓ applicant basic loaded
     ✓ login success
     ✓ email sent

     checks.........................: 99.69% ✓ 3238 
			...
  

이를 통해 해당 설정이 크루루 서비스에 가장 적합한 값임을 확정할 수 있었습니다.


톰캣 설정 값 조정

참고 사항

  • 위에서 결정된 HikariCP 설정 값을 적용한 후 테스트를 진행하였습니다.
  • TPS를 최대치로 설정한 뒤 테스트를 수행하였습니다.
    • TPS를 최소치로 설정한 경우, 테스트 성공률이 지속적으로 100%에 도달해 유의미한 성능 평가가 어렵다고 판단하였습니다.

톰캣 설정 값 조정

톰캣 설정 값을 기본 값으로 두었을 때의 테스트 성공률을 먼저 측정하였습니다.

Tomcat Thread

현재 활성화 된 Tomcat Connections

[톰캣 설정 값 조정 근거]

  • maxThread
    • 최대 스레드 수가 실제 사용 중인 스레드 수에 비해 지나치게 높게 설정되어 있습니다.
    • HikariCP의 커넥션 풀 크기와 균형을 맞춰 조정하였습니다.
    • Read DB와 Write DB의 최대 커넥션 수를 합산하여, 이를 기준으로 maxThreads를 설정하였습니다.
  • maxConnections
    • 톰캣 서버의 동시 활성화 커넥션 수가 최대 10개임을 확인했습니다.
    • 요청이 몰리는 상황에서도 여유를 두기 위해, maxConnections 값을 130~150 사이로 조정하였습니다.
  • acceptCount
    • maxConnections가 충분히 큰 경우, 대부분의 요청이 대기 없이 처리되므로, acceptCount를 줄여도 성능에 영향을 미치지 않는다고 판단했습니다.
    • 자원 낭비 방지와 불필요한 대기 시간 최소화를 위해 기본값 100에서 절반씩 줄여가며 테스트를 진행했습니다.

위 근거를 바탕으로 톰캣 설정 값들을 조정하면서 테스트를 수행한 결과는 다음과 같습니다.

maxThread maxConnections acceptCount 테스트 성공률(%, 요청 수 최대치 기준)
15 150 50 99.51
15 130 50 99.53
15 130 25 99.57
15 130 0 99.55

타임아웃 기준 엄하게 변경

기존 테스트로는 유의미한 변화를 확인할 수 없다고 판단하여 타임아웃 시간 기준을 더 엄격하게 잡았습니다. 부하 테스트 서버에 같은 요청을 3회 보내본 뒤, 그 최댓값을 10ms 또는 50ms 단위로 반올림하여 설정하였습니다.

전에 진행한 테스트와 같은 톰캣 설정값으로 테스트를 수행한 결과입니다.

maxThread maxConnections acceptCount 테스트 성공률(%, 요청 수 최대치 기준)
15 150 50 76.45
15 130 50 76.45
15 130 25 75.62
15 130 0 74.48

이를 조정 전 수치와 비교해보기 위해, 전체 조정 전과 톰캣 조정 전 설정값으로 테스트를 수행했습니다.

| Write DB HikariCP Maximum Pool Size | Read DB HikariCP Maximum Pool Size | tomcat

settings 테스트 성공률 (%, 요청 수 최대치 기준)
10 10 default 74.00
5 10 default 77.24

톰캣 설정 값을 변경하기 전 상태가 성공률이 제일 높아, 톰캣 설정 값을 다르게 변경해보며 재시도하였습니다.

maxThread maxConnections acceptCount 테스트 성공률(%, 요청 수 최대치 기준)
30 8192 100 74.99
300 8192 100 72.92
30 256 100 75.28

여전히 톰캣 기본 설정 값이 가장 높은 성공률을 보이고 있습니다.

타임아웃 제거 후 수치 비교

이번에는 타임아웃을 제거하고 다시 테스트하여 다른 설정과 톰캣 기본 설정의 요청 응답 시간 p95 값을 비교해 보았습니다.

tomcat settings HikariCP settings req_duration(ms)
15/150/50 5/10 308.47
15/130/50 5/10 276.32
15/130/25 5/10 271.86
15/130/0 5/10 261.08
default 5/10 263.42
default default 292.05

시간이 오래 걸리는 API의 req_duration 데이터도 비교하였습니다. API 호출 빈도를 고려해 자주 발생할 것으로 예상되는 순서대로 왼쪽부터 정렬하였습니다.

tomcat settings GET /processes (ms) GET /dashboards (ms) POST /login (ms) POST /email (ms)
15/150/50 406 558 481 453
15/130/50 386 530 460 488
15/130/25 379 488 506 472
15/130/0 377 498 530 494
default 377 510 453 492

저희는 기본 설정이 크루루의 전체적인 서비스 운영에 가장 적절하다고 판단했습니다.

가장 자주 발생할 거라 예상되는 GET /processes API에서 가장 빠른 응답 시간을 기록하며, 다른 API의 응답 시간도 다른 설정들과 비교하여 크게 뒤떨어지지 않았습니다. 모든 요청에 대한 평균 응답 시간도 제일 빠른 tomcat 15/130/0에 비해 2.34ms정도만 뒤떨어지고 있기 때문에 다른 요청에 대해서도 충분히 좋은 성능을 보여준다고 판단했습니다.


결론

HikariCP와 톰캣의 최종 설정 값을 다시 요약해 보자면 다음과 같습니다.

💡 HikariCP 설정 값

  • Read DB HikariCP Size = 10
  • Write DB HikariCP Size = 5

톰캣 설정 값(기본 값)

  • maxThread = 200
  • maxConnections = 8192
  • acceptCount = 100
지표 \ HikariCP 설정 값 조정
테스트 성공률(%) 74.48 77.24
응답 시간(ms) 292.05 263.42

이번 부하 테스트 및 HikariCP와 톰캣의 설정 값 조정을 통해 최대 목표 TPS에서 정상 응답률을 **74.48 → 77.24%**까지 끌어올릴 수 있었고, 응답 시간을 95p 기준 292.05 → 263.42ms로 개선할 수 있었습니다.