본문 바로가기

카테고리 없음

부하테스트 시나리오 작성

1.부하 테스트 대상 선정 및 목적, 시나리오 등의 계획을 세우고 이를 문서로 작성

 

1. 부하 테스트 대상 선정

 

1.후보 대상 : 각 domain에 해당하는 interface들

 

coupon(쿠폰) : 선착순 쿠폰 발급  /coupon/issue_limited_coupon

payment(결제) : 결제 승인 요청 /payment/confirm

product(상품) : 상품 상세 조회 /product/detail/{productId}

purchase_order(주문) : 주문 생성 /purchase_order/create_order

statics(통계) : 인기상품조회 /statics/top5 (3일간 최다 판매량 5개 상품 조회)

user(사용자) : 잔고조회 user/get_my_balance

                       보유 쿠폰 조회 /user/get_my_coupons

 

2. 동시성 이슈가 생길 것 같은 대상들

 

선착순 쿠폰 발급 - 선착순 쿠폰 발급시 동시성 이슈 발생 가능성 높음. - 시나리오 제외

 

[v] 결제 승인 요청  - 결제 승인시 상품의 재고 차감이 될 떄 동시성 이슈 발생 가능성 높음.

 

3. 목적, 시나리오 등의 계획

 

목적 유저가 상품을 주문 결제 할시 오류가 있는지 테스트한다.(유저의 전체 행동으로 테스트)

 

유저 접속(가정) -> 상품 상세 조회 -> 1s -> 주문 -> 3s -> 결제 - 3s

 

 

4. 적합한 테스트 스크립트를 작성하고 수행

 

import http from 'k6/http';
import { check, sleep, group } from 'k6';

export let options = {
    vus: 10,  // 가상 사용자 수
    duration: '30s', // 테스트 실행 시간
};


export default function () {

group('product_detail', () => {
    // URL에 productId 값을 올바르게 포함시킵니다.
    let productId = 1;

    let res = http.get(`http://localhost:8080/product/detail/${productId}`);
    check(res, { '상품 상세 조회 상태 코드 200': (r) => r.status === 200 });
    sleep(1);
});

group('create_order', () => {
    // ProductDTO 객체 예시 (실제 DTO 구조에 맞게 수정)
    let orderProducts = [{
      productId: 1,          // 상품 ID
      name: "Test Product",  // 상품 이름
      quantity: 2            // 주문 수량
    }];

    // 사용한 쿠폰이 없으면 빈 배열, 실제 쿠폰 정보가 있다면 해당 객체 배열 전달
    let usedCoupons = [];

    let orderPayload = JSON.stringify({
      userId: 1,
      orderProducts: orderProducts,
      usedCoupons: usedCoupons
    });

    let orderParams = { headers: { 'Content-Type': 'application/json' } };
    let res = http.post('http://localhost:8080/purchase_order/create_order', orderPayload, orderParams);
    check(res, { '주문 생성 상태 코드 200': (r) => r.status === 200 });

    // 응답 JSON에서 purchaseOrderId 추출 (응답 필드명이 PurchaseOrderCreateResponse와 일치한다고 가정)
    let json = res.json();
    console.log(json);
    purchaseOrderId = json.purchaseOrderId;
    sleep(3); // 3초 대기
  });

  // 3. 결제 승인 요청
  group('confirm_payment', () => {
    let paymentPayload = JSON.stringify({
      userId: userId,
      purchaseOrderId: purchaseOrderId,
      vendor: "TEST" // PaymentVendor enum의 값에 맞게 설정 (예시)
    });
    let paymentParams = { headers: { 'Content-Type': 'application/json' } };
    let res = http.post('http://localhost:8080/payment/confirm', paymentPayload, paymentParams);
    check(res, { '결제 승인 상태 코드 200': (r) => r.status === 200 });
    sleep(3); // 3초 대기
  });
}

스크립트를 작성하였으나 아래 오류를 해결하지 못함.

ERRO[0004] GoError: the body is null so we can't transform it to JSON - this likely was because of a request error getting the response                                                                                         
running at reflect.methodValueCall (native)                                                                                                                                                                                     
default at file:///C:/Study/hhplus/hhplus-7th-ecommerce/load-test-senario.js:43:24(47)                                                                                                                                          
        at go.k6.io/k6/js/modules/k6.(*K6).Group-fm (native)
        at default (file:///C:/Study/hhplus/hhplus-7th-ecommerce/load-test-senario.js:21:6(11))  executor=constant-vus scenario=default source=stacktrace
WARN[0004] Request Failed                                error="Get \"http://localhost:8080/product/detail/1\": dial tcp 127.0.0.1:8080: connectex: No connection could be made because the target machine actively refused it. 
WARN[0004] Request Failed                                error="Get \"http://localhost:8080/product/detail/1\": dial tcp 127.0.0.1:8080: connectex: No connection could be made because the target machine actively refused it. 
WARN[0004] Request Failed                                error="Post \"http://localhost:8080/purchase_order/create_order\": dial tcp 127.0.0.1:8080: connectex: No connection could be made because the target machine actively refused it."

 

 

 

아래는 작성 전 조사 내용이다.

1. e-commerce 프로젝트 진행 시 고려해야할 TPS 설정

 

1. TPS의 정의

   TPS(Transaction per sec)는 클라이언트의 요청으로부터 응답이 돌아오기까지의 초당 처리 개수이다.  클라이언트 요청부터 응답이 돌아오는 시간, 즉 응답시간(Response Time)은 지연시간(Latency  Time)과 처리시간(Processing Time)으로 나뉜다. 지연시간은 클라이언트와 서버 사이의 시간이고 처리시간은 서버와 DB 사이의 시간이다. 백엔드 엔지니어는 서버와 DB 사이의 처리시간을 단축시키는 역할을 할 수 있을 것이다.

 

참조 :  최범균님 유튜브 :  https://www.youtube.com/watch?v=JJJ4LReZ5q4&list=PLwouWTPuIjUg0dmHoxgqNXyx3Acy7BNCz&index=19  

f-lab 기술 블로그 : https://f-lab.kr/insight/rps-vs-tps-server-optimization-20250102


 

2. TPS 테스트 시나리오를 위한 조사(일반기업 사례) 

 

   만약 e-commerce 프로젝트를 런칭했다고 치자.  실질적으로 국내 이름을 알법한 전자 상거래 회사들을 기준으로 TPS를 안정적으로 처리하려면 어느정도의 처리량이 필요할까?

 

2024년 12월 2일자 한경뉴스에 실린 기사에 실린 쇼핑앱을 기준으로한 MAU(월간 활성 사용자)이다.

https://www.hankyung.com/article/202412027041g

출처 : https://www.hankyung.com/article/202412027041g

 

쿠팡 : 3203만명

알리익스프레스 : 904만명

a-bly : 878만명

11번가 : 744만명

G마켓 : 548만명

 

이다.(모바일앱 기준이지만 일단 이 기사를 기준으로...) 이제 복잡한 여러가지 상황은 제외하고 단순하게 생각해보겠다.

 

쿠팡을 기준 단순 산술로 하루 접속자를 계산해보자.

3203만명 / 30 = 107만명

 

동일한 방식으로 G마켓 기준

548만명 / 30 = 18만명

 

단순히 하루 24시간을 사람들이 모두 접속한다고 볼 수는 없을 것이다.

사람이 8시간은 자고 10시간은 일한다(이동시간과 점심시간 근무시간 포함)고 가정할때 실질적으로 e-commerce에 접속 가능한 시간을 24-18시간 = 6시간이라고 가정하자.(월급루팡이나 백수는 제외하자)

 

그렇다면 시간당 접속자는

쿠팡 기준 : 107만 / 6 = 17만명

G마켓 기준 : 18만 / 6 = 3만명

 

이라고 가정한다.

접속자들은 해당 시간동안 꾸준히 무언가를 할 것이다. 예를 들면 상품 검색, 둘러보기, 구매 와 같은 서비스를 17만명, 혹은 3만명이 동시에 한다고 생각했을 때 예상 TPS를 3만~17만이라고 잡을 수 있겠다.

(물론 우리의 서비스는 이보다 훨씬 적은 트래픽을 처리하겠지만...?)

 

아래와 같이 사용자가 40% 이탈하지 않으려면 2초 이내에 백엔드 서버와 프론트에서 모든 것을 처리해야 한다.

https://www.ranktracker.com/ko/blog/user-experience-and-seo-how-site-speed-and-usability-impact-rankings/

 

유명한 기업( 평범한 사용자가 이름을 들어본 )에 입사하고 싶다면 최소한 초당 3만~ 17만 정도의 요청을 안정적으로 처리하는 서버를 만들 수 있으면 될 것 같다.(실제로는 유저가 몰리는 시간도 있을 것이지만 그런 상황들은 배제했다.)

 

참고로 알리바바는 2020년 광군제에 초당 58만건의 주문을 처리했다고 한다.
https://zdnet.co.kr/view/?no=20201119161057

 

기준 시나리오 TPS는 30,000~170,000으로 잡아 보겠다. 

2. 평균/중간/최대 응답시간 

 

- 평균 응답 시간 - 모든 응답 시간의 합 / 응답 개수

 

- 중간 응답 시간 - 모든 응답 시간 정렬 후 중간 값

 

- 최대 응답 시간 - 모든 응답 시간 중 최대값

 

실제로는 테스트 도구를 통해 파악하는 것이 정확하다고 한다. 

 

- Apache JMeter : 평균/중간/최대 응답시간 계산 제공.

- K6 : 평균 응답 시간 및 백분위 수(P50, P90, P99)를 제공.

 

이외에 아래 블로그와 같이 계산할 수 있을 것이다.

https://hyuntaeknote.tistory.com/10

 

내가 만든 서비스는 얼마나 많은 사용자가 이용할 수 있을까? - 1편(성능 테스트란?)

개요 Agora 프로젝트를 서버 배포에 성공하고, 서비스가 얼마나 많은 사용자를 감당할 수 있을지 알고 싶어 졌습니다. 이를 위해 성능 테스트를 진행해보았습니다. 이번 포스팅 시리즈는 성능 테

hyuntaeknote.tistory.com

 

 

3. 다량의 트래픽 유입 시 동시성 이슈 발생 여부 체크

 

   아래 2008년도에 작성된 동시성 이슈 체크하는 방법이라는 글에서는 다섯가지를 활용해서 체크하라고 한다. 정적 분석(Static Analysis), 동적 분석(Dynamic Analysis), 부하 테스트(Load Testing), 스트레스 테스트(Stress Testing), 그리고 로그 및 이벤트 트레이싱 이다.

https://learn.microsoft.com/en-us/archive/msdn-magazine/2008/june/tools-and-techniques-to-identify-concurrency-issues

 

Tools And Techniques to Identify Concurrency Issues

Concurrency Article 09/10/2019 In this article --> Rahul V. Patil Boby George In order to satisfy the perpetual need for increased computing power, the hardware industry is steadily shifting toward multi- and many-core processor systems. Unlike the increas

learn.microsoft.com

 

1. 정적 분석

- 프로그램 실행 없이 소스 코드 분석

- 컴파일 타임에 실행

- 공유 변수 감지, 락 획득 순서 검출, 비정상적 스레드 종료 감지 등의 기능을 가지고 있음.

- 대표적인 분석 도구 : FindBugs, Coverity, FxCop, Clang Static Analyzer

추가: IntelliJ - Qodana 

 

 2, 동적 분석

- 프로그램 실행 중 동시성 문제 감지

- Race Condition, Deadlock, Livelock 등의 문제를 탐색하는데 유용.

- 실시간 스레드 프로파일링, 데드락 감지, 경쟁조건( Race Condition) 감지 등의 기능을 가지고 있음.

- Windows 환경에서는 Concurrency Visualizer가, Java 환경에서는 VisualVM, YourKit 등의 도구가 사용됨,

- IntelliJ에서는 내장 프로파일러(울티메이트 가능),  VisualVM(Java 8이상 포함), Java Flight Recorder(Java11이상 기본 지원)&Mission Control를 사용할 수 있다.

 

3. 부하테스트, 스트레스 테스트

 

1. 부하 테스트

 

   부하 테스트는 애플리케이션이 일정한 부하에서 정상적으로 작동하는지 확인하는 데 사용. 일반적으로 예상되는 사용자 수보다 조금 더 높은 수준의 요청을 처리하면서 시스템이 안정적으로 작동하는지를 평가.

  • 테스트 방식:
    • JMeter, Locust, k6 같은 부하 테스트 도구를 사용하여 수천~수만 개의 요청을 발생시킴
    • 서버의 응답 시간, CPU 및 메모리 사용량을 분석하여 성능 한계를 확인
  • 문제 탐색:
    • 특정 동시 요청 수를 초과할 때 성능 저하 발생 여부 확인
    • 공유 리소스가 과도하게 사용될 경우 데이터베이스 잠금이나 네트워크 병목이 발생하는지 점검

2. 스트레스 테스트

 

   스트레스 테스트는 시스템이 최대 한계를 넘는 부하에서 어떻게 반응하는지 확인하는 테스트. 즉, 애플리케이션이 감당할 수 없는 수준까지 트래픽을 증가시키면서 장애 발생 시점을 분석.

  • 테스트 방식:
    • 동시 사용자 수를 점진적으로 증가시키며 한계를 확인
    • 서버 응답 시간, 장애 발생률, 데이터베이스 연결 수 등을 모니터링
  • 문제 탐색:
    • 특정 TPS(초당 트랜잭션 수) 이상에서 서버가 다운되는지 확인
    • 오류 코드(HTTP 500, 502, 503) 발생 여부 점검

4. 로그 및 이벤트 트래이싱

문제가 발생할 것으로 예상되는 곳에 로그를 배치해두기.