티스토리 뷰
부하테스트
시스템, API 또는 애플리케이션이 동시 사용자 수나 요청량 증가에 따라 성능이 어떻게 변화하는지를 측정하는 중요한 과정입니다. 이를 통해 특정 부하 상황에서 시스템의 응답 속도를 확인하고, 초당 처리 가능한 요청 수를 평가하며, 부하 증가 시 시스템이 안정적으로 작동하는지 확인할 수 있습니다. 또한 성능 저하의 원인을 유발하는 코드, DB 설정, 또는 서버 환경을 파악하여 병목현상을 제거하고 성능을 최적화하는 데 활용됩니다. 이러한 테스트의 결과는 시스템 안정성과 성능 개선의 중요한 기준점을 제공합니다.
K6
JavaScript 기반의 오픈소스 부하 테스트 도구로, API, 웹 서비스, 또는 애플리케이션의 성능 테스트를 목적으로 사용됩니다. 이도구는 Go 언어로 작성되어 경량화되고 빠른 성능을 제공하며, 테스트 시나리오를 JavaScript로 작성할 수 있어 높은 유연성을 자랑합니다. K6는 점진적 부하 증가, 스파이크 테스트 등 다양한 부하 시나리오를 지원하며, 클라우드 실행 및 InfluxDB/Grafana와의 통합을 성능 테스트와 병목 현상 분석에 효과적입니다.
Grafana
성능 및 가동 시간 모니터링에 널리 사용되는 도구로, 다양한 플러그인과 대시보드를 활용하여 데이터를 효과적으로 시각화할 수 있습니다. Grafana는 InfluxDB, Prometheus 등과 유연하게 통합되어 데이터 분석과 표시 기능을 강화합니다.
InfluxDB
시계열 데이터 처리에 특화된 데이터베이스로, 주로 요청 시간, 처리량과 같은 성능 모니터링 데이터를 저장하는데 최적화되어 있습니다. K6와 기본적으로 통합 가능하며, Grafana와 함께 사용하여 성능 테스트 데이터를 시각화하는데 활용됩니다.
K6 + InfluxDB + Grafana
부하 테스트 환경에서 매우 효과적인 방식입니다. K6는 테스트 데이터를 InfluxDB에 저장하고, Grafana는 이를 실시간으로 시각화하여 테스트 진행 상황을 한눈에 확인할 수 있도록 도와줍니다.
K6 LifeCycle
K6 라이프사이클을 활용하면, setup() 함수에서 테스트에 필요한 대기열 토큰 등을 미리 생성한 후, default() 함수에서 이 토큰을 사용하여 설정된 시나리오에 따라 API를 동적으로 호출할 수 있습니다. 이를 통해 효율적이고 체계적으로 부하 테스트가 가능합니다.
테스트 시나리오 선정
Actor → 토큰 조회 → 콘서트 날짜 조회 → 콘서트 좌석 조회 → 좌석 예약 → 결제
스크립트 설정
- ramping-vus: 사용자가 점진적으로 증가/감소하는 시나리오.
- stages:
- 1s 동안 사용자 수를 0에서 100으로 증가
- 3s 동안 사용자 수를 100으로 유지
- 1s 동안 사용자 수를 0으로 감소
- 목적: 시스템이 사용자 부하 증가/감소 상황에서 어떻게 동작하는지 평가.
export const options = {
scenarios: {
load_test: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '1s', target: 100 },
{ duration: '3s', target: 100 },
{ duration: '1s', target: 0 },
],
},
},
};
분석 결과
- 총 HTTP 요청 수: 496
- 요청 처리 속도: 27.82 요청/초 (초당 약 28개 요청)

응답 시간
평균 응답시간 | 최소 응답 시간 | 최대 응답 시간 | 중앙값 | 상위 90% 응답 시간(p90) | 상위 95% 응답 시간(p95) |
2.08s | 4.53ms | 8.59s | 1.38s | 5.25s | 6.2s |

결과
시스템의 성능은 평균 응답 시간이 다소 높으며, 상위 90% 이상의 요청에서 응답 시간이 급격히 길어지는 경향을 보인다. 특히 95%에 이르러서는 응답 시간이 더욱 길어져 전체적인 최적화가 필요한 상황으로, 병목 지점이 존재하거나 성능 저하를 유발하는 코드가 있을 가능성이 높다.
안정성 측면에서는 1.61%의 실패율이 낮아 보일 수 있지만, 이러한 비율이 증가할 경우 심각한 문제가 될 수 있어 안정성을 보강해야 한다. 긴 응답 시간과 실패율은 사용자 경험을 저하시키는 주요 원인으로 작용하므로, 시스템 성능과 안정성을 개선하여 사용자 만족도를 높이는 것이 필요해 보인다.
스크립트 전체 코드
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
export const options = {
scenarios: {
load_test: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '1s', target: 100 },
{ duration: '3s', target: 100 },
{ duration: '1s', target: 0 },
],
},
},
};
const BASE_URL = 'http://localhost:8080/api/concerts';
const QUEUE_URL = 'http://localhost:8080/api/waiting-queue';
const users = new SharedArray('users', function () {
return Array.from({ length: 1000 }, (_, i) => i + 1);
});
function fetchToken(userId) {
const queueRes = http.get(`${QUEUE_URL}/position`, {
headers: {
'user-id': userId,
},
});
check(queueRes, {
'Token fetched': (r) => r.status === 200
});
const token = queueRes.json().token;
if (!token) {
console.error(`Failed to fetch token for user ${userId}`);
}
return token;
}
function fetchSchedule(concertId, token) {
const scheduleRes = http.get(`${BASE_URL}/${concertId}/schedules`, {
headers: { Token: token },
});
check(scheduleRes, {
'Concert schedules checked': (r) => r.status === 200,
});
const schedules = scheduleRes.json().schedules;
if (schedules.length === 0) {
console.log('No available schedules for concert ' + concertId);
return;
}
return schedules[0].concertScheduleId;
}
function fetchSeats(concertId, scheduleId, token) {
const seatsRes = http.get(`${BASE_URL}/${concertId}/schedules/${scheduleId}/seats`, {
headers: { Token: token },
});
check(seatsRes, {'Seats availability checked': (r) => r.status === 200});
const seats = seatsRes.json().seats.map((seat) => seat.concertSeatId);
if (seats.length === 0) {
console.log('No available seats for concert ' + concertId + ' and schedule ' + scheduleId);
return;
}
return seats;
}
function makeReservation(concertId, scheduleId, concertSeatId, userId, token) {
const reservationPayload = JSON.stringify({
concertSeatIds: [concertSeatId],
userId: userId,
});
const reservationRes = http.post(
`${BASE_URL}/${concertId}/schedules/${scheduleId}/seats/reservation`,
reservationPayload,
{
headers: {
'Content-Type': 'application/json',
Token: token,
},
}
);
check(reservationRes, {'Reservation made': (r) => r.status === 200});
return reservationRes.json().concertReservationId;
}
function makePayment(concertId, scheduleId, reservationId, seatId, userId, token) {
const paymentPayload = JSON.stringify({
userId: userId,
concertSeatIds: [seatId],
concertReservationId: [reservationId],
});
const paymentRes = http.post(
`${BASE_URL}/${concertId}/schedules/${scheduleId}/seats/reservation/payment`,
paymentPayload,
{
headers: {
'Content-Type': 'application/json',
Token: token,
},
}
);
check(paymentRes, {'Payment completed': (r) => r.status === 200});
}
export default function () {
const userId = users[Math.floor(Math.random() * users.length)];
// Step 1: Fetch token
const token = fetchToken(userId);
if (!token) {
console.error('Token fetching failed for user ' + userId);
return;
}
const concertId = 1;
// Step 2: Fetch schedule
const scheduleId = fetchSchedule(concertId, token);
if (!scheduleId) {
console.error('No schedule available for concert ' + concertId);
return;
}
sleep(1);
// Step 3: Fetch seats
const seatIds = fetchSeats(concertId, scheduleId, token);
if (!seatIds || seatIds.length === 0) {
console.error('No seats available for concert ' + concertId + ' and schedule ' + scheduleId);
return;
}
const seatIndex = Math.floor(Math.random() * seatIds.length);
const seatId = seatIds[seatIndex];
sleep(1);
// Step 4: Make reservation
const reservationId = makeReservation(concertId, scheduleId, seatId, userId, token);
if (!reservationId) {
console.error('Reservation failed for user ' + userId);
return;
}
sleep(2);
// Step 5: Make payment
makePayment(concertId, scheduleId, reservationId, seatId, userId, token);
sleep(1);
}
끝.
'hhplus' 카테고리의 다른 글
가상 장애 대응 방안 보고서 (0) | 2025.01.03 |
---|---|
에러 코드 정의 및 Handler 구현과 Filter, Interceptor 활용 (1) | 2025.01.02 |
캐시를 적용하면 좋은 시나리오 (0) | 2024.12.30 |
시나리오에서 발생할 수 있는 동시성 이슈 정리 (0) | 2024.12.17 |
인덱스 성능 최적화 👑 (0) | 2024.12.06 |
- Total
- Today
- Yesterday
- index
- function
- Lower
- counter
- find
- operators
- bool
- for
- isalpha
- Lambda
- Upper
- Python
- combinations
- Built-in Functions
- If
- permutations
- isdigit
- zip
- Method
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |