어제 오늘 내일

[Spring Boot] "내 서버는 몇 명까지 버틸까?" nGrinder로 부하 테스트(Load Test) 하고 병목 지점 찾기 본문

IT/SpringBoot

[Spring Boot] "내 서버는 몇 명까지 버틸까?" nGrinder로 부하 테스트(Load Test) 하고 병목 지점 찾기

hi.anna 2026. 3. 29. 08:57

 

서버 개발의 끝은 배포가 아니라 성능 튜닝입니다.
API 응답 속도가 0.1초인지 3초인지는 혼자 테스트할 땐 모릅니다.

네이버가 만들고 오픈소스로 공개한 nGrinder는 엔터프라이즈급 부하 테스트 도구입니다. 스크립트 하나로 수천 명의 가상 사용자(VUser)가 동시에 로그인하고, 상품을 조회하고, 결제하는 시나리오를 만들 수 있습니다.


1. nGrinder 구조 (Controller + Agent)

nGrinder는 크게 두 가지로 나뉩니다.

  1. Controller (지휘관): 웹 UI를 제공하고, 테스트 스크립트를 관리하고, 결과를 보여줍니다.
  2. Agent (병사): 실제 부하(트래픽)를 발생시키는 일꾼입니다. 서버가 여러 대라면 Agent도 여러 대 띄워서 엄청난 트래픽을 만들 수 있습니다.

2. 설치하기 (Docker Compose)

설치가 까다롭기로 유명하지만, 도커를 쓰면 5분 컷입니다.

docker-compose.yml

version: '3'
services:
  controller:
    image: ngrinder/controller
    restart: always
    ports:
      - "80:80"     # 웹 UI 접속 포트
      - "16001:16001"
      - "12000-12009:12000-12009"
    volumes:
      - ./ngrinder-controller:/opt/ngrinder-controller

  agent:
    image: ngrinder/agent
    restart: always
    links:
      - controller
    environment:
      - CONTROLLER_HOST=controller
    volumes:
      - ./ngrinder-agent:/opt/ngrinder-agent

docker-compose up -dhttp://localhost:80에 접속합니다. (초기 ID/PW: admin/admin)


3. 테스트 스크립트 작성 (Groovy)

nGrinder는 Groovy(자바 문법)로 스크립트를 짭니다. JUnit 테스트 코드와 거의 똑같습니다.

  1. Script 메뉴 -> Create -> Groovy Maven Project 선택
  2. 테스트할 URL 입력 (예: http://192.168.0.5:8080/api/products)
  • 주의: localhost라고 적으면 안 됩니다! (도커 컨테이너 기준이라 자기 자신을 찾게 됨) 내 PC의 사설 IP를 적으세요.
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.scriptgen.HTTPRequest
import HTTPClient.NVPair
import HTTPClient.Cookie
import HTTPClient.HTTPResponse
import HTTPClient.CookieModule

@RunWith(GrinderRunner)
class TestRunner {

    public static GTest test
    public static HTTPRequest request

    @BeforeProcess
    public static void beforeProcess() {
        test = new GTest(1, "Product API Test")
        request = new HTTPRequest()
    }

    @BeforeThread
    public void beforeThread() {
        // 로그인 쿠키 설정 등이 필요하면 여기서
        grinder.statistics.delayReports = true
    }

    @Test
    public void test() {
        // GET 요청 보내기
        HTTPResponse result = request.GET("http://192.168.0.5:8080/api/products")

        // 응답 코드가 200인지 검증
        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response is 301 or 302.")
        } else {
            assertThat(result.statusCode, is(200))
        }
    }
}

4. 테스트 실행 (Performance Test)

이제 시나리오를 돌려봅시다.

  1. Performance Test 메뉴 -> Create Test
  2. Basic Configuration:
  • Agent: 1 (에이전트 몇 대 쓸래?)
  • Vuser per Agent: 100 (에이전트 1대당 가상 사용자 100명 = 총 100명 동시 접속)
  • Duration: 1분 (1분 동안 공격!)
  • Target Host: 내 서버 IP
  1. Save and Start 클릭!

5. 결과 분석: TPS가 떨어지면 망한 겁니다

테스트가 끝나면 그래프가 나옵니다. 가장 중요한 지표는 TPS (Transactions Per Second)입니다.

  • TPS 그래프가 우상향하거나 일정하다: 서버가 잘 버티고 있음.
  • TPS가 오르다가 뚝 떨어진다: 서버 한계 도달 (DB 커넥션 풀 부족, 메모리 부족, 스레드 풀 고갈 등).
  • Errors 그래프가 치솟는다: 서버가 500 에러를 뱉기 시작함.

튜닝 포인트 찾기:
이때 우리가 배웠던 VisualVM이나 Pinpoint를 같이 켜두고 보면, 어디서 막히는지(병목) 정확히 알 수 있습니다.

  • "어? DB 커넥션을 못 얻어서 대기하네?" -> HikariCP 늘리기
  • "어? 쿼리가 느리네?" -> 인덱스 추가 또는 Redis 도입

마치며

오늘의 결론입니다.

  1. nGrinder는 가상의 사용자(Bot)를 만들어 내 서버를 디도스(DDoS) 공격하듯 테스트한다.
  2. TPS가 꺾이는 지점이 내 서버의 최대 성능 한계점이다.
  3. 부하 테스트 없이 오픈하는 것은 안전벨트 없이 고속도로를 달리는 것과 같다.

이제 여러분은 "이 서버는 동시 접속자 5,000명까지는 거뜬합니다!"라고 숫자로 증명할 수 있는 개발자가 되었습니다.

다음 포스팅에서는 이 시리즈의 마지막 주제, "새로고침 없이 알림이 오네요?" HTTP가 아닌 양방향 통신, WebSocket과 STOMP로 실시간 채팅방 만들기에 대해 알아보겠습니다.

도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊

반응형
Comments