Skip to content

feat: k6 부하테스트 스크립트 및 Grafana 대시보드 추가 - #93

Merged
docodocod merged 4 commits into
developfrom
feat/#87-grafana-custon-k6-test
May 27, 2026
Merged

feat: k6 부하테스트 스크립트 및 Grafana 대시보드 추가#93
docodocod merged 4 commits into
developfrom
feat/#87-grafana-custon-k6-test

Conversation

@docodocod

Copy link
Copy Markdown
Contributor

k6 시나리오 테스트: 매장 탐색, 예약 동시성, 전체 플로우, 서킷브레이커
k6 단일 API 테스트: 매장 목록, PostGIS 지리 쿼리, 잔여석 조회, 쿠폰 선착순 발급 k6 내구성 테스트: 10VU x 30분 Soak 테스트 (메모리 누수, 커넥션풀 고갈 탐지) run.ps1: 테스트 번호로 실행하는 래퍼 스크립트 (01~08, 10)
Grafana 백엔드 모니터링 대시보드 JSON (수동 import용)

📢 기능 설명

필요시 실행결과 스크린샷 첨부

연결된 issue

연결된 issue를 자동을 닫기 위해 아래 {이슈넘버}를 입력해주세요.

close #87

✅ 체크리스트

  • PR 제목 규칙 잘 지켰는가?
  • 추가/수정사항을 설명하였는가?
  • 이슈넘버를 적었는가?

k6 시나리오 테스트: 매장 탐색, 예약 동시성, 전체 플로우, 서킷브레이커
k6 단일 API 테스트: 매장 목록, PostGIS 지리 쿼리, 잔여석 조회, 쿠폰 선착순 발급
k6 내구성 테스트: 10VU x 30분 Soak 테스트 (메모리 누수, 커넥션풀 고갈 탐지)
run.ps1: 테스트 번호로 실행하는 래퍼 스크립트 (01~08, 10)
Grafana 백엔드 모니터링 대시보드 JSON (수동 import용)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Grafana dashboards and a comprehensive suite of k6 load testing scripts (covering store browsing, reservation concurrency, full user flow, circuit breaker, store list, nearby stores, remains, coupon issue, and soak tests) along with a PowerShell runner script. The reviewer feedback identifies several critical issues in the k6 scripts that need to be addressed: using Korean string values instead of English Enum constants for Category and District (which will cause 400 Bad Request errors), using UTC dates that can lead to test failures before 9 AM KST, specifying k6 timeouts as strings (e.g., '65s') instead of millisecond numbers, and potential k6 runner crashes when parsing non-JSON error responses without a try-catch block.

Comment thread k6/03-full-flow.js Outdated
Comment thread k6/05-store-list.js Outdated
Comment thread k6/01-store-browse.js Outdated

export default function () {
const storeId = STORE_IDS[Math.floor(Math.random() * STORE_IDS.length)];
const today = new Date().toISOString().split('T')[0];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

new Date().toISOString()는 UTC 기준 시간을 반환하므로, 한국 시간(KST, UTC+9) 기준으로 오전 9시 이전에 테스트를 실행하면 어제 날짜가 생성됩니다. 이로 인해 과거 날짜에 대한 잔여석 조회(GET /api/v1/remains)가 요청되어 API가 실패하거나 빈 결과를 반환할 수 있습니다. 한국 시간 기준으로 안전하게 오늘 날짜를 구하도록 수정하는 것이 좋습니다.

Suggested change
const today = new Date().toISOString().split('T')[0];
const today = new Date(Date.now() + 9 * 60 * 60 * 1000).toISOString().split('T')[0];

Comment on lines +63 to +65
} else if (res.status === 400 || res.status === 409) {
const body = res.json();
const code = body?.code || '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

고부하 상황에서 서버가 502, 503, 504 등 비정상 응답(HTML 에러 페이지 등)을 반환할 경우, res.json() 호출 시 예외가 발생하여 k6 VU(가상 사용자) 실행이 중단될 수 있습니다. 08-coupon-issue.js에서 적용하신 것처럼 안전하게 try-catch로 감싸서 예외를 방지하는 것이 좋습니다.

Suggested change
} else if (res.status === 400 || res.status === 409) {
const body = res.json();
const code = body?.code || '';
} else if (res.status === 400 || res.status === 409) {
const body = (() => { try { return res.json(); } catch { return {}; } })();
const code = body?.code || '';

Comment thread k6/03-full-flow.js Outdated
export default function () {
const start = Date.now();
const storeId = STORE_IDS[Math.floor(Math.random() * STORE_IDS.length)];
const today = new Date().toISOString().split('T')[0];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

new Date().toISOString()는 UTC 기준 시간을 반환하므로, 한국 시간(KST) 기준으로 오전 9시 이전에 테스트를 실행하면 어제 날짜가 생성됩니다. 이로 인해 과거 날짜에 대한 잔여석 조회가 요청되어 API가 실패할 수 있으므로, KST 기준의 오늘 날짜를 생성하도록 수정하는 것이 좋습니다.

Suggested change
const today = new Date().toISOString().split('T')[0];
const today = new Date(Date.now() + 9 * 60 * 60 * 1000).toISOString().split('T')[0];

Comment thread k6/04-circuit-breaker.js
Comment on lines +83 to +87
group('챗봇 메시지 전송', () => {
const res = http.post(`${BASE_URL}/api/v1/chat/messages`, payload, {
headers: HEADERS_AUTH,
timeout: '65s', // LLM 응답 최대 대기
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

k6의 http 요청 옵션에서 timeout은 밀리초(ms) 단위의 숫자(number) 타입을 기대합니다. '65s'와 같은 문자열을 지정하면 올바르게 인식되지 않고 기본값(60초)으로 동작하거나 예외가 발생할 수 있습니다. LLM 응답 대기를 위해 의도한 65초를 밀리초 단위 숫자로 명시해 주세요.

Suggested change
group('챗봇 메시지 전송', () => {
const res = http.post(`${BASE_URL}/api/v1/chat/messages`, payload, {
headers: HEADERS_AUTH,
timeout: '65s', // LLM 응답 최대 대기
});
group('챗봇 메시지 전송', () => {
const res = http.post(BASE_URL + '/api/v1/chat/messages', payload, {
headers: HEADERS_AUTH,
timeout: 65000, // LLM 응답 최대 대기 (65초)
});

Comment thread k6/06-store-nearby.js
Comment on lines +80 to +84
group(`근처 매장 조회 (PostGIS nearby) - ${loc.name}`, () => {
const res = http.get(
`${BASE_URL}/api/v1/stores/nearby?latitude=${lat}&longitude=${lng}&page=0&size=10`,
{ headers: HEADERS_JSON, timeout: '15s' },
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

k6의 http 요청 옵션에서 timeout은 밀리초(ms) 단위의 숫자(number) 타입을 기대합니다. '15s'와 같은 문자열은 올바르게 인식되지 않으므로, 의도한 15초를 밀리초 단위 숫자인 15000으로 수정해야 합니다.

Suggested change
group(`근처 매장 조회 (PostGIS nearby) - ${loc.name}`, () => {
const res = http.get(
`${BASE_URL}/api/v1/stores/nearby?latitude=${lat}&longitude=${lng}&page=0&size=10`,
{ headers: HEADERS_JSON, timeout: '15s' },
);
group('근처 매장 조회 (PostGIS nearby) - ' + loc.name, () => {
const res = http.get(
BASE_URL + '/api/v1/stores/nearby?latitude=' + lat + '&longitude=' + lng + '&page=0&size=10',
{ headers: HEADERS_JSON, timeout: 15000 },
);

Comment thread k6/06-store-nearby.js Outdated
`&minLng=${loc.minLng}&maxLng=${loc.maxLng}` +
`&centerLat=${lat}&centerLng=${lng}&limit=50`;

const res = http.get(url, { headers: HEADERS_JSON, timeout: '15s' });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

k6의 http 요청 옵션에서 timeout은 밀리초(ms) 단위의 숫자(number) 타입을 기대합니다. '15s'와 같은 문자열은 올바르게 인식되지 않으므로, 의도한 15초를 밀리초 단위 숫자인 15000으로 수정해야 합니다.

Suggested change
const res = http.get(url, { headers: HEADERS_JSON, timeout: '15s' });
const res = http.get(url, { headers: HEADERS_JSON, timeout: 15000 });

Comment thread k6/07-remains.js
Comment on lines +50 to +54
// 오늘 포함 향후 7일 중 랜덤 날짜 — 캐시 히트 방지 + 실제 사용 패턴 재현
const today = new Date();
const dayOffset = Math.floor(Math.random() * 7); // 0~6일 후
today.setDate(today.getDate() + dayOffset);
const date = today.toISOString().split('T')[0];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

new Date() 객체를 생성한 뒤 toISOString()을 호출하면 UTC 기준 시간이 사용됩니다. 한국 시간(KST) 기준으로 오전 9시 이전에 테스트를 실행하고 dayOffset0으로 결정되면, date가 어제 날짜로 설정되어 과거 날짜에 대한 잔여석 조회가 요청됩니다. 이는 API 실패나 잘못된 테스트 결과를 초래할 수 있으므로 KST 기준의 날짜를 계산하도록 수정하는 것이 좋습니다.

Suggested change
// 오늘 포함 향후 7일 중 랜덤 날짜 — 캐시 히트 방지 + 실제 사용 패턴 재현
const today = new Date();
const dayOffset = Math.floor(Math.random() * 7); // 0~6일 후
today.setDate(today.getDate() + dayOffset);
const date = today.toISOString().split('T')[0];
// 오늘 포함 향후 7일 중 랜덤 날짜 — 캐시 히트 방지 + 실제 사용 패턴 재현
const today = new Date(Date.now() + 9 * 60 * 60 * 1000);
const dayOffset = Math.floor(Math.random() * 7); // 0~6일 후
today.setDate(today.getDate() + dayOffset);
const date = today.toISOString().split('T')[0];

Comment thread k6/10-soak.js Outdated

export default function () {
const storeId = STORE_IDS[Math.floor(Math.random() * STORE_IDS.length)];
const today = new Date().toISOString().split('T')[0];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

new Date().toISOString()는 UTC 기준 시간을 반환하므로, 한국 시간(KST) 기준으로 오전 9시 이전에 테스트를 실행하면 어제 날짜가 생성됩니다. 이로 인해 과거 날짜에 대한 잔여석 조회가 요청되어 API가 실패할 수 있으므로, KST 기준의 오늘 날짜를 생성하도록 수정하는 것이 좋습니다.

Suggested change
const today = new Date().toISOString().split('T')[0];
const today = new Date(Date.now() + 9 * 60 * 60 * 1000).toISOString().split('T')[0];

docodocod and others added 3 commits May 27, 2026 15:06
Enum 매핑 수정

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Enum 매핑 수정

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
- timeout 문자열을 ms 숫자로 수정 (65s→65000, 15s→15000)
- UTC→KST 날짜 변환 적용 (01, 03, 07, 10번 스크립트)
- res.json() try-catch 안전 처리 (02번 - 502/503 HTML 응답 대비)
- category 필터 한글→영문 Enum 값으로 수정 (03번)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@docodocod
docodocod merged commit e32882c into develop May 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant