k6로 부하테스트중 timeout 문제 발생

송민지·2025년 6월 28일
0

PortCoin

목록 보기
6/7

문제

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

해결과정

첫번째 batch size 와 fetch join 설정

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로 확인해보니 그래프가 시간이 지날수록 비슷해 지는것을 확인할 수 있었습니다.

profile
항상 밝게

0개의 댓글