chore: k6 부하테스트 시나리오 고도화 및 모니터링 대시보드 개선 - #105
Conversation
- 01·03·05·06·07번: event_spike(워밍업 후 스파이크) + ramp_up(점진 증가) 두 시나리오 적용 - 04번: ramp_up 단독으로 서킷브레이커 OPEN 시점 탐색 시나리오로 개선 - docker-compose.prod.yml: Prometheus remote write 수신 활성화 및 포트 외부 노출 - run.ps1: 기본값 운영 서버 주소로 변경 - k6-dashboard.json: event_spike 시나리오명 동기화, 심화 지표 패널 4개 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request refactors the k6 load testing suite to compare 'event_spike' and 'ramp_up' scenarios, introduces a Grafana dashboard for real-time monitoring, and configures Prometheus remote write. Key feedback includes addressing a security vulnerability where Prometheus port 9090 is exposed publicly in production, correcting k6 timeout options to use millisecond numbers instead of string formats (e.g., '65s' and '15s'), URL-encoding Korean characters in district queries, and refactoring date calculations to prevent timezone-related inconsistencies.
| ports: | ||
| - "9090:9090" |
There was a problem hiding this comment.
운영 환경(docker-compose.prod.yml)에서 Prometheus 포트(9090)를 외부(0.0.0.0:9090)에 그대로 노출하는 것은 보안상 매우 위험합니다. 특히 --web.enable-lifecycle 옵션이 활성화되어 있어 누구나 HTTP POST 요청을 통해 설정을 재로드하거나 Prometheus 서버를 종료할 수 있으며, 메트릭 데이터가 외부에 무방비로 노출됩니다.
외부 k6 등에서 remote write를 수신해야 하는 경우, 포트를 직접 노출하기보다는 Nginx 등의 리버스 프록시를 통해 SSL 및 Basic Authentication(기본 인증)을 적용하여 /api/v1/write 경로만 안전하게 노출하는 것을 권장합니다. 만약 내부 통신만 필요하다면 ports 대신 expose를 사용하거나 127.0.0.1:9090:9090과 같이 로컬 루프백에만 바인딩해야 합니다.
| const res = http.post(`${BASE_URL}/api/v1/chat/messages`, payload, { | ||
| headers: HEADERS_AUTH, | ||
| timeout: 65000, // LLM 응답 최대 대기 (65초) | ||
| timeout: '65s', |
| `¢erLat=${lat}¢erLng=${lng}&limit=50`; | ||
|
|
||
| const res = http.get(url, { headers: HEADERS_JSON, timeout: 15000 }); | ||
| const res = http.get(url, { headers: HEADERS_JSON, timeout: '15s' }); |
There was a problem hiding this comment.
| if (useFilter) { | ||
| // 필터 조합을 랜덤하게 — 카테고리만, 지역만, 둘 다 섞어서 테스트 | ||
| if (Math.random() > 0.5) url += `&category=${randomCategory}`; | ||
| if (Math.random() > 0.5) url += `&district=${randomDistrict}`; |
There was a problem hiding this comment.
randomDistrict 변수에는 '강남구', '마포구' 등 한글 문자열이 포함되어 있습니다. URL에 한글과 같은 비ASCII 문자를 인코딩 없이 직접 추가하면, k6가 요청을 보낼 때 URI가 올바르지 않거나 서버에서 파라미터를 정상적으로 디코딩하지 못해 에러가 발생할 수 있습니다. 따라서 encodeURIComponent()를 사용하여 안전하게 인코딩하는 것이 좋습니다.
| if (Math.random() > 0.5) url += `&district=${randomDistrict}`; | |
| if (Math.random() > 0.5) url += '&district=' + encodeURIComponent(randomDistrict); |
| const today = new Date(Date.now() + 9 * 60 * 60 * 1000); | ||
| const dayOffset = Math.floor(Math.random() * 7); // 0~6일 후 | ||
| const dayOffset = Math.floor(Math.random() * 7); | ||
| today.setDate(today.getDate() + dayOffset); | ||
| const date = today.toISOString().split('T')[0]; |
There was a problem hiding this comment.
new Date()로 생성된 객체에서 getDate()와 setDate()를 호출하면 실행 환경(OS/로컬 PC)의 타임존에 영향을 받습니다. 반면 toISOString()은 항상 UTC 기준으로 변환하므로, 실행 환경의 타임존에 따라 날짜 계산이 비일관적으로 동작할 수 있습니다.
타임존에 무관하게 일관된 날짜 계산을 하려면, 모든 계산을 밀리초(ms) 단위로 처리한 후 최종적으로 toISOString()을 호출하는 방식이 훨씬 안전하고 직관적입니다.
| const today = new Date(Date.now() + 9 * 60 * 60 * 1000); | |
| const dayOffset = Math.floor(Math.random() * 7); // 0~6일 후 | |
| const dayOffset = Math.floor(Math.random() * 7); | |
| today.setDate(today.getDate() + dayOffset); | |
| const date = today.toISOString().split('T')[0]; | |
| const todayMs = Date.now() + 9 * 60 * 60 * 1000; | |
| const dayOffset = Math.floor(Math.random() * 7); | |
| const date = new Date(todayMs + dayOffset * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; |
- 05: district 파라미터 한글 encodeURIComponent 적용 - 07: setDate/getDate 타임존 혼용 → ms 연산으로 통일 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📢 기능 설명
필요시 실행결과 스크린샷 첨부
연결된 issue
연결된 issue를 자동을 닫기 위해 아래 {이슈넘버}를 입력해주세요.
close #{이슈넘버}
✅ 체크리스트