현재 마감 임박 상품을 저렴하게 판매하는 서비스
(프로젝트 링크) 의 주문 파트
를 맡아 개발하고 있습니다. 그러던 중 동시에 여러 명의 고객이 한 상품을 주문할 때 갱신 손실
문제가 발생했고 이를 해결하기 전
테스트 코드와 Jmeter를 이용해 동시 주문 상황을 테스트하였습니다.
위에서 언급한 갱신 손실
문제의 해결 과정은 링크에서 확인 가능합니다
더 정확히 알아보기 위해 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);
}
여러 고객이 동시에 주문하는 상황을 가정하기 위해 Jmeter를 사용하였습니다.
간단히 요약하면
현재 재고가 1000개씩 있는 상품 1, 2에 대하여
100명의 고객이(= 스레드 100개)
상품 1,2를 각각 1개씩 동시에 구매하는 상황입니다.
모든 요청이 성공한 것 처럼 보였으나
재고가 100개가 아닌 72개, 70개만 줄어들었습니다.
전
후
테스트해 보고 싶은 내용은 다음과 같습니다.
현재 아래 캡쳐 이미지에는 '회원가입 후 주문', '회원가입'과 같은 이름을 임의로 설정해 놓은 상태입니다.
a. Test Plan 우클릭 > Add > Threads > Thread Group
추가
스레드 개수 등을 설정할 수 있습니다. (동시에 요청할 횟수)
b. Thread Group 우클릭 > Add > Listener > View Results Tree (Summary Report / Graph Results)
추가
결과를 보기 위해 사용됩니다.
API를 호출하여 Response Body로 Access Token을 받기
Thread Group 우클릭 > Add > Sampler > Http Request
추가
API 호출 시 필요한 정보 작성
providerId
는 Random 함수를 이용해 작성했습니다.(링크 참고)예를 들어, localhost:8080/api/orders
로 POST 요청을 보낸다면 다음과 같이 작성합니다. Request Body에 담을 데이터도 예시로 작성하였습니다. 추가로 헤더에 데이터를 담고자 한다면
HTTP Request > Add > Config Element > HTTP Header Manager
를 추가하여 헤더에 담을 데이터를 아래의 캡처본과 같이 작성합니다.
HTTP Request > Add > Post Processors > JSON Extractor
추가
제가 테스트하는 API의 response 는 nickname, accessToken, refreshToken으로 구성됩니다.
{
"nickName": "승원",
"accessToken": "eyJhbbGciOiJIUzI1.......",
"refreshToken": "eyJhbGciOiJIUzI1......."
}
이 중 accessToken을 추출하기 위해 다음과 같이 작성합니다.
주문 API 호출하면서 (1)에서 받은 access token을 헤더로 전달
위의 (1)-(a)와 마찬가지의 방법으로 API를 호출하기 위한 Http Request와 필요시 HTTP Header Manager를 추가합니다.
Thread Group 우클릭 > Add > Sampler > Http Request
추가
(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로 추출 시에 정한 변수명과 동일하게 입력해야 합니다.
Debug Sampler 를 통해 추출 후 저장이 제대로 되었는지 확인할 수 있습니다.
Thread Group 우클릭 > Add > Sampler > Debug Sampler 추가
후에 상단바의 Start
버튼을 눌러 실행 -> View Results Tree
왼쪽의 각 요청 중 Debug Sampler 클릭 -> Response data -> Response Body 클릭 시 추출한 데이터를 정상적으로 확인할 수 있습니다..
a. Test Plan 우클릭 > Add > Logic Controller > Loop Controller 추가
b. Loop Count : Loop Controller내의 작업을 반복할 횟수
c. Loop Controller 내로 (1),(2)에서 추가한 Http Request를 드래그하여 이동
상단 바의 실행 버튼을 누르면 실행할 수 있습니다.
테스트 결과는 링크와 같이 대시보드 형태로 생성할 수 있습니다.