백앤드 개발자라면 누구나 높은 Traffic과 높은 가용성을 견디는 백앤드 프로그램을 개발하고 싶을 것입니다.
하지만......
열심히 개발한 나의 프로젝트의 접속자 수는 0.... ^^;
이러한 상황에서 내가 개발한 프로젝트가 설계대로 동작하는지 확인하는 방법은 없을까요?
> Gatling
물론 브라우저를 열고 F5를 연타한다던가, 스크립트로 간단하게 툴을 구현해 볼 수 있겠지만,
사실 "동시에" 최대한 많은 request를 전송할 수 있도록 구현하는 것은 품이 많이 드는 작업입니다.
Gatling은 동시에 최대한 많은 요청을 전송 할 수 있는 기술이 적용되어 있고, 또한 아래와 같이 Test 결과를 시각적으로 보여주는 기능 또한 존재하기 때문에 직접 구현하는 것에 비해 많은 장점이 있습니다.
(생략 ...)
게틀링 다운로드: https://gatling.io/open-source/start-testing/
Gatling을 다운로드하고 압축을 풀어보면 크게 아래의 3개의 폴더가 있는 것을 확인할 수 있습니다.
이제, 우리가 할 것은 scala로 작성된 시뮬레이션 코드를
user-files/simulations
에 넣고 bin/gatling.sh
를 통해 실행해 주기만 하면 됩니다.
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의 모임으로 이루어져 있습니다.
만약, 여러분의 서비스가 API1
과 API2
를 이어서 바로 호출해야만 작동하는 서비스라면,
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가 독립적으로 동작한다면 위 섹션의 시나리오 내용을 확장해 가며 테스트를 구성하면 되겠지만, 실제 환경에서는 그렇지 않은 경우도 많습니다.
예를 들어, 기존에 있던 글에 댓글을 다는 테스트
의 경우, 게시글의 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/
위 링크를 참조해 주세요!