Contents
- 시작하기 : 서버 성능 테스트 이야기
- 전체 서비스 구조
- 성능 테스트 시나리오 설명
- K6 사용 이유와 K6의 테스트 단위
- 테스트 시나리오 설명
- 테스트 프로세스 설명
- 마치며
시작하기 : 서버 성능 테스트 이야기
서버 성능테스트 이야기는 현재 진행 중인 프로젝트 In-Bob-We-Trust의 성능 테스트, 분석, 개선 과정을 담아낸 기술 블로그입니다. 진행 도중 알게 되는 내용들, 취한 액션들을 일지 형식으로 최대한 읽기 쉽게 담아내 보겠습니다. 같은 주제를 다룬 여러 기술 블로그들을 참고해본 결과, 내용이 상당히 길어질 것을 예상할 수 있으니 이야기를 여러 에피소드로 나눠서 포스트 하겠습니다.
이번 이야기에서는 테스트와 관련된 Overview, 하이레벨 뷰를 설명합니다. 현시점 프로젝트 구조와 초기 서버 사양, 그리고 모니터링에 사용된 지표, 적용할 테스트 시나리오 등을 소개합니다.
이어지는 에피소드들은 테스트 실행-분석-개선 사이클을 계속하는 과정을 담아낼 예정입니다. 예상된 결과를 다루는 것이 아니기 때문에 다소 번잡해 보일 수 있는 점 양해 부탁드립니다.
시작하겠습니다.
전체 서비스 구조
현시점 프로젝트의 서버들이 동작하는 서비스 구조입니다. 프로젝트에 대한 하이레벨 뷰를 가짐으로써 테스트 프로세스를 더욱 쉽게 이해할 수 있을 것이라 생각되어 공유해봅니다. 프로젝트의 주제는 배달의민족 중계 서비스로 고객님의 주문 결제시점부터 배달완료까지의 서비스를 담당합니다. 이번 성능 테스트 대상은 좌측 상단의 Delivery-Info-Service(배달정보서비스)입니다. 배달정보서비스는 주문 시점부터 배달완료까지 주문의 상태를 조회하고 기록하는 기능을 담당합니다.
성능 테스트 시나리오 설명
이번 성능 테스트의 시나리오는 저희 서비스의 핵심 기능인 주문 및 배달 현황을 기록하는 하나의 유저 플로우입니다. 유저 플로우를 테스트 단위로 설정함으로써 성능 테스트를 최대한 실제상황과 유사하게 묘사했습니다.
- 배민 서비스에서 신규 주문을 전송한다. POST /api/delivery
- 사장님이 주문을 접수한다. PUT /api/delivery/accept
- 배달 대행사에서 라이더를 배정한다. PUT /api/delivery/rider
- 라이더님이 주문을 픽업한다. PUT /api/delivery/pickup
- 라이더님이 배달을 완료한다. PUT /api/delivery/pickup
K6 사용 이유와 K6의 테스트 단위
K6 사용 이유
- Test as Code : 테스트 프레임워크 선정에 중요한 부분으로 UI 보다는 스크립트 가능 여부를 고려했습니다. 스크립트는 반복적인 테스트를 작성하거나 버전 관리하기에 용이합니다. 필요시 자동화도 편리하게 할 수 있습니다. K6는 테스트의 모든 부분을 코드로 작성 가능합니다.
- Ease-of-use : K6는 팀원이 익숙하며 러닝 커브가 낮은 자바스크립트로 작성합니다.
- Lightweight : K6는 가볍습니다. 테스트 툴들의 가상 유저는 보통 스레드와 1대 1 매핑시킵니다. NGrinder, Jmeter 등 자바 툴들은 스레드 별 최소 1MB의 메모리가 발생하지만 K6 는 Go 로 작성되어있으며 스레드별 100kb를 넘지 않습니다.. 참고자료
K6 테스트 단위
- 아래 자바스크립트 코드는 하나의 시나리오입니다. K6 에서는 iteration
- Iteration 은 K6의 한 명의 가상 유저가 시나리오를 실행하는 단위입니다.
- 부하 테스트 진행할 때 다수의 가상 유저를 생성해서 시나리오를 동시다발적으로 실행하게 됩니다.
테스트 시나리오 설명
테스트 시나리오의 키포인트를 공유합니다.
현실의 이벤트 발생과정을 반영하려면?
- 실제 신규 주문 접수 요청이 들어오면 사장님과 사장님 애플리케이션은 몇 초의 텀을 가진 이후 주문 접수 요청을 전송합니다. 라이더님과 배달 대행사의 경우도 다르지 않습니다. 이를 어느 정도 반영하기 위해 각 요청 사이사이에 sleep(1) 메서드로 납득이 갈만한 시간 동안 스레드를 SLEEP 상태로 두었습니다.
- 1초라는 시간은 컴퓨터에게 충분한 기다림이며 테스트 부하를 조작하기 용이합니다.
테스트 시나리오의 여러 단계의 결과를 분리하려면?
- check(category, callback()) 메서드는 각 요청의 성공 여부를 테스트 성공-실패 여부에 반영되게 하는 기능입니다.
- K6의 기본적인 http_req_failed라는 Threshold가 존재하지만 이번 시나리오는 여러 요청들이 섞여있기 때문에 명시적으로 분리해줌으로써 테스트 결과를 아래와 같이 받아볼 수 있습니다. (예시 이미지)
아래는 K6를 활용해서 작성한 시나리오입니다.
export default () => {
const uri = "<baseUrl>:8888";
const URI = uri + "/api/delivery"
// 신규주문
const req_addDelivery = makeNewDelivery();
req_addDelivery['riderId'] = null;
const addDelivery = http.post(URI, JSON.stringify(req_addDelivery), params);
check(addDelivery, {
'addDelivery is OK 200': () => {
if (addDelivery.status !== 200) {
console.info('addDelivery result >>> ' + addDelivery.body);
}
return addDelivery.status === 200;
}
});
sleep(1);
const SAVED_DELIVERY = JSON.parse(addDelivery.body);
const DELIVRY_ID = SAVED_DELIVERY.id;
// 주문접수
const req_acceptDelivery = makeDelivery(DELIVRY_ID, 'ACCEPTED');
req_acceptDelivery['riderId'] = null;
req_acceptDelivery['orderTime'] = new Date().toISOString();
sleep(0.01);
req_acceptDelivery['pickupTime'] = new Date().toISOString();
const acceptDelivery = http.put(URI + "/accept", JSON.stringify(req_acceptDelivery), params);
check(acceptDelivery, {
'acceptDelivery is OK 200': () => {
if (acceptDelivery.status !== 200) {
console.info('acceptDelivery result >>> ' + acceptDelivery.body);
}
return acceptDelivery.status === 200;
}
});
sleep(1);
// 라이더 배정
const req_setDeliveryRider = makeDelivery(DELIVRY_ID, 'ACCEPTED');
const setDeliveryRider = http.put(URI + "/rider", JSON.stringify(req_setDeliveryRider), params);
check(setDeliveryRider, {
'setDeliveryRider is OK 200': () => {
if (setDeliveryRider.status !== 200) {
console.info('setDeliveryRider result >>> ' + setDeliveryRider.body);
}
return setDeliveryRider.status === 200;
}
});
sleep(1);
// 픽업완료
const req_setPickedUp = makeDelivery(DELIVRY_ID, 'PICKED_UP');
req_setPickedUp['deliveryStatus'] = 'PICKED_UP';
const setPickedUp = http.put(URI + "/pickup", JSON.stringify(req_setPickedUp), params);
check(setPickedUp, {
'setPickedUp is OK 200': () => {
if (setPickedUp.status !== 200) {
console.info('setPickedUp result >>> ' + setPickedUp.body);
}
return setPickedUp.status === 200;
}
});
sleep(1);
// 배달완료
const req_setComplete = makeDelivery(DELIVRY_ID, 'COMPLETE');
const setComplete = http.put(URI + "/complete", JSON.stringify(req_setComplete), params);
check(setComplete, {
'setComplete is OK 200': () => {
if (setComplete.status !== 200) {
console.info('setComplete result >>> ' + setComplete.body);
}
return setComplete.status === 200;
}
});
sleep(1);
};
테스트 프로세스 설명
중요 포인트
- 앞으로 성능 테스트 이야기들은 1. 성능테스트 실행 > 2. 테스트 결과 분석 > 3. 성능 개선 싸이클로 진행됩니다.
- 대부분의 서비스들은 AWS EC2 인스턴스에서 실행했습니다.
- 모니터링 & 시각화 툴인 Grafana는 Grafana Cloud를 활용해서 별도 관리가 필요 없습니다.
- 서비스 사양은 테스트마다 달라질 수 있으니 테스트 진행하는 부분에서 다루겠습니다.
마치며
지금까지 이번 성능 테스트의 하이레벨 뷰를 소개했습니다. 다음 편부터는 실제로 테스트를 실행하고 분석하는 과정을 기록해나갈 예정입니다.
'in-bob-we-trust' 카테고리의 다른 글
서버 성능테스트 이야기 3 [ 성능개선 : JVM 메모리 영역 확장 ] (0) | 2022.01.28 |
---|---|
서버 성능테스트 이야기 2 [ 첫 테스트 ] (0) | 2022.01.28 |
프로젝트 비용 최적화를 위한 Reactive + MongoDB Atlas Serverless 적용 및 예시 (Spring Webflux) (0) | 2022.01.25 |
[Reactive한 라이더위치 기능구현 ] 요구사항 분석부터 위치정보 저장 기능 구현까지 (0) | 2022.01.13 |
Github 프로젝트 & Intellij 전반에 걸쳐 Google Java Style Guide 를 강제하기 (0) | 2022.01.09 |