Artillery로 서버 부하테스트하기

post-thumbnail

개요

최근 사내 서비스 유저가 점점 늘어나고 있습니다.

또한 대량 영업으로 한꺼번에 지점이 많아질 계획입니다.

서버가 버틸 수 있을까? 라는 의문이 들기 시작합니다.

따라서, 가상 유저를 만들어 가입하고 사용해보는 시나리오대로 진행시

서버가 버틸 수 있는지 테스트를 해보고자 합니다.

Artillery

Artillery 공식문서

저희 팀이 선택한 툴은 Artillery 입니다.

Node서버에서 사용할 수 있는 Artillery는

간편하게 사용할 수 있는 기능과 유연성을 제공하여 개발자가 어플리케이션을 효과적으로 테스트할 수 있는 오픈 소스 라이브러리 입니다.

원했던 대로 가상유저, 시나리오, 리포트 페이지 등의 기능을 제공해 줍니다.

설치하기

npm install -g artillery

npm으로 전역 설치를 해줍니다.

이때 주의할 점은 artillery는 최신 노드버전이 필요합니다.
에러가 뜬다면 nvm를 이용해 최신 노드버전을 설치하시면 됩니다.

  • 회사에서는 더욱 간편하게 사용하기 위해 repository로 만들어 dev dependency
    로 설치하였습니다.

시나리오 작성

앞서 말했듯이 Artillery는 시나리오 테스트를 지원합니다.

테스트 작성은 Json, yaml 파일 형식을 사용할 수 있는데요,

공식문서에는 yaml로 가이드가 나와있지만.. json이 더 친숙하고 편할 것 같아서
저는 json파일 형식을 사용했습니다.

설정

"config": {
  "target": "http://localhost:3001",
  "phases": [{ "duration": 10, "arrivalRate": 2 }],
  "defaults": {
    "headers": {
      "User-Agent": "Artillery"
    }
  },
  "variables": { "pin": "1111", "cardData": "12341234" },
  "processor": "./functions.js"
},

target : 테스트할 서버 주소
phases : 테스트할 요청시간과 비율을 정합니다.

  • duration : 테스트 지속 시간
  • arrivalRate : 가상의 유저 수
    defaults : 시나리오 기본 값
    payload : 임의의 데이터를 보내기 위해서 사용(CSV를 읽어서 변수에 지정할 수 있습니다)
    variables : 변수 지정하기
    processor : 커스텀 JS코드 불러오기
function printLog(requestParams, response, context, ee, next) {
	const { url, method } = requestParams;
	const { statusCode, body } = response;
	console.log(`[${method}] [${statusCode}] ${url}`);
	return next();
}

module.exports = {
	printLog,
};

커스텀 JS파일을 작성해서 진행상황 로그를 불러올 수 있습니다.

플로우 작성

name : 시나리오 이름
flow : 시나리오에서 진행하는 테스트 동작을 순서대로 작성
capture: 응답으로 받은 데이터에서 다시 변수로 지정해서 뒤에 보내는 요청에 사용
match : 응답 데이터가 원하는 값이 오는지를 확인할 수 있다.
weight : 시나리오에대한 가중치, 이 값이 높으면 해당 시나리오는 더 많이 발생

예시 )


"scenarios": [
  {
    "flow": [
      {
        "post": {
          "url": "/auth/kiosk/signUp",
          "json": {
            "hasAgreedMarketing": true,
            "hasAgreedTerms": true,
            "phone": "{{$randomNumber(10000000000,99999999999)}}",
            "pin": null,
          },
          "capture": { "json": "$.result.jwtInfo.accessToken", "as": "accessToken" }
        }
      },
      {
        "get": {
          "url": "/users/me",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "capture": [
            { "json": "$.authCode", "as": "authCode" },
            { "json": "$.id", "as": "userId" },
            { "json": "$.phone", "as": "phone" }
          ],
          "afterResponse": "printLog"
        }
      },
      {
        "post": {
          "url": "/users/me/verify-code",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "json": {
            "phone": "{{phone}}",
            "authCode": "{{authCode}}"
          },
          "afterResponse": "printLog"
        }
      },
      {
        "post": {
          "url": "/users/me/set-pin",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "json": {
            "pin": "{{pin}}"
          },
          "afterResponse": "printLog"
        }
      },
      {
        "post": {
          "url": "/users/me/deposit",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "json": {
            "money": 10000,
            "payMethod": "CARD",
            "storeId": "1"
          },
          "afterResponse": "printLog"
        }
      },
      {
        "post": {
          "url": "/users/me/usages/v2",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "json": {
            "couponId": 4,
            "funnel": "KIOSK",
            "money": 10000,
            "storeDeviceId": 1
          },
          "afterResponse": "printLog"
        }
      },
      {
        "get": {
          "url": "/users/me/deposit/store/1",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "capture": {
            "json": "$.depositBalance",
            "as": "depositBalance"
          },
          "afterResponse": "printLog"
        }
      },
      {
        "post": {
          "url": "/users/me/usages/v2",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "json": {
            "funnel": "KIOSK",
            "money": "{{depositBalance}}",
            "storeDeviceId": 1
          },
          "afterResponse": "printLog"
        }
      },
      {
        "patch": {
          "url": "/subscriptions/cancel-subscription/{{subscriptionId}}",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "afterResponse": "printLog"
        }
      },
      {
        "delete": {
          "url": "/users/me/user/{{userId}}",
          "headers": { "Authorization": "Bearer {{accessToken}}" },
          "afterResponse": "printLog"
        }
      }
    ]
  }
]

테스트 시작

artillery run --output report.json main.json

main.json에 대한 시나리오를 시작하며,
report.json이라는 리포트파일을 생성하게 됩니다.

artillery report report.json

해당 커맨드로 report를 html로 변환할 수 있습니다

마치며

Artillery를 이용한 시나리오 작성을 해보았습니다.

문법이랄 것도 없이 json파일로 작성하는 것이다보니 러닝커브가 높지 않다는 느낌을 받았습니다.

다음 계획은 staging 서버를 만들어서 부하테스트를 실제로 해보는 것입니다.

0개의 댓글