SLI‐1 부하테스트 결과 및 보고서 - 100-hours-a-week/KTB4-7th-Cloud GitHub Wiki
SLI-1 매출 분석 조회를 대상으로 Smoke와 Load를 실행하고, k6 결과를 Nginx 로그와 Backend 자원 지표로 비교했다.
초기 Load 2,998건 중 43건이 10초 타임아웃으로 실패했다. 이후 경로 비교에서 Nginx 경유 요청만 실패해, 컨테이너에서 Backend로 연결하는 구간을 우선 조사 대상으로 정했다.
실행 조건과 자료
| 단계 | 요청 기록 구간, KST | 설정 | 실제 요청률 | 요청 수 | HTTP 실패 |
|---|---|---|---|---|---|
| Smoke | 2026-10-01 23:59:52–10-02 00:01:52 | 1 RPS, 2분 | 1.01 RPS | 121 | 1건, 0.83% |
| Load | 2026-10-02 00:04:08–00:14:08 | 5 RPS, 10분 | 5.00 RPS | 2,998 | 43건, 1.43% |
- 대상:
GET /v1/sales/analyses, Q1은 이번 달 조회, Q2는 지정 기간 조회. 두 요청을 번갈아 실행했다. - 경로: 로컬 k6 → 테스트용 Nginx, 8088 → 로컬 Backend, 8080. 운영 서버 검증 결과는 아니다.
- 자료: Smoke는
k6-local-monitor-xUqIpX, Load는k6-local-monitor-W9m8Rd의 요약 - 집계: 지표는 계정 생성과 업로드 준비 단계를 제외한 실제 k6 요청 구간으로 집계했다.
k6 측정 결과
정상 완료는 HTTP 200, COMPLETED, 요청 기간 일치로 판정했다.
p95와 p99에는 실패 요청도 포함된다.
| API, 단계 | 요청 수 N | 정상 완료 G / 비율 | 500ms 이내 정상 | 1초 이내 정상 | p95, ms | p99, ms |
|---|---|---|---|---|---|---|
| Q1, Smoke | 61 | 61 / 100% | 61 / 100% | 61 / 100% | 16.25 | 16.81 |
| Q2, Smoke | 60 | 59 / 98.33% | 59 / 98.33% | 59 / 98.33% | 19.23 | 4,112.80 |
| Q1, Load | 1,499 | 1,477 / 98.53% | 1,477 / 98.53% | 1,477 / 98.53% | 14.71 | 10,000.23 |
| Q2, Load | 1,499 | 1,478 / 98.60% | 1,478 / 98.60% | 1,478 / 98.60% | 14.88 | 10,000.23 |
Load의 정상 완료 2,955건은 모두 500ms 이내였다. 성공 HTTP 응답만 보면 p99는 16.34ms, 최대는 110.61ms다. 반면 실패 요청은 모두 약 10초를 기다렸다.
목표 대비 판정
| 단계, API | 정상 완료율 ≥99.9% | 500ms 이내 ≥95% | 1초 이내 ≥99% |
|---|---|---|---|
| Smoke, Q1 | 충족 | 충족 | 충족 |
| Smoke, Q2 | 미달 | 충족 | 미달 |
| Load, Q1 | 미달 | 충족 | 미달 |
| Load, Q2 | 미달 | 충족 | 미달 |
Smoke는 Q2 1건 실패로 스크립트의 100% 성공 조건도 통과하지 못했다.
Nginx 접근 로그 결과
| 단계 | HTTP 200 | HTTP 499 | request_time의 p95, p99 | upstream_response_time의 p95, p99 |
|---|---|---|---|---|
| Smoke | 120건 | 1건 | 13ms, 17ms | 14ms, 17ms |
| Load | 2,955건 | 43건 | 12ms, 10,000ms | 12ms, 10,000ms |
k6 실패는 모두 status 0, 오류 코드 1050, request timeout이었다. Nginx에서도 동일한 건수의 499가 기록됐고, Load의 499 요청은 9,998–10,001ms를 기다렸다. upstream_status는 모두 -였다. 따라서 이번 실패는 확인된 HTTP 500 응답이 아니라 클라이언트가 10초 timeout에 도달해 연결을 끝낸 현상이다.
추가 검증과 분석
추가 검증 조건
기존 타임아웃의 원인을 확인하기 위해 기존 측정 경로를 연결하여 비교했다. API와 정상 완료 기준은 위와 같으며, 이번 비교에서는 같은 Backend 프로세스, 계정, 매출 데이터를 재사용했다.
- 공통 부하: 5 RPS, 사전 VU 10명, 최대 30명, 요청 타임아웃 10초.
- 추가 측정: 'X-Diagnostic-ID'를 이용해서 k6, Nginx, Backend 로그를 연결했다. Nginx 연결시간과 응답 헤더 시간, Backend 요청 도착과 처리 종료 시각을 기록했다.
- 연결 상태: Nginx 컨테이너에서 Backend 방향 TCP 소켓 상태를 약 1초마다 확인했다.
경로별 비교 결과
| 호출 경로 | 시간 | 요청 수 | 정상 완료 | 타임아웃 | p95, ms | p99, ms | 미실행 요청 |
|---|---|---|---|---|---|---|---|
| Nginx 경유, 8088 | 10분 | 2,993 | 2,921 | 72 | 24.79 | 10,000.34 | 7 |
| Backend 직접, 8080 | 10분 | 3,000 | 3,000 | 0 | 8.99 | 12.89 | 0 |
| Backend 직접, 매 요청 새 TCP 연결 | 2분 | 601 | 601 | 0 | 8.64 | 11.17 | 0 |
분석 결과
가장 유력한 문제 지점은 Nginx 컨테이너에서 Mac의 Backend로 TCP 연결을 수립하는 구간이다.
| 확인된 근거 | 판단 |
|---|---|
| 경유 실패 72건 모두 Nginx 499, Backend 도착과 처리 종료 기록 없음 | 실패 요청은 Backend의 HTTP 처리 단계에 들어온 기록이 없다. 성공한 2,921건은 모두 기록됐다. |
실패 72건의 upstream_connect_time, upstream_header_time 모두 - |
연결 완료와 응답 헤더 수신 시간이 기록되기 전에 요청이 종료됐다. |
Backend 방향 소켓 38개에서 SYN_SENT 관측, 같은 소켓 최장 약 9.73초 유지 |
실제 TCP 연결 수립 대기가 관측됐다. 소켓 수집은 도중에 시작해 실패 72건 전체와 일대일 매칭한 자료는 아니다. |
추정 원인: 컨테이너와 호스트 사이의 연결이 일부 요청에서 성립하지 못해 대기했고, k6가 10초 제한에 도달해 종료한 것으로 보인다. Backend 직접 호출은 새 TCP 연결을 만드는 조건에서도 모두 정상 완료했다. 세부 원인은 아직 확정하지 않았다.
원인 후보
SYN_SENT는 TCP 연결 요청을 보낸 뒤 응답을 기다리는 상태다.
| 원인 후보 | 확인할 내용 |
|---|---|
| Docker Desktop의 컨테이너와 호스트 사이 통신 | 일부 연결의 전달이나 응답이 지연되는지 |
| VPN, 방화벽, 보안 프로그램 | Docker 프로세스의 연결이나 응답을 차단하는지 |
| Nginx의 Backend 주소 설정 | 실제 접속 IP와 포트, 여러 주소 중 일부에서만 실패하는지 |
| 새 연결 수용 한계 | Backend나 중간 네트워크 계층에서 연결 대기가 쌓이는지 |
Nginx 경유에서만 실패했고, Backend 직접 호출은 매 요청 새 TCP 연결을 만드는 조건에서도 성공했기 때문에 우선 Docker Desktop을 거치는 통신 경로를 확인한다.
다음 검증
- Mac에서 Nginx를 직접 실행하고 같은 Backend, 계정, 데이터와 5 RPS, 10분 조건으로 비교한다. 실패가 사라지는지 확인해 Docker 경로의 영향을 좁힌다.
- 실패 구간의 TCP 패킷을 수집해 연결 요청(SYN)이 Mac에 도착하는지, 연결 응답(SYN-ACK)이 돌아오는지 확인한다.
- 문제가 없을 시 다음 후보 검사한다.
아직 검증을 실행하지는 않았음.