nGrinder를 활용한 성능 테스트

dragonappear·2022년 8월 13일
0

Performance

목록 보기
3/4

출처

제목: "내가 만든 서비스는 얼마나 많은 사용자가 이용할 수 있을까? - 2편(nGrinder를 활용한 성능테스트)"
작성자: tistory(Hooligans)
작성자 수정일: 2020년12월27일 
링크: https://hyuntaeknote.tistory.com/11
작성일: 2022년8월13일

이번에는 서비스의 성능 지표를 확인하기 위해서 부하를 발생시켜보자

nGrinder란?

  • nGrinder란 네이버에서 The Grinder 라는 성능 테스트 도구를 기반으로 제작한 오픈소스 성능 테스트 솔루션이다.

  • 스크립트 생성과 테스트 실행, 모니터링 및 결과 보고서 생성을 통합된 Web UI를 통해 사용할 수 있으므로 성능 테스트를 보다 쉽게 할 수 있다.

  • 추가적으로 단순히 부하만을 발생시키는 것이 아니라, Groovy,Jython 스크립트를 통해서 다양한 시나리오를 테스트할 수 있다는 점도 큰 장점이다.

    • 무엇보다도 오픈소스 솔루션이기 때문에 무료로 사용할 수 있다.

테스트 환경 구성

nGrinder를 통해 성능 테스트를 하기 위해서는 위 그림과 같이 Controller,Agent,Target Server가 각각 별도의 서버에 구성되어있어야 한다.

  • Controller

    • 테스트 스크립트를 작성하고, Agent에 부하 발생 명령을 해서 테스트를 시작할 수 있도록 하는 웹 어플리케이션 서버이다.
    • 추가적으로 성능 테스트를 위해서 UI를 제공하고 테스트 실행 절차를 설정할 수 있돌고 하며, 실행 중인 테스트를 모니터링하거나, 테스트 결과를 수집해서 시각화해주는 역할을 한다.
  • Agent

    • 실질적으로 부하를 발생시키는 주체로 프로세스와 스레드 수를 조정하며 vUser(가상 사용자)를 생성한다
    • 통상 vUser 수 = 프로세스 수 X 스레드 수로 계산한다.
    • vUser는 컨트롤러에서 실행한 테스트 스크립트에 따라 동작하여 Target Server에 부하를 발생시킨다.
  • Target Server

    • 테스트하고자 하는 대상 서버를 의미하며, 테스트 중 타겟 서버에서 발생한 오류들 혹은 실시간 CPU,Memory 상태 등 조금 더 자세한 정보를 확인하고 싶다면 Target nGrinder Monitor를 설치하면 확인할 수 있다.

위의 세가지 요소를 각각 다른 서버에 설치하는 이유는 컨트롤러,Agent 모두 부하를 발생하고 모니터링하는데 있어서 서버의 자원을 사용하기 때문이다.

만약 세 가지 요소가 하나의 서버에 존재한다면 서버는 자원을 나눠서 사용해야 하고, 그만큼 Context Switching이 발생하는 등 테스트에 있어서 불필요한 노이즈가 발생하게 되기 때문에 순수한 Target 서버의 성능을 도출하기 어려워진다.

그러므로 각각 서버를 분리하여 설치해야만 하고, 필요에 따라 vUser 수를 늘리기 위해서 Agent 서버를 추가적으로 설치할 수 있다.


nGrinder 설치

https://github.com/naver/ngrinder/wiki/Installation-Guide
https://hub.docker.com/r/ngrinder/controller/
https://opentutorials.org/module/351/3338
https://velog.io/@rudwnd33/nGrinder%EB%A1%9C-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%B4%EB%B3%B4%EA%B8%B0

Controller

$ docker pull ngrinder/controller
docker run -d -v ~/ngrinder-controller:/opt/ngrinder-controller --name controller -p 80:80 -p 16001:16001 -p 12000-12009:12000-12009 ngrinder/controller
  • 80: Default controller web UI port.
  • 9010-9019: agents connect to the controller cluster thorugh these ports.
  • 12000-12029: controllers allocate stress tests through these ports.

Agent

$ docker pull ngrinder/agent
docker run -d --name agent --link controller:controller ngrinder/agent

성능 테스트

지금부터 서비스의 단위 성능 테스트를 해보자

위와 같이 테스트에 필요한 Controller, Agent, Target Server 가 기본적으로 구성되어 있고, Target 서버 앞에는 이후에 Scale-Out 시에 사용할 로드밸런서를 미리 구성했다.

단일 서버를 사용하는 중에도 로드 밸런서를 두고 테스트하는 이유는 테스트 대상을 고립시키기 위해서입니다. Scale-out을 하려면 로드 밸런서가 필요하게 되고, 로드 밸런서와 서버를 동시에 추가하게 된다면 어떤 대상에 의해서 성능이 증가하고 감소했는지 정확하게 확인하기 어렵기 때문이다.

이처럼 단일 서버 구성에도 로드밸런서를 추가함으로써 Scale-out 시에도 순수하게 서버가 늘어났을 때 증가하는 성능에 대해서 측정을 할 수 있다.

테스트 스크립트는 nGrinder 개발자분이 작성하신 'Groovy 스크립트 구조' 포스팅을 참고하여 작성하자.

import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.plugin.http.HTTPRequest
import net.grinder.plugin.http.HTTPPluginControl
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import net.grinder.scriptengine.groovy.junit.annotation.AfterThread
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

import java.util.Date
import java.util.List
import java.util.ArrayList

import HTTPClient.Cookie
import HTTPClient.CookieModule
import HTTPClient.HTTPResponse
import HTTPClient.NVPair

import groovy.json.JsonOutput

@RunWith(GrinderRunner)
class TestRunner {

	public static GTest test
	public static HTTPRequest request
	public static NVPair[] headers = []
	public static Cookie[] cookies = []

	@BeforeProcess
	public static void beforeProcess() {
		HTTPPluginControl.getConnectionDefaults().timeout = 6000
		test = new GTest(1, "123.123.123.123")//Target IP
		request = new HTTPRequest()
        
		List<NVPair> headerList = new ArrayList<>()
		headerList.add(new NVPair("Content-Type", "application/json"))
		headers = headerList.toArray()
	}

	@BeforeThread 
	public void beforeThread() {
		test.record(request)
		grinder.statistics.delayReports=true;
		
        def threadContext = HTTPPluginControl.getThreadHTTPClientContext()
        cookies = CookieModule.listAllCookies(threadContext)
        cookies.each {
            CookieModule.removeCookie(it, threadContext)
        }
		
		def loginBody = JsonOutput.toJson([userId: 'testuser1', password: 'password'])
		HTTPResponse res = request.POST("http://123.123.123.123/users/login", loginBody.getBytes(), headers);
		cookies = CookieModule.listAllCookies(threadContext)
	}
	
	@Before
	public void before() {
		request.setHeaders(headers)
		cookies.each { CookieModule.addCookie(it, HTTPPluginControl.getThreadHTTPClientContext()) }
	}

	@Test
	public void getSingleFeedTest(){
		HTTPResponse result = request.GET("http://123.123.123.123/feeds/test")
	}
	
	@AfterThread
	public void afterThread() {
		HTTPResponse result = request.POST("http://123.123.123.123/users/logout")
	}
}

이렇게 테스트 스크립트 작성이 끝나면 테스트 설정을 하자

가장 중요한 것은 vUser 수를 선정하는 것인데, 500명, 1000명의 사용자를 기준으로 각각 테스트해보자
테스트 시간은 Ramp-up 시간을 포함하여 유효한 결과를 도출하고자 10분 동안 테스트하자.

아래는 vUser를 500명 기준으로 테스트한 결과이다.

전체적인 결과도 중요하지만, 가장 필요한 데이터는 throughput,Latency이다.

TPS가 Throughput을 의미하고, 평균 테스트 시간이 Latency를 의미한다.

결과적으로 서비스는 vUser 500명 기준, 약 2000 TPS 처리량을 갖고, 테스트 요청당 230ms의 지연시간이 걸린다는 것을 알 수 있습니다.

다음은 vUser를 1000명으로 테스트한 결과이다.

보시는 바와 같이 사용자가 늘어났는데 Throughput은 약 2000 TPS로 동일하고, Latency만 두 배로 증가했습니다. 이 부분은 아래 그래프를 보자.

  • vUser 수가 최고점에 다다르면 고부하 영역에 들어가게 되고, 고부하 영역에서 일정 User 수를 초과하면 Buckle zone으로 진입해서 처리량이 줄어드는 현상이 발생한다.
  • 현재 vUser 500명 기준과 1000명 기준일 때, 처리량은 일정하므로 현재 테스트한 API에 대한 시스템의 성능은 약 2000 TPS 정도로 추정할 수 있습니다.
  • 하지만 이는 단위 성능 테스트이므로 전체 시스템의 성능이라고 단정할 수 없고, 여러 단위 테스트를 종합하고 성능 개선을 한 이후, 통합 시나리오 테스트 및 성능 개선 과정까지 끝내야 전체 시스템의 성능을 예상할 수 있다.

현재 상태에서 WAS의 CPU 사용량을 확인해보자

vUser 500명을 기준으로 테스트하는 중간에 htop 명령을 통해 확인한 결과, WAS의 CPU 사용량이 90% 이상 점유하는 것을 보면 현재 WAS에서 최대 처리량에 거의 도달했음을 볼 수 있다.

현재 WAS 상태에 대해 진단을 해보았으니, JVM 튜닝 및 슬로우 쿼리 분석 등 다양한 성능 개선 작업을 시도하면서 서비스의 성능을 최대한 올려보자


결론

  • nGrinder를 활용하면 스크립트 생성과 테스트 실행, 모니터링 및 결과 보고서 생성을 통합된 Web UI를 통해 사용할 수 있으므로 성능 테스트를 보다 쉽게 할 수 있습니다.
  • nGrinder를 활용한 성능 테스트를 하기 위해서는 Controller, Agent, Target 서버가 필요하고, 이는 각각 다른 서버로 구성되어 있어야 합니다.
  • vUser가 증가함에 따라 Throughput이 한계에 도달하면 현 상태의 최대 처리량으로 추정할 수 있습니다. 단, 실제 서비스의 성능은 이보다 더 적을 수 있습니다.
  • 단위 성능 테스트 결과는 전체 서비스의 성능을 나타내지 않습니다. 하지만 이를 개선함으로써 전체적인 성능을 증가시킬 수 있습니다.

0개의 댓글