장애 대응.. +부하테스트
향해99 최종장으로써 장애대응을 파트를 진행하려 한다. 대부분이 부하 테스트의 목적이 있는 것 같은데, 4가지의 테스트가 있다.
Load Test (부하 테스트)
- 시스템이 예상되는 부하를 정상적으로 처리할 수 있는지 평가
- 특정한 부하를 제한된 시간 동안 제공해 이상이 없는지 파악
- 목표치를 설정해 적정한 Application 배포 Spec 또한 고려해 볼 수 있음
Endurance Test( 내구성 테스트 )
- 시스템이 장기간 동안 안정적으로 운영될 수 있는지 평가
- 특정한 부하를 장기간 동안 제공했을 때, 발생하는 문제가 있는지 파악
- 장기적으로 Application을 운영할 때 발생할 수 있는 숨겨진 문제를 파악해 볼 수 있음 (feat. Memory Leak, Slow Query 등 )
Stress Test(스트레스 테스트)
- 시스템이 지속적으로 증가하는 부하를 얼마나 잘 처리할 수 있는지 평가
- 점진적으로 부하를 증가시켰을 때, 발생하는 문제가 있는지 파악
- 장기적으로 Application을 운영하기 위한 Spec 및 확장성과 장기적인 운영 계획을 파악해 볼 수 있음
Peak Test ( 최고 부하 테스트)
- 시스템에 일시적으로 많은 부하가 가해졌을 때, 잘 처리하는지 평가
- 목표치로 설정한 임계 부하를 일순간에 제공했을 때, 정상적으로 처리해내는지 파악
- 선착순 이벤트 등을 준비하면서 정상적으로 서비스를 제공할 수 있을지 파악해 볼 수 있음
위의 4개의 테스트가 있으며, api의 특성에 따라 어떤 테스트를 할지 결정하게 된다.
🕰️ 0. 예상 TPS는?
TPS(Transaction Per Second)는 IT 시스템에서 초당 처리할 수 있는 트랜잭션의 수를 의미하는 성능 지표이다.
TPS의 주요 개념
- 트랜잭션: 트랜잭션은 데이터베이스 상에서 하나의 단위로 처리되는 작업입니다. 예를 들어, 은행에서 계좌 이체를 하는 경우, 금액 차감과 금액 입금이 하나의 트랜잭션으로 간주됩니다.
- 초당 처리 건수: TPS는 초당 처리되는 트랜잭션의 수를 의미합니다. 예를 들어, 1초에 100개의 트랜잭션이 처리된다면 이 시스템의 TPS는 100이 됩니다.
- TPS 측정: TPS는 일반적으로 부하 테스트 툴을 사용하여 측정합니다. 예를 들어, JMeter나 Gatling과 같은 툴을 사용하면 시스템에 인위적으로 부하를 주어 초당 처리할 수 있는 트랜잭션의 수를 측정할 수 있습니다.
예상 TPS란?
TPS= 100×5/60 =8.33 TPS
> (1,000*5)*6/60 = 500TPS
> (10초에 1,000명씩은 좌석 볼 것이다. 새로고침 명당 5번 기준 ) * 6
- /payment
> (10초에 1,000명씩은 예약을 할 것이다. 새로고침 명당 2번 기준 ) * 6
> (1,000*2)*6/60 = 200TPS
포인트
- /wallet/balane/info
> (10초에 1,000명씩은 포인트 조회. 새로고침 명당 2번 기준 ) * 6
> (1,000*2)*6/60 = 200TPS - /wallet/balane/info
> (10초에 1,000명씩은 포인트 충전. 새로고침 명당 1.5번 기준 ) * 6
> (1,000*1.5)*6/60 = 150TPS
🏋️♀️ API 테스트
1) 동시 접속자 수 처리 능력
> (10초에 1,000명씩은 좌석 볼 것이다. 새로고침 명당 5번 기준 ) * 6
> (1,000*5)*6/60 = 500TPS
1단계 무작정 시작해보기
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '10s', target: 300 },
{ duration: '10s', target: 3000 },
{ duration: '10s', target: 300 },
{ duration: '10s', target: 5000 },
{ duration: '10s', target: 200 },
{ duration: '10s', target: 0 }, // 10초 동안 사용자 수를 0으로 줄임 (테스트 종료)
],
};
export default function () {
const url = 'http://localhost:8080/reservation/available/seat?concertScheduleId=1'; // 쿼리 파라미터로 전달
// API 호출
const response = http.get(url);
// 응답이 200 OK인지 확인
check(response, {
'예약 가능 일자': (res) => res.status === 200,
});
// 응답 데이터 출력
console.log(`Response body ${__VU}: `, response.body);
}
checks.........................: 100.00% ✓ 48478 ✗ 0
data_received..................: 14 MB 225 kB/s
data_sent......................: 6.1 MB 102 kB/s
http_req_blocked...............: avg=180.65µs min=1µs med=4µs max=128.89ms p(90)=310.3µs p(95)=537µs
http_req_connecting............: avg=125.82µs min=0s med=0s max=94.34ms p(90)=215µs p(95)=363µs
http_req_duration..............: avg=1.34s min=1.43ms med=2.68ms max=13.87s p(90)=8.51s p(95)=11.91s
{ expected_response:true }...: avg=1.34s min=1.43ms med=2.68ms max=13.87s p(90)=8.51s p(95)=11.91s
http_req_failed................: 0.00% ✓ 0 ✗ 48478
http_req_receiving.............: avg=4ms min=14µs med=120µs max=5.49s p(90)=2.29ms p(95)=8.71ms
http_req_sending...............: avg=303.75µs min=5µs med=16µs max=1.17s p(90)=91µs p(95)=162µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=1.34s min=1.29ms med=2.46ms max=13.87s p(90)=8.5s p(95)=11.88s
http_reqs......................: 48478 807.260677/s
iteration_duration.............: avg=2.56s min=1.71ms med=970.49ms max=18.34s p(90)=11.33s p(95)=15.44s
iterations.....................: 48478 807.260677/s
vus............................: 12 min=12 max=4983
vus_max........................: 5000 min=5000 max=5000
- http_req_duration : 요청 응답 시간을 보면 평균 1.34.초가 걸렸고 90%의 요청이 8.51초 이내에 완료되었으며, 이는 매우 긴 응답 시간이다.
- http_req_waiting (서버에서 응답을 기다리는 시간) : 평균 대기 시간이 1.34초로, 긴 시간이다.
너무 느리다. 개선이 필요 할 것 같다.
해결
api 내에서 사용하는 유일한 쿼리이다. 조회 쿼리로 JPQL상으로는 문제 없어 보인다.
@Query("select s from ConcertSeat s where s.concertSchedule.id = :concertScheduleId and s.status = :status")
List<ConcertSeat> findAllByConcertScheduleIdAndStatus(@Param("concertScheduleId") Long concertScheduleId, @Param("status")SeatStatus status);
2024-08-22T19:50:25.685+09:00 DEBUG 1314 --- [concert] [nio-8080-exec-2] org.hibernate.SQL :
select
cs1_0.concert_seat_id,
cs1_0.assignment_time,
cs1_0.concert_schedule_id,
cs1_0.seat,
cs1_0.status,
cs1_0.temp_assignment_time,
cs1_0.user_id,
cs1_0.version
from
concert_seat cs1_0
where
cs1_0.concert_schedule_id=?
and cs1_0.status=?
2024-08-22T19:50:25.707+09:00 TRACE 1314 --- [concert] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
2024-08-22T19:50:25.708+09:00 TRACE 1314 --- [concert] [nio-8080-exec-2] org.hibernate.orm.jdbc.bind : binding parameter (2:ENUM) <- [UNASSIGNED]
2024-08-22T19:50:25.715+09:00 DEBUG 1314 --- [concert] [nio-8080-exec-2] org.hibernate.SQL :
select
cs1_0.concert_schedule_id,
c1_0.concert_id,
c1_0.concert_at,
c1_0.concert_title,
c1_0.description,
c1_0.version,
cs1_0.concert_at,
cs1_0.create_at,
cs1_0.price,
cs1_0.total_available,
cs1_0.update_at,
cs1_0.version
from
concert_schedule cs1_0
left join
concert c1_0
on c1_0.concert_id=cs1_0.concert_id
where
cs1_0.concert_schedule_id=?
하지만 호출하면 두개의 쿼리가 날아간다. 만약 ConcertSeat 엔티티의 ConcertSchedule 속성이 EAGER 로딩으로 설정되어 있다면, Hibernate는 기본적으로 해당 엔티티를 즉시 로딩하려고 시도한다.
fetch를 LAZY로 설정해준 뒤에 확인해보면
select
cs1_0.concert_seat_id,
cs1_0.assignment_time,
cs1_0.concert_schedule_id,
cs1_0.seat,
cs1_0.status,
cs1_0.temp_assignment_time,
cs1_0.user_id,
cs1_0.version
from
concert_seat cs1_0
where
cs1_0.concert_schedule_id=?
and cs1_0.status=?
쿼리를 한번만 호출 한다.
성능 지표 비교
지표처리 전처리 후비교
총 요청 수 (http_reqs) | 48,478 | 100,553 | 증가 (약 2배) |
초당 요청 수 (http_reqs/s) | 807.26/s | 1,675.72/s | 증가 (약 2배) |
평균 응답 시간 (http_req_duration) | 1.34초 | 510ms | 감소 (성능 개선) |
최소 응답 시간 | 1.43ms | 455µs | 감소 (개선) |
중앙값 응답 시간 (med) | 2.68ms | 1.94ms | 감소 (개선) |
90번째 백분위수 (p(90)) | 8.51초 | 2.88초 | 감소 (개선) |
95번째 백분위수 (p(95)) | 11.91초 | 3.99초 | 감소 (개선) |
최대 응답 시간 (max) | 13.87초 | 5.72초 | 감소 (개선) |
평균 수신 시간 (http_req_receiving) | 4ms | 1.87ms | 감소 (개선) |
평균 대기 시간 (http_req_waiting) | 1.34초 | 508.1ms | 감소 (개선) |
평균 전송 시간 (http_req_sending) | 303.75µs | 23.77µs | 감소 (개선) |
최대 사용자 수 (vus_max) | 5000 | 5000 | 변화 없음 |
평균 반복 실행 시간 (iteration_duration) | 2.56초 | 967.66ms | 감소 (개선) |
쿼리를 바꿔주는 것만으로도 성능이 많이 개선되었다. 요청 수가 늘어난 반면 모든 부분에서 개선되었다.
1,675TPS로 필요로 했던 200TPS보다는 굉장히 빠른 속도이다.
Enduration Test
- 해당 부분은 일정하게 사용자를 받을 수 있는 부분이다. 그에 맞게 테스트를 진행해봤다.
export const options = {
stages: [
{ duration: '2m', target: 300 }, // 2분 동안 300명의 사용자로 증가
{ duration: '3m', target: 300 }, // 3분 동안 300명 유지
{ duration: '2m', target: 3000 }, // 2분 동안 3000명의 사용자로 증가
{ duration: '3m', target: 3000 }, // 3분 동안 3000명 유지
{ duration: '2m', target: 5000 }, // 2분 동안 5000명의 사용자로 증가
{ duration: '5m', target: 5000 }, // 5분 동안 5000명 유지 (최대 부하)
{ duration: '2m', target: 300 }, // 2분 동안 300명의 사용자로 감소
{ duration: '3m', target: 300 }, // 3분 동안 300명 유지
{ duration: '2m', target: 0 }, // 2분 동안 사용자 수를 0으로 줄임 (테스트 종료)
],
};
checks.........................: 99.99% ✓ 1556544 ✗ 46
data_received..................: 435 MB 274 kB/s
data_sent......................: 196 MB 124 kB/s
http_req_blocked...............: avg=21.85µs min=1µs med=3µs max=902.74ms p(90)=6µs p(95)=19µs
http_req_connecting............: avg=12.5µs min=0s med=0s max=679.52ms p(90)=0s p(95)=0s
http_req_duration..............: avg=2.09s min=429µs med=402.08ms max=2m33s p(90)=5.24s p(95)=5.93s
{ expected_response:true }...: avg=2.08s min=429µs med=402.06ms max=2m32s p(90)=5.24s p(95)=5.93s
http_req_failed................: 0.00% ✓ 46 ✗ 1556544
http_req_receiving.............: avg=5.36ms min=10µs med=837µs max=2m26s p(90)=6.47ms p(95)=34.91ms
http_req_sending...............: avg=125.84µs min=4µs med=15µs max=2m29s p(90)=37µs p(95)=53µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=2.08s min=383µs med=400.15ms max=2m33s p(90)=5.24s p(95)=5.92s
http_reqs......................: 1556590 981.13991/s
iteration_duration.............: avg=2.04s min=517.32µs med=1.16s max=23.41s p(90)=5.3s p(95)=5.95s
iterations.....................: 1556590 981.13991/s
vus............................: 2 min=2 max=5000
vus_max........................: 5000 min=5000 max=5000
평균 요청시간이 2초로 꽤 높다. 최대는 5초까지도 나간다. 서버에 비해 사용자 수가 높다는 생각이 들었다. 이럴 경우 서버 증설이 필요하지 않을까 싶다. 쿼리 외에 들어있는게 없으므로 더 이상의 개선의 여지는 없어 보였다.
🤼 전체 시나리오 테스트
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
reservation_process: {
executor: 'per-vu-iterations',
vus: 1000, // 동시 사용자의 수
iterations: 1, // 각 사용자가 한 번씩 시나리오를 수행
maxDuration: '1m', // 최대 시나리오 수행 시간
},
},
};
let globalSeatId = 0; // 전역 변수를 사용하여 전체 시나리오에서 증가
export default function () {
// 1. 토큰 생성 요청
let userIdtoken = (__VU - 1) * 10 + __ITER + 1; // __VU와 __ITER을 조합하여 1부터 시작
let urltoken = 'http://localhost:8080/api/token';
let payload1 = JSON.stringify({
userId: userIdtoken,
});
let params1 = {
headers: {
'Content-Type': 'application/json',
},
};
let restoken = http.post(urltoken, payload1, params1);
check(restoken, {
'토큰 생성 성공': (r) => r.status === 200,
});
let tokenData;
try {
tokenData = JSON.parse(restoken.body);
} catch (e) {
console.error('JSON 파싱 오류:', e.message);
return;
}
let token = tokenData.token;
let tokenOrder = tokenData.tokenOrder;
let waitingTime = tokenData.waitingTimeSeconds;
// Authorization 헤더에 Bearer 토큰 추가
let authHeader = { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } };
console.log('토큰 생성 응답: ' + restoken.body);
// 2. 대기 시간 조건 처리
if (waitingTime === 0) {
waitingTime = 10; // waitingTime이 0이면 10초로 설정
}
console.log(`Waiting for ${waitingTime} seconds...`);
sleep(waitingTime); // 지정된 시간 동안 대기
// 3. 토큰 정보 확인 및 활성화
let urltoken2 = 'http://localhost:8080/api/token/info';
let restoken2 = http.get(urltoken2, authHeader);
check(restoken2, {
'토큰 활성화 성공': (r) => r.status === 200,
});
console.log('토큰 정보 응답: ' + restoken2.body);
/** 토큰 끝 */
// 1. 예약 가능 날짜 확인
let url1 = 'http://localhost:8080/reservation/available/date?concertScheduleId=1';
let res1 = http.get(url1, authHeader); // 토큰을 포함한 요청
check(res1, {
'예약 가능 날짜 확인 성공': (r) => r.status === 200,
});
console.log('예약 가능 날짜 확인 응답: ' + res1.body);
sleep(2); // 사용자가 다음 단계로 넘어가기 전 2초 대기 (thinkingTime)
// 2. 예약 가능 좌석 확인
let url2 = 'http://localhost:8080/reservation/available/seat?concertScheduleId=1';
let res2 = http.get(url2, authHeader); // 토큰을 포함한 요청
check(res2, {
'예약 가능 좌석 확인 성공': (r) => r.status === 200,
});
console.log('예약 가능 좌석 확인 응답: ' + res2.body);
sleep(3); // 사용자가 좌석 선택 전 3초 대기 (thinkingTime)
// 전역 변수로 concertSeatId를 증가시킴
globalSeatId += 1;
// 3. 임시 예약
let concertSeatId = __VU + 1 + __ITER;
let url3 = 'http://localhost:8080/reservation';
let payload3 = JSON.stringify({
concertScheduleId: 1,
concertId: 1,
concertSeatId: concertSeatId,
});
let res3 = http.post(url3, payload3, authHeader); // 토큰을 포함한 요청
check(res3, {
'임시 예약 성공': (r) => r.status === 200,
});
console.log('임시 예약 응답: ' + res3.body);
let concertReservationId = JSON.parse(res3.body).concertReservationId;
sleep(4); // 사용자가 결제 전 4초 대기 (thinkingTime)
// 4. 충전
let userId = (__VU - 1) * 10 + __ITER + 1; // __VU와 __ITER을 조합하여 1부터 시작
let url4 = 'http://localhost:8080/wallet/charge';
let payload4 = JSON.stringify({
userId: userId,
amount: 30000,
});
let res4 = http.post(url4, payload4, authHeader); // 토큰을 포함한 요청
check(res4, {
'충전 성공': (r) => r.status === 200,
});
console.log('충전 응답: ' + res4.body);
sleep(4); // 사용자가 결제 전 4초 대기 (thinkingTime)
// 5. 결제
let url5 = 'http://localhost:8080/payment';
let payload5 = JSON.stringify({
userId: userId, // 충전에서 사용한 userId
concertScheduleId: 1,
concertReservationId: concertReservationId, // 임시 예약에서 얻은 concertReservationId 사용
concertSeatId: concertSeatId, // 임시 예약에서 사용한 concertSeatId
});
let res5 = http.post(url5, payload5, authHeader); // 토큰을 포함한 요청
check(res5, {
'결제 성공': (r) => r.status === 200,
});
console.log('결제 응답: ' + res5.body);
sleep(1); // 마지막 단계 후 1초 대기 (thinkingTime)
}
처음에는 10명 정도로 시작했다.
✓ 토큰 생성 성공
✓ 토큰 활성화 성공
✓ 예약 가능 날짜 확인 성공
✓ 예약 가능 좌석 확인 성공
✓ 임시 예약 성공
✓ 충전 성공
✓ 결제 성공
checks.........................: 100.00% ✓ 70 ✗ 0
data_received..................: 45 kB 1.8 kB/s
data_sent......................: 16 kB 617 B/s
http_req_blocked...............: avg=366.17µs min=2µs med=3.5µs max=3.41ms p(90)=2.32ms p(95)=2.72ms
http_req_connecting............: avg=149.98µs min=0s med=0s max=1.96ms p(90)=725.7µs p(95)=1.13ms
http_req_duration..............: avg=149.33ms min=20.23ms med=42.07ms max=692.61ms p(90)=691.59ms p(95)=692.25ms
{ expected_response:true }...: avg=149.33ms min=20.23ms med=42.07ms max=692.61ms p(90)=691.59ms p(95)=692.25ms
http_req_failed................: 0.00% ✓ 0 ✗ 70
http_req_receiving.............: avg=710.68µs min=19µs med=224µs max=6.63ms p(90)=2.83ms p(95)=3.06ms
http_req_sending...............: avg=87.17µs min=9µs med=24.5µs max=1.05ms p(90)=267.2µs p(95)=370.74µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=148.53ms min=19.92ms med=42.01ms max=689.1ms p(90)=688.56ms p(95)=688.83ms
http_reqs......................: 70 2.79053/s
iteration_duration.............: avg=25.08s min=25.08s med=25.08s max=25.08s p(90)=25.08s p(95)=25.08s
iterations.....................: 10 0.398647/s
vus............................: 10 min=10 max=10
1. 테스트 개요
- 동시 사용자 수: 10명
- 시나리오 반복 횟수: 10회
- 총 HTTP 요청 수: 70회
2. 평균 요청에 대한 성능 평가
- 평균 응답 시간: 149.33ms
- 최소 응답 시간: 20.23ms
- 최대 응답 시간: 692.61ms
- 느낌: 최소 응답 시간은 20.23ms로 매우 빠르고, 최대 응답 시간도 692.61ms로 1초를 넘지 않아서 크게 문제될 부분은 없어 보였다.
하지만 1000명으로 유저수를 늘려보았을 때에
✗ 토큰 생성 성공
↳ 41% — ✓ 416 / ✗ 584
✓ 토큰 활성화 성공
✓ 예약 가능 날짜 확인 성공
✓ 예약 가능 좌석 확인 성공
✓ 임시 예약 성공
✓ 충전 성공
✓ 결제 성공
토큰 생성 성공률이 현저하게 떨어지게 되었다. 원인 파악 및 보수가 필요해보인다. 그렇게 몇번을 바꿔봐도 매번 실패값이 높았다. 내놓은 결론은 실제로 한번에 1000명정도가 서버에 들어오지 않을 뿐더러, 한번에 많은 부하를 서버가 받아들이지 못하는 것 같았다. (실제로 back단에는 log가 찍히지 않는다.)
방법을 다르게해서 부하를 서서히 주는.. 그래프와 같은 방식으로 넣어봤다.
- LOAD TEST
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
reservation_process: {
executor: 'ramping-vus', // 사용자 수를 점진적으로 증가시키는 executor
startVUs: 0, // 시작 시 동시 사용자 수
stages: [
{ duration: '30s', target: 100 }, // 30초 동안 동시 사용자 수를 100으로 증가
{ duration: '1m', target: 1000 }, // 1분 동안 동시 사용자 수를 1000으로 증가
{ duration: '30s', target: 0 }, // 30초 동안 동시 사용자 수를 0으로 감소
],
},
},
};
let globalSeatId = 0; // 전역 변수를 사용하여 전체 시나리오에서 증가
export default function () {
// 1. 토큰 생성 요청
let userIdtoken = (__VU - 1) * 10 + __ITER + 1; // __VU와 __ITER을 조합하여 1부터 시작
let urltoken = 'http://localhost:8080/api/token';
let payload1 = JSON.stringify({
userId: userIdtoken,
});
let params1 = {
headers: {
'Content-Type': 'application/json',
},
};
let restoken = http.post(urltoken, payload1, params1);
check(restoken, {
'토큰 생성 성공': (r) => r.status === 200,
});
let tokenData;
try {
tokenData = JSON.parse(restoken.body);
} catch (e) {
console.error('JSON 파싱 오류:', e.message);
return;
}
let token = tokenData.token;
let tokenOrder = tokenData.tokenOrder;
let waitingTime = tokenData.waitingTimeSeconds;
// Authorization 헤더에 Bearer 토큰 추가
let authHeader = { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } };
console.log('토큰 생성 응답: ' + restoken.body);
// 2. 대기 시간 조건 처리
if (waitingTime === 0) {
waitingTime = 10; // waitingTime이 0이면 10초로 설정
}
console.log(`Waiting for ${waitingTime} seconds...`);
sleep(waitingTime); // 지정된 시간 동안 대기
// 3. 토큰 정보 확인 및 활성화
let urltoken2 = 'http://localhost:8080/api/token/info';
let restoken2 = http.get(urltoken2, authHeader);
check(restoken2, {
'토큰 활성화 성공': (r) => r.status === 200,
});
console.log('토큰 정보 응답: ' + restoken2.body);
// 1. 예약 가능 날짜 확인
let url1 = 'http://localhost:8080/reservation/available/date?concertScheduleId=1';
let res1 = http.get(url1, authHeader); // 토큰을 포함한 요청
check(res1, {
'예약 가능 날짜 확인 성공': (r) => r.status === 200,
});
console.log('예약 가능 날짜 확인 응답: ' + res1.body);
sleep(2); // 사용자가 다음 단계로 넘어가기 전 2초 대기 (thinkingTime)
// 2. 예약 가능 좌석 확인
let url2 = 'http://localhost:8080/reservation/available/seat?concertScheduleId=1';
let res2 = http.get(url2, authHeader); // 토큰을 포함한 요청
check(res2, {
'예약 가능 좌석 확인 성공': (r) => r.status === 200,
});
console.log('예약 가능 좌석 확인 응답: ' + res2.body);
sleep(3); // 사용자가 좌석 선택 전 3초 대기 (thinkingTime)
// 전역 변수로 concertSeatId를 증가시킴
globalSeatId += 1;
// 3. 임시 예약
let concertSeatId = __VU + 1 + __ITER;
let url3 = 'http://localhost:8080/reservation';
let payload3 = JSON.stringify({
concertScheduleId: 1,
concertId: 1,
concertSeatId: concertSeatId,
});
let res3 = http.post(url3, payload3, authHeader); // 토큰을 포함한 요청
check(res3, {
'임시 예약 성공': (r) => r.status === 200,
});
console.log('임시 예약 응답: ' + res3.body);
let concertReservationId = JSON.parse(res3.body).concertReservationId;
sleep(4); // 사용자가 결제 전 4초 대기 (thinkingTime)
// 4. 충전
let userId = (__VU - 1) * 10 + __ITER + 1; // __VU와 __ITER을 조합하여 1부터 시작
let url4 = 'http://localhost:8080/wallet/charge';
let payload4 = JSON.stringify({
userId: userId,
amount: 30000,
});
let res4 = http.post(url4, payload4, authHeader); // 토큰을 포함한 요청
check(res4, {
'충전 성공': (r) => r.status === 200,
});
console.log('충전 응답: ' + res4.body);
sleep(4); // 사용자가 결제 전 4초 대기 (thinkingTime)
// 5. 결제
let url5 = 'http://localhost:8080/payment';
let payload5 = JSON.stringify({
userId: userId, // 충전에서 사용한 userId
concertScheduleId: 1,
concertReservationId: concertReservationId, // 임시 예약에서 얻은 concertReservationId 사용
concertSeatId: concertSeatId, // 임시 예약에서 사용한 concertSeatId
});
let res5 = http.post(url5, payload5, authHeader); // 토큰을 포함한 요청
check(res5, {
'결제 성공': (r) => r.status === 200,
});
console.log('결제 응답: ' + res5.body);
sleep(1); // 마지막 단계 후 1초 대기 (thinkingTime)
}
✓ 토큰 생성 성공
✓ 토큰 활성화 성공
✓ 예약 가능 날짜 확인 성공
✓ 예약 가능 좌석 확인 성공
✗ 임시 예약 성공
↳ 90% — ✓ 1520 / ✗ 167
✓ 충전 성공
✗ 결제 성공
↳ 90% — ✓ 1404 / ✗ 148
checks.........................: 97.33% ✓ 11498 ✗ 315
data_received..................: 145 MB 995 kB/s
data_sent......................: 2.6 MB 18 kB/s
http_req_blocked...............: avg=102.57µs min=1µs med=5µs max=56.71ms p(90)=343.8µs p(95)=493µs
http_req_connecting............: avg=72.71µs min=0s med=0s max=55.93ms p(90)=263µs p(95)=380.39µs
http_req_duration..............: avg=2.07s min=1.74ms med=1.6s max=10.47s p(90)=4.7s p(95)=6.24s
{ expected_response:true }...: avg=2.08s min=1.74ms med=1.61s max=10.47s p(90)=4.69s p(95)=6.23s
http_req_failed................: 2.66% ✓ 315 ✗ 11498
http_req_receiving.............: avg=14.72ms min=22µs med=1.4ms max=1.35s p(90)=38.43ms p(95)=48.34ms
http_req_sending...............: avg=71.26µs min=8µs med=28µs max=189.23ms p(90)=75µs p(95)=109µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=2.06s min=1.63ms med=1.59s max=10.47s p(90)=4.68s p(95)=6.23s
http_reqs......................: 11813 81.193602/s
iteration_duration.............: avg=40.63s min=24.1s med=42.81s max=1m0s p(90)=51.82s p(95)=53.74s
iterations.....................: 1521 10.4542/s
vus............................: 1 min=1 max=1000
vus_max........................: 1000 min=1000 max=1000
- 실패가 10퍼센트
- 응답,요청 시간 등이 매우 많이 나간다.
테스트시에 실패는 많이 줄은 모습이지만, 역시나 서버의 성능을 따라가지 못한다. 실무에서는 서버를 늘리는 등의 방법도 있겠지만, 현재는 코드에서 변경이 가능하다면 수정이 필요하다.
- 임시 예약 및 결제 단계: 두 단계에서 각각 10%의 실패율이 발생, 이 단계들은 추가적인 최적화가 필요합
- 응답 시간: 응답 시간이 특히 높은 상황에서 서버의 성능 최적화가 필요, 최대 응답 시간을 줄이고, P90 및 P95 응답 시간을 개선할 필요가 있다.
- 서버 자원 관리: 이 부하에서 서버 자원이 충분히 사용되고 있는지, 과도한 부하로 인해 어떤 자원이 부족한지 (예: CPU, 메모리, 디스크 I/O 등) 분석이 필요하다.