PortCoin 프로젝트의 부하테스트중 마지막 부분에서 request fail i/o timeout문제가 발생하였습니다.

Spring console확인결과 portfolio_coin entity의 currentPrice값을 업데이트 하는도중 50개가 넘는 쿼리문이 발생하고 있는 문제를 확인하였습니다.
k6 테스트 결과
█ TOTAL RESULTS
checks_total.......................: 61985 707.49902/s
checks_succeeded...................: 99.82% 61874 out of 61985
checks_failed......................: 0.17% 111 out of 61985
✗ token exists
↳ 99% — ✓ 7058 / ✗ 2
✗ profit list is array
↳ 99% — ✓ 7008 / ✗ 52
✗ profit list not empty
↳ 99% — ✓ 7008 / ✗ 52
✗ main status is 200
↳ 99% — ✓ 40800 / ✗ 5
HTTP
http_req_duration.......................................................: avg=10.17ms min=0s med=4.91ms max=24.97s p(90)=23.26ms p(95)=32.22ms
{ expected_response:true }............................................: avg=10.15ms min=1.74ms med=4.91ms max=24.97s p(90)=23.22ms p(95)=32.19ms
http_req_failed.........................................................: 0.10% 59 out of 54925
http_reqs...............................................................: 54925 626.915926/s
EXECUTION
iteration_duration......................................................: avg=16.24ms min=1.79ms med=4.74ms max=30.02s p(90)=34.88ms p(95)=44.63ms
iterations..............................................................: 47865 546.332832/s
vus.....................................................................: 1 min=1 max=10
vus_max.................................................................: 10 min=10 max=10
NETWORK
data_received...........................................................: 1.0 GB 12 MB/s
data_sent...............................................................: 7.7 MB 88 kB/s
currentPrice 업데이트 과정에서 현재 저장된 코인의 갯수만큼 update 쿼리가 생성되고 있기 때문에,
application-dev.yml에서 batch size를 50으로 지정하였습니다. 또한 fetch join을 사용하여
portfolioCoin과 coin entity를 같이 조회하도록 변경하였습니다.
콘솔에는 update 쿼리가 출력되는게 눈에 띄게 줄었고,
█ TOTAL RESULTS
checks_total.......................: 77603 1293.24814/s
checks_succeeded...................: 99.85% 77489 out of 77603
checks_failed......................: 0.14% 114 out of 77603
✓ token exists
✗ profit list is array
↳ 99% — ✓ 6410 / ✗ 57
✗ profit list not empty
↳ 99% — ✓ 6410 / ✗ 57
✓ main status is 200
HTTP
http_req_duration.......................................................: avg=8.29ms min=-10760264ns med=4.72ms max=2.68s p(90)=15.1ms p(95)=32.11ms
{ expected_response:true }............................................: avg=8.28ms min=-10760264ns med=4.72ms max=2.68s p(90)=15.01ms p(95)=32.11ms
http_req_failed.........................................................: 0.08% 57 out of 71136
http_reqs...............................................................: 71136 1185.476072/s
EXECUTION
iteration_duration......................................................: avg=9.26ms min=2.01ms med=4.68ms max=2.69s p(90)=27.52ms p(95)=43.55ms
iterations..............................................................: 64669 1077.704005/s
vus.....................................................................: 10 min=10 max=10
vus_max.................................................................: 10 min=10 max=10
NETWORK
data_received...........................................................: 1.5 GB 24 MB/s
data_sent.............
실패율 역시 0.03% 감소하였습니다.
수익률 요청 API는
[
{
"portfolioId": 0,
"fullName": "string",
"image": "string",
"profitLoss": 0
}
]
이렇게 ResponseDto를 반환하고 있습니다. 따라서 res.status==200과 array에 데이터가 담겨오는지 확인하는 시나리오의 수정이 필요하였습니다.
import http from 'k6/http';
import { check } from 'k6';
const BASE_URL = 'http://host.docker.internal:8080'; //k6, grafana, influxdb가 docker-compose로 실행중이여서 외부 ip 노출
const EMAIL = '테스트유저 이메일';
const PASSWORD = '테스트유저 비밀번호';
// options: 시나리오 병렬 실행
export const options = {
scenarios: {
main_page: {
executor: 'constant-vus',
exec: 'mainPage', // 조회할 api 함수명
vus: 5, //가상 유저
duration: '1m', // 1분동안
},
profit_rate: {
executor: 'constant-vus',
exec: 'profitRate',
vus: 5,
duration: '1m',
},
},
httpTimeout: '2m', //Timeout 설정
};
//메인 페이지 조회 (토큰 불필요)
export function mainPage() {
const res = http.get(`${BASE_URL}/api/v1/coin/price`);
check(res, {
'main status is 200': (r) => r.status === 200,
});
}
//수익률 계산 (토큰 필요)
export function profitRate() {
// 1. 로그인 요청
const loginRes = http.post(`${BASE_URL}/api/v1/users/auth/sign-in`, JSON.stringify({
email: EMAIL,
password: PASSWORD,
}), {
headers: { 'Content-Type': 'application/json' },
});
const accessToken = loginRes.body?.trim();
check(loginRes, {
'token exists': (r) => typeof r.body === 'string' && r.body.length > 0
});
// 2. 수익률 요청
const profitRes = http.get(`${BASE_URL}/api/v1/portfolio-coins/1025`, {
headers: {
Authorization: accessToken || '',
},
});
check(profitRes, {
'main status is 200': (r) => r.status === 200, // HTTP 상태코드 200인지 확인
'profit list is array': (r) => {
try {
const data = JSON.parse(r.body);
return Array.isArray(data);
} catch {
return false;
}
},
});
}
█ TOTAL RESULTS
k6-1 |
k6-1 | checks_total.......................: 54211 640.76741/s
k6-1 | checks_succeeded...................: 99.80% 54105 out of 54211
k6-1 | checks_failed......................: 0.19% 106 out of 54211
k6-1 |
k6-1 | ✗ token exists
k6-1 | ↳ 99% — ✓ 4812 / ✗ 1
k6-1 | ✗ main status is 200
k6-1 | ↳ 99% — ✓ 44532 / ✗ 53
k6-1 | ✗ profit list is array
k6-1 | ↳ 98% — ✓ 4761 / ✗ 52
k6-1 |
k6-1 | HTTP
k6-1 | http_req_duration.......................................................: avg=6.91ms min=0s med=3.85ms max=1.88s p(90)=17.83ms p(95)=23.12ms
k6-1 | { expected_response:true }............................................: avg=6.9ms min=1.71ms med=3.85ms max=1.88s p(90)=17.81ms p(95)=23.11ms
k6-1 | http_req_failed.........................................................: 0.10% 54 out of 49398
k6-1 | http_reqs...............................................................: 49398 583.878337/s
k6-1 |
k6-1 | EXECUTION
k6-1 | iteration_duration......................................................: avg=9.15ms min=1.77ms med=3.78ms max=30.02s p(90)=26.04ms p(95)=32.35ms
k6-1 | iterations..............................................................: 44585 526.989264/s
k6-1 | vus.....................................................................: 1 min=1 max=6
k6-1 | vus_max.................................................................: 6 min=6 max=6
k6-1 |
k6-1 | NETWORK
k6-1 | data_received...........................................................: 998 MB 12 MB/s
k6-1 | data_sent...............................................................: 6.4 MB 76 kB/s
결과적으로는 다시 실패율이 올라갔습니다. 로그를 확인해보니

timeout이 되어 요청이 실패한 경우들이 있어 timeout시간을 2분으로 늘려봤는데도 0.19%에서 감소되지 않았습니다.
하지만 기존과 다르게 check_failed 숫자가 114개에서 106개로 8개 정도 줄이는데는 성공하였습니다.
Grafana로 확인해보니 그래프가 시간이 지날수록 비슷해 지는것을 확인할 수 있었습니다.
