[Web] Gatling 부하 테스트 도구 (Scala)
업데이트:
1. 배경
자신이 만든 서비스가 몇 명의 사용자를 수용 할 수 있는지 알고 싶지 않나요?
그리고 동일한 인프라 자원으로 1000명의 사용자를 받을 수 있는데,
100명의 사용자 밖에 못 받는다면 얼마나 억울할까요 😭
그렇기 때문에 시간이 오래 걸리는 API가 있다면 찾아서 개선이 필요합니다.
- 잘못된 코드 하나가 서비스에 영향을 주고 있지는 않은지
- 특정 쿼리의 시간이 오래 걸리고 있지는 않은지
- 비동기로 처리해야하는 로직은 없는지
그런데 그 전에 첫 번째 고민인 몇 명의 사용자를 수용 할 수 있는지에 대한
궁금증을 해결해봅시다~!
Gatling 이란?
오픈 소스 부하 테스트 프레임워크입니다.
(부하 테스트 툴은 Gatling 말고도 Ngrinder, Locust, JMeter 등이 있습니다)
Gatling을 사용하면 아래에 대한 질문에 답을 할 수 있게 됩니다~!
- 어느 정도의 성능을 내나요?
- 어디에서 부하가 걸리나요?
그리고 단일 API호출에 대한 테스트뿐만 아니라
실제 사용자의 행동과 유사하게 테스트 시나리오를 만들어 테스트할 수도 있습니다.
성능 지표
- TPS : Transaction Per Second의 약자로서 초당 트랜잭션의 개수
용어
- Scenario : http request의 조합이며, 시나리오는 각각의 injection 을 가집니다.
- Injection : 가상 사용자를 어떤 조건으로 주입할지 결정합니다.
- Protocol : http 동작에서 사용자가 각각의 연결 풀을 가질지 등을 결정합니다.
이해를 돕기 위해 시나리오 의 예를 들면,
공고 상세 페이지를 그리기 위해서 ‘공고API’ 와 ‘댓글API’ 가 필요하다면,
‘공고API + 댓글API’ 가 하나의 시나리오가 됩니다.
여러 API의 조합으로 시나리오를 간단하게 만들어보고, 지금부터 부하테스트를 해보겠습니다.
2. 사용법
1. gatling demo 저장소 클론
먼저 gatling 깃헙 저장소에서 데모를 오픈소스로 받겠습니다.
git clone https://github.com/gatling/gatling-maven-plugin-demo-scala
2. Scala Plugin 설치 및 sdk 추가
그리고 Intellij에서 scala를 세팅해주겠습니다.
1) Preferences > Plugins 에서 ‘Scala’ 플러그인을 설치합니다.
2) Project Structure > Global Libraries 에서 scala-sdk-2.13을 설치합니다.
3. 코드 작성
저는 🗂src > 🗂scala > 🗂home 에서 WebGetSimulation.scala 파일을 생성하겠습니다.
예제로는 메인 화면이 카테고리와 공고 이렇게 2개의 API가 필요하다고 할 때,
메인 화면(Main Page)에 대한 시나리오를 만들어 보겠습니다.
먼저 API가 다음과 같다고 합시다.
1) 카테고리 : http://localhost:9000/v2/category/default
2) 공고 : http://localhost:9000/v2/posts
그리고 위의 URL에 대한 GET 요청이 request timeout 기준을 10초일 때
status 200이 떨어지는지 확인하겠습니다.
그리고 tps를 30초 동안 1에서 200까지 올려보고, 다시 5초 동안 200에서 1로 내려봅니다.
직접 코드로 확인해봅시다.
전체 코드는 다음과 같습니다.
WebGetSimulation.scala
package home
import io.gatling.core.Predef._
import io.gatling.core.structure.{ChainBuilder, ScenarioBuilder}
import io.gatling.http.Predef._
import scala.concurrent.duration.DurationInt
class WebGetSimulation extends Simulation {
val category: ChainBuilder = exec(
http("Category")
.get("http://localhost:9000/v2/category/default")
.requestTimeout(10.seconds)
.check(status.is(200))
)
val postAll: ChainBuilder = exec(
http("Post")
.get("http://localhost:9000/v2/posts")
.requestTimeout(10.seconds)
.check(status.is(200))
)
val myScenario: ScenarioBuilder = scenario("Main Page")
.exec(category, postAll)
private val tps = 200
setUp(
myScenario.inject(
rampUsersPerSec(1) to tps during (30.seconds),
rampUsersPerSec(tps) to (1) during (5.seconds)
).protocols(http.shareConnections.header("Content-Type", "application/json"))
)
}
4. 실행
Intellij에서 Engine 파일을 시작해주고,
터미널에서 home에 해당하는 1번을 선택해서 엔터를 눌러주면 됩니다.
3. 결과 확인
터미널에 보이는 경로에서 파일 형태로 결과지를 받을 수 있습니다.
1. Request 통계
약 30초 동안 총 7034개의 request가 실행되었고, 그 중 2%가 실패하게 되었습니다.
그런데 메인 화면에서 카테고리API와 공고API가 동시에 실행되기 때문에
30초 동안 3500번의 메인 화면 진입을 아슬아슬하게 받아낸다고 판단할 수 있습니다.
2. Active User & 응답 시간 확인
request가 많아지면서 가상 사용자의 수의 max 값은 1361명으로 집계되었습니다.
응답을 받은 사용자가 빠져나가더라도 갑자기 사용자가 몰린다면
응답 시간이 6초를 초과하고 있으며,
쌓인 요청 때문에 timeout이 발생하는 시점도 확인할 수 있습니다.
이제 결과를 통해 나아갈 방향을 잡게 됩니다.
성능 테스트는 ‘서비스 안정성’ 을 위한 첫 걸음일 뿐입니다.
자신의 서비스가 몇 명까지 수용할 것인지에 따라 개선 여부를 결정해야 합니다.
4. 개선
잘못된 코드가 있다거나 쿼리 자체가 오래 걸리는 문제는 아니었기에
위의 서비스에 Redis 캐시를 사용해본 결과를 확인해보겠습니다.
캐시를 적용하고 tps를 200에서 1000으로 높여보고 테스트 해보겠습니다.
1. Request 통계
약 30초 동안 기존보다 5배가 넘는 총 35034개의 request가 실행되었고,
실패율은 0%가 되었습니다.
2. Active User & 응답 시간 확인
그리고 가상 사용자의 수가 4149명까지 올라갔지만
최대 3초 정도 안에 응답을 했습니다.
이번 성능 개선은 캐싱으로 DB 접근을 최대한 줄이는 것에 있었네요~!
몇 명의 사용자가 사용할 수 있는 서비스인지에 대한 궁금증을 해결하려고 시작했는데
얻어 가는게 많았던 정리였습니다.
동일한(주어진) 인프라 자원 속에서 서비스의 성능을 높일 수 있는 스킬을 늘려 보아요~!
내가 만든 서비스의 사용자 경험이 더 좋아지도록 🚀🔫🔚