๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
nGrinder

nGrinder๋กœ ๋กœ์ปฌํ™˜๊ฒฝ ์„œ๋ฒ„์— ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธํ•˜๊ธฐ(feat. ํšŒ์›๊ฐ€์ž… api)

by sangyunpark99 2024. 9. 11.

๐Ÿ”Ž ์‹ ๊ทœ ํšŒ์› ๊ฐ€์ž… ์ด๋ฒคํŠธ๋ฅผ ์„ ์ •ํ•œ ์ด์œ 

์‹ ๊ทœ ํšŒ์› ๊ฐ€์ž… ์ด๋ฒคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์€ ๋Œ€๋ถ€๋ถ„์˜ ํ•€ํ…Œํฌ ํšŒ์‚ฌ์—์„œ ํ•˜๋Š” ์ด๋ฒคํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹ ๊ทœ ํšŒ์› ๊ฐ€์ž… ์ด๋ฒคํŠธ๋กœ ์„ ์ •ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

 

๐Ÿ”Ž  ๊ธฐ๋Œ€ํ•˜๋Š” ๋ชฉํ‘œ

์ด ์ด๋ฒคํŠธ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š” ํŠธ๋ž˜ํ”ฝ ๋ฌธ์ œ๋ฅผ ์ฒ˜๋ฆฌํ•ด๋ณด๊ณ ,  ์ข€ ๋” ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•์„ ์ฐพ์•„๋ณด๊ณ  ์‹ถ๋‹ค.

๋˜ํ•œ ํŠธ๋ž˜ํ”ฝ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์„ ๊ธ€๋กœ ์ž‘์„ฑํ•จ์œผ๋กœ์จ, ๊ฐœ๋ฐœ ์ง๊ตฐ์˜ ๋ชจ๋“  ๋ถ„๋“ค์—๊ฒŒ ์œ ์˜๋ฏธํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์‹ถ๋‹ค.

 

 

๊ทธ๋Ÿผ ์ถœ๋ฐœํ•ด๋ณด์ž!!

 

 

๐Ÿ”Ž nGrinder๋กœ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ํ•ด๋ณด๊ธฐ

nGrinder๋กœ ๋กœ์ปฌํ™˜๊ฒฝ์—์„œ ์š”์ฒญ์ด ์ž˜ ๊ฐ€๋Š”์ง€ ํ…Œ์ŠคํŠธ ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค. ํšŒ์›๊ฐ€์ž…์„ ํ•˜๋Š” API๋ฅผ ํ…Œ์ŠคํŠธ ํ•˜๊ณ ์ž ํ•œ๋‹ค.

 

 

์ฐธ๊ณ ๋กœ ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ ํ•˜๊ธฐ ์œ„ํ•ด์„ , URL ์ž…๋ ฅ๋ž€์— localhost๊ฐ€ ์•„๋‹Œ 127.0.0.1์„ ์ž…๋ ฅํ•ด ์ฃผ์–ด์•ผ Invalid Input์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

127.0.0.1์— ํฌํŠธ๋ฒˆํ˜ธ๊นŒ์ง€ ํฌํ•จํ•ด์„œ, 127.0.0.1:8080์„ ์ž…๋ ฅํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.(ํฌํŠธ ๋ฒˆํ˜ธ๊นŒ์ง€ ๊ฐ™์ด ์ž…๋ ฅํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.)

 

ํšŒ์›๊ฐ€์ž… ํ•˜๋Š” api๋ฅผ ํ˜ธ์ถœํ•˜๋Š”๋ฐ ์„ฑ๊ณตํ–ˆ๋‹ค.

์ง€ํ‘œ๋ฅผ ๋ถ„์„ํ•ด๋ณด์ž..!!

Total Vusers : ํ…Œ์ŠคํŠธํ•œ ์œ ์ € ์ˆ˜

TPS : ์ดˆ๋‹น ํŠธ๋žœ์žญ์…˜ ๊ฐœ์ˆ˜(Transaction Per Second)
Peak TPS : ์ตœ๊ณ ์  TPS

Mean Test Time : ๊ฐœ๋ณ„ ํ…Œ์ŠคํŠธ ์‹œ๊ฐ„, ์ž‘์„ฑํ•œ ์Šคํฌ๋ฆฝํŠธ์˜ @Test ํ•จ์ˆ˜์˜ ํ‰๊ท  ์ˆ˜ํ–‰์‹œ๊ฐ„์„ ์˜๋ฏธํ•œ๋‹ค.

Executed Tests : ์‹คํ–‰ํ•œ ํ…Œ์ŠคํŠธ ์ˆ˜

Successful Tests : ์„ฑ๊ณตํ•œ ํ…Œ์ŠคํŠธ ์ˆ˜

Errors : ์—๋Ÿฌ ๋ฐœ์ƒ ์ˆ˜

Run time : ์‹คํ–‰ ์‹œ๊ฐ„

 

testํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

๐Ÿ”จ  ๋”ฐ๋กœ ๊ณต๋ถ€ํ•œ ๋ถ€๋ถ„

QPS : Query Per Second

QPS๊ฐ€ ๊ธ‰๊ฒฉํ•˜๊ฒŒ ์ฆ๊ฐ€ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ๋” ๋งŽ์€ ํŠธ๋žœ์žญ์…˜์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, CPU๋ฅผ ํฌํ•จํ•œ ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰๋„ ๊ธ‰์ฆํ•˜๊ฒŒ ๋œ๋‹ค.

 

QPS๊ฐ€ ์ฆ๊ฐ€ํ•˜๋ฉด ์–ด๋– ํ•œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๊ฐ€?

CPU ์‚ฌ์šฉ๋Ÿ‰ ๊ธ‰์ฆ, ๋ณ‘๋ชฉ ํ˜„์ƒ, IO ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€, Lock ๋ฐ Deadlock ์™€ ๊ฐ™์€ ๋ฌธ์ œ๋“ค์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

QPS๊ฐ€ ์ฆ๊ฐ€ํ•จ์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š” DB CPU ์ฆ๊ฐ€ ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ• ๊ฒƒ์ธ๊ฐ€?

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์ด ์กด์žฌํ•œ๋‹ค.

(1) ์ฟผ๋ฆฌ ์ตœ์ ํ™” - ์ฟผ๋ฆฌ ๋ถ„์„, ๋ณต์žกํ•œ ์กฐ์ธ ์ตœ์†Œํ™”, ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ

(2) ์ธ๋ฑ์Šค ํŠœ๋‹ - ์ ์ ˆํ•œ ์ธ๋ฑ์Šค ์„ค๊ณ„, ๊ณผ๋„ํ•œ ์ธ๋ฑ์Šค ์ œ๊ฑฐ

(3) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒค๋”ฉ - ์ˆ˜ํ‰๋ถ„ํ• (DB ํŠธ๋ž˜ํ”ฝ ๋ถ„์‚ฐํ•˜๊ธฐ)

(4) ์บ์‹ฑ - ์บ์‹ฑ ๋ ˆ์ด์–ด ์ถ”๊ฐ€, DB ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ์บ์‹ฑ

(5) ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํด๋Ÿฌ์Šคํ„ฐ๋ง ๋ฐ ๋ฆฌํ”Œ๋ฆฌ์ผ€์ด์…˜ - ์ฝ๊ธฐ ์ „์šฉ ์„œ๋ฒ„ ์ถ”๊ฐ€, ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ

(6) DBMS ์—…๊ทธ๋ ˆ์ด๋“œ

 

๐Ÿ”Ž  ์ฟผ๋ฆฌ๋ฌธ DB ๋ฐ˜์˜์ด ์ž˜ ๋˜๋Š”์ง€ ํ™•์ธ

nGrinder๋ฅผ ์‹คํ–‰ํ•œ ํ›„, ์ฟผ๋ฆฌ๋ฌธ์ด ์ž˜ ๋‚ ๋ผ๊ฐ€๋Š”์ง€ ํ™•์ธํ•ด๋ณด์•˜๋‹ค.

์ฟผ๋ฆฌ๋ฌธ์ด ํ›จํ›จ ์ž˜ ๋‚ ์•„๊ฐ„๋‹ค. ๐Ÿ˜˜

 

DB์— ์ €์žฅ์ด ์ž˜ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ!

 

๊ณ„์ •์ด ์ค‘๋ณต๋˜์„œ ์ €์žฅ๋˜๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ๐Ÿšซ

 

 

๐Ÿ”Ž  ๋กœ๊ทธ์ธ ์ค‘๋ณต ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ ์ž ์ค‘๋ณต๋œ ๊ณ„์ •์„ ํ™•์ธํ•˜๋Š” ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋Š” ํšŒ์› ๊ฐ€์ž…์‹œ ์‚ฌ์šฉํ•˜๋Š” ๋กœ์ง์ด๋‹ค.

public Long createMember(final MemberCreateRequest request) {

        Member member = request.toEntity();

        if(checkAlreadyExistedUser(member)) {
            throw new AlreadyExistedUserException();
        }
        
        return memberRepository.save(member).getId();
    }

 

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์œ ์ €์˜ ์ด๋ฉ”์ผ์ด DB์— ์ด๋ฏธ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง์ด๋‹ค.

private boolean checkAlreadyExistedUser(Member member) {
    return memberRepository.findByEmail(member.getEmail()).isPresent();
}

 

postman์œผ๋กœ ๊ฒฐ๊ณผ๊ฐ€ ์ž˜ ๋ฐ˜์˜์ด ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž

 

์ฒซ๋ฒˆ์งธ API ์š”์ฒญํ•˜๊ธฐ

 

๋‘๋ฒˆ์งธ API ์š”์ฒญํ•˜๊ธฐ

 

๐Ÿ”Ž  nGrinder๋กœ ํšŒ์›๊ฐ€์ž… ์š”์ฒญ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ

๊ธฐ์กด์— nGrinder์— ์‚ฌ์šฉํ•œ script๋Š” ๋™์ผํ•œ ๊ณ„์ •์œผ๋กœ ํšŒ์›๊ฐ€์ž… api๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ๋Š” ๋ฐฉ์‹์ด์˜€๋‹ค.

ํšŒ์›๊ฐ€์ž… ์ค‘๋ณต์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋กœ์ง์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ์œผ๋ฏ€๋กœ, nGrinder๋ฅผ ํ†ตํ•œ apiํ˜ธ์ถœ์€ ์ฒซ๋ฒˆ์งธ api ํ˜ธ์ถœ๋งŒ ์„ฑ๊ณตํ•œ ํ›„, ๋‚˜๋จธ์ง€ api ํ˜ธ์ถœ์„ ์‹คํŒจํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

 

nGrinder Test๊ฒฐ๊ณผ

 

 

๐Ÿ”Ž  nGrinder ํšŒ์›๊ฐ€์ž… ์š”์ฒญ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
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 static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

import org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager

/**
* A simple example using the HTTP plugin that shows the retrieval of a single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {

	public static GTest test
	public static HTTPRequest request
	public static Map<String, String> headers = [:]
	public static String body = "{\n  \"email\": \"abc@abc.com\",\n  \"password\": \"abc123\",\n  \"name\": \"abc\"\n}"
	public static List<Cookie> cookies = []

	@BeforeProcess
	public static void beforeProcess() {
		HTTPRequestControl.setConnectionTimeout(300000)
		test = new GTest(1, "127.0.0.1")
		request = new HTTPRequest()

		// Set header data
		headers.put("Content-Type", "application/json")
		grinder.logger.info("before process.")
	}

	@BeforeThread
	public void beforeThread() {
		test.record(this, "test")
		grinder.statistics.delayReports = true
		grinder.logger.info("before thread.")
	}

	@Before
	public void before() {
		request.setHeaders(headers)
		CookieManager.addCookies(cookies)
		grinder.logger.info("before. init headers and cookies")
	}

	@Test
	public void test() {
		HTTPResponse response = request.POST("http://127.0.0.1:8080/api/members", body.getBytes())

		if (response.statusCode == 301 || response.statusCode == 302) {
			grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
		} else {
			assertThat(response.statusCode, is(200))
		}
	}
}

 

ํ˜„์žฌ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์˜ ์ฃผ์š”ํ•œ ์›์ธ์€ test ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ• ๋•Œ๋งˆ๋‹ค ๋™์ผํ•œ ์ด๋ฉ”์ผ, ํŒจ์Šค์›Œ๋“œ, ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์—ฌ ํ˜ธ์ถœํ•ด์ฃผ๊ณ  ์žˆ๋‹ค.

public static String body = "{\n  \"email\": \"abc@abc.com\",\n  \"password\": \"abc123\",\n  \"name\": \"abc\"\n}"

 

์ด ๋ถ€๋ถ„์„ test๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ๋ณ€๊ฒฝ๋˜๋Š” ์ž„์˜์˜ ๊ฐ’์œผ๋กœ ์ ์šฉ์‹œ์ผœ์ฃผ์ž!

 

UUID๋ฅผ ์‚ฌ์šฉํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด test์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ์—ˆ๋‹ค.

@Test
public void test() {
	
	String uniqueEmail = "user" + UUID.randomUUID().toString() + "@example.com";
	String uniqueName = "user" + UUID.randomUUID().toString();
	String uniquePassword = UUID.randomUUID().toString(); // ๊ฐ„๋‹จํ•œ ๋ฌด์ž‘์œ„ ๋ฌธ์ž์—ด ์ƒ์„ฑ

	String body = String.format("{\"email\": \"%s\", \"name\": \"%s\", \"password\": \"%s\"}", uniqueEmail, uniqueName, uniquePassword);

	HTTPResponse response = request.POST("http://127.0.0.1:8080/api/members", body.getBytes())

	if (response.statusCode == 301 || response.statusCode == 302) {
		grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
	} else {
		assertThat(response.statusCode, is(200))
	}
}

 

๐Ÿ”Ž nGrinder๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์ž˜ ์‹คํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ

๊ณ„์ •์„ ํ™•์ธํ•ด์ฃผ๋Š” ๋กœ์ง ํ•˜๋‚˜๋งŒ ์ถ”๊ฐ€ํ–ˆ์„ ๋ฟ์ธ๋ฐ, TPS๊ฐ€ 267 โžก 110์œผ๋กœ ๋–จ์–ด์ง€๊ฒŒ ๋˜์—ˆ๋‹ค.

 

ํšŒ์› ๊ฐ€์ž…์„ ํ•˜๊ธฐ ์œ„ํ•œ ๋งค๋ฒˆ์˜ ์ฟผ๋ฆฌ ์š”์ฒญ๋งˆ๋‹ค ๊ฐ€์ž…ํ•˜๊ณ ์ž ํ•˜๋Š” ๊ณ„์ •์— ๋Œ€ํ•œ ์œ ์ €์˜ ์กด์žฌ์—ฌ๋ถ€๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด select ์ฟผ๋ฆฌ๋ฌธ์„ ๋‚ ๋ ค์ฃผ๊ณ  ์žˆ๋‹ค.

 

๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ nGrinder๋กœ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธํ•ด๋ณด๊ธฐ ์™„๋ฃŒ..!

 

'nGrinder' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

nGrinder์™€ ์นœํ•ด์ ธ๋ณผ๊นŒ(feat.๊ณต์‹๋ฌธ์„œ)  (2) 2024.09.01