부하 테스트로 성능 개선하기
이 글은 우아한테크코스 백엔드 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의 경우, 커넥션을 얻기 위해 대기 중(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의 최대 활성 커넥션이 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%에 도달해 유의미한 성능 평가가 어렵다고 판단하였습니다.
톰캣 설정 값 조정
톰캣 설정 값을 기본 값으로 두었을 때의 테스트 성공률을 먼저 측정하였습니다.
[톰캣 설정 값 조정 근거]
- 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로 개선할 수 있었습니다.