내가 만든 사이트의 부하를 테스트 하는 법/[Gatling]

devkingsejong·2021년 5월 19일
0

부하분산

목록 보기
1/1
post-thumbnail

백앤드 개발자라면 누구나 높은 Traffic과 높은 가용성을 견디는 백앤드 프로그램을 개발하고 싶을 것입니다.
하지만......

열심히 개발한 나의 프로젝트의 접속자 수는 0.... ^^;

이러한 상황에서 내가 개발한 프로젝트가 설계대로 동작하는지 확인하는 방법은 없을까요?


> Gatling

물론 브라우저를 열고 F5를 연타한다던가, 스크립트로 간단하게 툴을 구현해 볼 수 있겠지만,
사실 "동시에" 최대한 많은 request를 전송할 수 있도록 구현하는 것은 품이 많이 드는 작업입니다.
Gatling은 동시에 최대한 많은 요청을 전송 할 수 있는 기술이 적용되어 있고, 또한 아래와 같이 Test 결과를 시각적으로 보여주는 기능 또한 존재하기 때문에 직접 구현하는 것에 비해 많은 장점이 있습니다.

(생략 ...)


게틀링 다운로드

게틀링 다운로드: https://gatling.io/open-source/start-testing/

Gatling을 다운로드하고 압축을 풀어보면 크게 아래의 3개의 폴더가 있는 것을 확인할 수 있습니다.

  • bin: 실행 파일 등이 모여 있습니다.
  • user-files/simulations: 시뮬레이션(테스트)할 코드들이 모여 있습니다.
  • results: 이곳에 시뮬레이션(테스트) 결과 파일들이 저장됩니다.

이제, 우리가 할 것은 scala로 작성된 시뮬레이션 코드를
user-files/simulations에 넣고 bin/gatling.sh를 통해 실행해 주기만 하면 됩니다.


간단한 한 개의 API 테스트

package test

import scala.concurrent.duration._
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._

class RecordedSimulation extends Simulation {

	val httpProtocol = http("testSimaulation")
		.get("http://test.com")
        	.header("Client-Version", "1")

	val scn = scenario("Scenario1")
		.exec(httpProtocol)

    setUp( 
        scn.inject(atOnceUsers(1000)) 
    )
}

위 코드는 test.com에 대한 GET 요청을 동시에 1,000명을 수행하는 코드의 예제입니다.

위 코드를 보면 알 수 있듯, 우리는 테스트를 위한 하나의 scenario를 작성하시면 되는 것이고,
scenario는 일반적으로 exec의 모임으로 이루어져 있습니다.

만약, 여러분의 서비스가 API1API2를 이어서 바로 호출해야만 작동하는 서비스라면,

class RecordedSimulation extends Simulation {

	val httpProtocol = http("testAPI1")
		.get("http://test.com")
        	.header("Client-Version", "1")

	val scn = scenario("Scenario1")
		
        .exec(httpProtocol)
        .pause(3)
        .exec(
        	http("testAPI2")
        		
                .post("http://test.com")
                .header("Client-Version", "1")
             )   
             (생략 ...)

위와 같이 구성해 준다면, 첫 번째 API가 호출된 뒤 3초가 지나고 두 번째 API(POST)를 호출하는 형태의 시나리오를 구성할 수 있습니다.


첫번째 API가 두번째 API에 영향을 주는 경우

만약, 모든 API가 독립적으로 동작한다면 위 섹션의 시나리오 내용을 확장해 가며 테스트를 구성하면 되겠지만, 실제 환경에서는 그렇지 않은 경우도 많습니다.

예를 들어, 기존에 있던 글에 댓글을 다는 테스트의 경우, 게시글의 ID가 고정되어 있을 것이기 때문에 정적으로 테스트 시나리오를 구성할 수도 있겠지만..
방금 내가 새로 작성한 게시글에 댓글을 다는 시나리오라면 동적으로 이를 받아와야 할 것입니다.

더 간단한 예제로는 로그인 후 (유저마다 다른) 세션을 받아 오는 경우도 있겠죠.

이럴 때 이용하는 것이 바로 Session 입니다.
Gatling에서 Session은 각 1개의 가상 유저가 사용하는 저장공간이라 볼 수 있습니다.
즉, atOnceUsers(1000)의 옵션으로 시나리오를 실행한다면, 1,000개의 Session이 만들어진다는 뜻입니다.

만약, 첫 API에서 반환하는 Body의 값이 다음 API에 영향을 준다면,
첫 번째 API가 호출될 당시 saveAs 등을 호출하여 해당 값을 Session에 저장한 뒤,
두 번째 API가 호출되는 순간 Session에서 이를 꺼내와 사용하면 됩니다.

그 예제는 아래와 같습니다.

class RecordedSimulation extends Simulation {

	val httpProtocol = 

	val scn = scenario("Scenario1")
		
        .exec(
        	http("testAPI1")
			.get("http://test.com/getYourNextURL")
        		.check(jsonPath("$..nextUrl").saveAs("nextUrl"))
        )
        .exec(
        	http("testAPI2")
                	.get(session => session("nextUrl"))
        )   
        (생략 ...)

코드를 작성하기 귀찮을 때?

만약 시나리오 코드를 작성하기 어렵거나, 시나리오가 길어질 것이라 예상된다면,
자동으로 패킷을 분석하여 코드로 만들어 주는 방법도 있습니다.

1. Recorder 실행

> ./recorder.sh

2. Proxy 관련 설정

Gatling Recorder에 Proxy 관련 포트를 설정해 준 뒤, 시나리오를 녹화할 브라우저에 이를 적용해 줍니다.

크롬:
설정 -> 시스템 컴퓨터 프록시 관리

파이어폭스:
설정 -> 네트워크 설정

3. 녹화 및 시뮬레이션 코드 확인

브라우저를 통해 직접 녹화 할 작업을 수행 한뒤 Save 버튼을 클릭합니다.
만약, 위 사진과 같은 시나리오가 녹화 되었다면 아래와 같은 코드를 확인할 수 있습니다.

val httpProtocol = http
		.baseUrl("https://www.naver.com")
		.inferHtmlResources()
		.acceptEncodingHeader("gzip, deflate")
		.acceptLanguageHeader("ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3")
		.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) 
        Gecko/20100101 Firefox/88.0")

	val headers_0 = Map(
		"Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;
        q=0.8",
		"Cache-Control" -> "max-age=0",
		"Upgrade-Insecure-Requests" -> "1")

	val headers_1 = Map(
		"Accept" -> "image/webp,*/*",
		"Cache-Control" -> "max-age=0")


	val scn = scenario("RecordedSimulation")
		.exec(http("request_0")
			.get("/")
			.headers(headers_0))
		.pause(1)
		.exec(http("request_1")
			.get("/favicon.ico?1")
			.headers(headers_1))
		.pause(15)

	setUp(scn.inject(atOnceUsers(1))).protocols(httpProtocol)

Gatling이 제공하는 기능은 정말 방대합니다.
이 외에도, query나 Body 등 다양한 데이터의 추가에 대해 알고 싶으면

https://gatling.io/docs/current/http/http_request/

위 링크를 참조해 주세요!

0개의 댓글