동시성 문제 테스트(테스트코드&Jmeter)

seungwon·2023년 11월 9일
1
post-thumbnail
post-custom-banner

개요

현재 마감 임박 상품을 저렴하게 판매하는 서비스(프로젝트 링크) 의 주문 파트를 맡아 개발하고 있습니다. 그러던 중 동시에 여러 명의 고객이 한 상품을 주문할 때 갱신 손실문제가 발생했고 이를 해결하기 전

테스트 코드와 Jmeter를 이용해 동시 주문 상황을 테스트하였습니다.

위에서 언급한 갱신 손실 문제의 해결 과정은 링크에서 확인 가능합니다

1. 테스트 코드

더 정확히 알아보기 위해 JAVA 라이브러리인 ExecutorService 와 CountDownLatch 를 이용해 간단한 테스트 코드를 작성했습니다.

@Test
public void 동시주문_테스트() throws InterruptedException {
    int threadCount = 100;

ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

    CountDownLatch countDownLatch = new CountDownLatch(threadCount);

    for (int i = 0; i < threadCount; i++) {
        executorService.submit(() -> {
                try {
                    // 재고 감소 코드
                    orderService.create(1);
                }
                finally {
                    latch.countDown();
                }
            }
        );
    }

    latch.await();

    Stock stock = stockRepository.findById(1L).orElseThrow();

    assertThat(stock.getQuantity()).isEqualTo(0L);

}

2. Jmeter

여러 고객이 동시에 주문하는 상황을 가정하기 위해 Jmeter를 사용하였습니다.

간단히 요약하면

  1. 현재 재고가 1000개씩 있는 상품 1, 2에 대하여

  2. 100명의 고객이(= 스레드 100개)

  3. 상품 1,2를 각각 1개씩 동시에 구매하는 상황입니다.

  4. 모든 요청이 성공한 것 처럼 보였으나

  5. 재고가 100개가 아닌 72개, 70개만 줄어들었습니다.

  • 100개 동시 주문 테스트
  • 100개 동시 주문 테스트



2-1. Jmeter로 시나리오를 작성하는 자세한 과정

테스트해 보고 싶은 내용은 다음과 같습니다.

  1. 회원가입 : API를 호출하여 Response Body로 Access Token을 받기
  2. 주문 : 주문 생성 API 호출하면서 1에서 받은 access token을 헤더로 전달
  3. "1번 과정 -> 2번 과정"을 차례로 진행

현재 아래 캡쳐 이미지에는 '회원가입 후 주문', '회원가입'과 같은 이름을 임의로 설정해 놓은 상태입니다.


(0) 초기화면

a. Test Plan 우클릭 > Add > Threads > Thread Group 추가

스레드 개수 등을 설정할 수 있습니다. (동시에 요청할 횟수)

b. Thread Group 우클릭 > Add > Listener > View Results Tree (Summary Report / Graph Results) 추가

결과를 보기 위해 사용됩니다.

  • View Results Tree : 각 요청의 결과
  • Summary Report : 요청 전체의 요약 (성공/실패 건수, Throughput등)
  • Graph Results : 전체 결과를 그래프를 통해 확인 가능

(1) 회원가입

API를 호출하여 Response Body로 Access Token을 받기

a. 회원가입 API를 호출하기

Thread Group 우클릭 > Add > Sampler > Http Request 추가

API 호출 시 필요한 정보 작성

  • providerId는 Random 함수를 이용해 작성했습니다.(링크 참고)

예를 들어, localhost:8080/api/orders로 POST 요청을 보낸다면 다음과 같이 작성합니다. Request Body에 담을 데이터도 예시로 작성하였습니다. 추가로 헤더에 데이터를 담고자 한다면

HTTP Request > Add > Config Element > HTTP Header Manager를 추가하여 헤더에 담을 데이터를 아래의 캡처본과 같이 작성합니다.

b. 회원가입 API를 호출하여 Response Body로 받은 Access Token을 추출하기

HTTP Request > Add > Post Processors > JSON Extractor추가

제가 테스트하는 API의 response 는 nickname, accessToken, refreshToken으로 구성됩니다.

{
  "nickName": "승원",
  "accessToken": "eyJhbbGciOiJIUzI1.......",
  "refreshToken": "eyJhbGciOiJIUzI1......."
}

이 중 accessToken을 추출하기 위해 다음과 같이 작성합니다.

  • Names of created variables: 추출한 데이터를 저장할 변수명 (이후에 이 이름으로 해당 데이터를 사용합니다.)
  • JSON Path expressions : 해당 데이터의 json 경로
  • Match No. (0 for Random) : 매칭될 개수
  • Default Values : 해당 값이 존재하지 않는다면 할당해 줄 값 (ex. NOT FOUND)

(2) 주문

주문 API 호출하면서 (1)에서 받은 access token을 헤더로 전달

a. HTTP Request, HTTP Header Manager 추가

위의 (1)-(a)와 마찬가지의 방법으로 API를 호출하기 위한 Http Request와 필요시 HTTP Header Manager를 추가합니다.

Thread Group 우클릭 > Add > Sampler > Http Request 추가


b. BeanShell PreProcessor 추가

(1)에서 받은 access token을 헤더로 전달하기 위해 BeanShell PreProcessor를 추가합니다.


Http Request 우클릭 > Add > Pre Processors > BeanShell PreProcessor 추가

*Post Processors > BeanShell PostProcessor 와 혼동하지 않도록 유의

script 내에 다음과 같이 작성합니다.

import org.apache.jmeter.protocol.http.control.Header;

sampler.getHeaderManager().add(new Header ("Authorization", "Bearer" + vars. get ("accessToken")));

Authorization header에
"Bearer " 문자열과
위의 (1)-b 에서 JSON Extractor로 추출한 accessToken을 이어 붙여 추가한다는 의미입니다.
ex) Bearer avcdafdvaesrwreva

accessToken과 같이 위에서 Json Extractor로 추출 시에 정한 변수명과 동일하게 입력해야 합니다.


c. Debug Sampler

Debug Sampler 를 통해 추출 후 저장이 제대로 되었는지 확인할 수 있습니다.

Thread Group 우클릭 > Add > Sampler > Debug Sampler 추가 후에 상단바의 Start버튼을 눌러 실행 -> View Results Tree

왼쪽의 각 요청 중 Debug Sampler 클릭 -> Response data -> Response Body 클릭 시 추출한 데이터를 정상적으로 확인할 수 있습니다..

(3) "1번 과정 -> 2번 과정" 순으로 진행

a. Test Plan 우클릭 > Add > Logic Controller > Loop Controller 추가

b. Loop Count : Loop Controller내의 작업을 반복할 횟수

c. Loop Controller 내로 (1),(2)에서 추가한 Http Request를 드래그하여 이동

상단 바의 실행 버튼을 누르면 실행할 수 있습니다.


그 외

CLI 실행

GUI로 진행하는 방법만을 서술해 놨는데 GUI로 설정한 환경을 콘솔 명령어를 통해서도 간단히 실행할 수 있습니다(무거운 테스트의 경우 cli로 실행하는 것을 추천합니다).

결과 추출

테스트 결과는 링크와 같이 대시보드 형태로 생성할 수 있습니다.


참고 : https://jmeter.apache.org/usermanual/index.html

post-custom-banner

0개의 댓글