[성능 테스트] nGrinder로 성능 테스트 + Grafana로 모니터링 + 서버 Scale-Out

최혜원·2024년 10월 19일
1

nGrinder(로컬테스트)

nGrinder 는 Grinder 라는 내부 엔진을 사용하며 nGrinder 는 이 엔진을 controller 와 agent로 감싸 다수의 테스트를 병렬처리할 수 있도록 한다. controller 와 agent 는 nGrinder 의 주요 요소이다.

controller

성능테스트를 위한 웹 인터페이스를 제공하는 역할을 한다. agent 를 관리하는 것이 controller 이며 테스트 통계를 보여주거나 사용자가 스크립트를 생성 또는 변경하는 것을 가능하게 한다.

ngrinder-controller-3.5.8.war 파일이 있는 디렉토리 경로 이동

cd /Users/choihyewon/Desktop/Work/ngrinder

ngrinder 실행

java -jar ngrinder-controller-3.5.8.war --port=8300

에러 발생

해결

java -Djava.io.tmpdir=/Users/choihyewon/Desktop/Work/ngrinder -jar ngrinder-controller-3.5.8.war --port=8300

localhost:8300 접속 후 로그인


agent

스크립트를 기반으로 프로세스를 실행하고 지정한 타겟에 실제로 트래픽을 발생시켜 스레드를 동작시키게 한다. Monitoring 모드에서 타겟 시스템의 성능을 모니터링 하는 것도 agent의 역할이다.

agent 설치

agent 실행

cd /Users/choihyewon/Desktop/Work/ngrinder/ngrinder-agent
./run_agent.sh

agent 관리

Script 작성

테스트 스크립트 생성

주의 ! localhost(X) -> 127.0.0.1
http://127.0.0.1:8080/api/notifications/longpoll

validate 클릭

에러 발생

General error during conversion: Unsupported class file major version 61

원인

nGrinder가 지원하지 않는 버전인 17버전을 사용하여 발생하였다.
로컬은 자바17인데 ngrinder 기본 권장 사양은 자바 8

해결

Java 버전을 11로 다운그레이드하기

Total Vusers: 테스트한 유저 수
TPS: 초당 처리 가능한 요청 수
Peak TPS: 가장 TPS가 높을 때
Mean Test Time: 지연 시간
Executed Tests: 실행한 테스트 수
Successful Tests: 성공한 테스트 수
Errors: 에러 발생 개수
Run time: 실행 시간



로드밸런싱이란?

간단하게 설명하면 서버가 부담하는 부하를 분산해주는 장치 혹은 기술이다.

많은 트래픽을 어떻게 대처할까?
크게 두가지로 나타낼 수 있다.

  • Scale up: 기존 서버의 성능을 높인다 (비용도 같이 올라간다)
  • Scale out: 여러 대의 서버를 두어 트래픽을 분산시킨다.(물리적 or 논리적)
    여기서 필자는 비용적인 부분을 생각해 논리적인 Scale out 방식을 생각했다.
    Scale out을 하기 위해서 무조건 해야 하는 일이 로드밸런싱이다!

+ 트래픽을 감당하는 장점 이외의 장점

무중단 서비스 & 배포

서버는 갑자기 다운될 수도 있고 배포를 하게 되면 다운이 된다.
당연스럽게도 여러 대의 서버로 나누게 되면 하나의 서버가 다운되어도 실제 클라이언트는 알 수 없다.
주의: 이것은 알고리즘에 따라 달라질 수 있다.

Nginx로 로드밸런싱 구현

동일한 애플리케이션을 실행하는 두 개의 서버 인스턴스

upstream spring-app {
    server spring-app-1:8080;
    server spring-app-2:8083;
}

upstream 블록에서 정의한 서버들 사이에 Nginx는 기본적으로 라운드 로빈(Round Robin) 방식으로 요청을 분배한다. 이는 다음과 같이 작동한다

  • 첫 번째 요청은 spring-app-1:8080로 전달된다.
  • 두 번째 요청은 spring-app-2:8083로 전달된다.
  • 세 번째 요청은 다시 spring-app-1:8080로 전달된다.
    이런 식으로 계속 번갈아가며 요청을 분배한다.

🔮 알고리즘 종류

  1. 라운드로빈 방식 (Round Robin Method) 서버에 들어온 요청을 순서대로 돌아가며 배정하는 방식이다. 클라이언트의 요청을 순서대로 분배하기 때문에 서버들이 동일한 스펙을 가지고 있다. 서버의 세션을 오래 유지할 필요가 없는 경우에 활용하기 좋다.
  2. 가중 라운드 로빈 방식 (Weighted Round Robin Method) 각 서버마다 가중치를 매기고 가중치가 높은 서버에 클라이언트 요청을 우선적으로 분배한다. 주로 서버의 트래픽 처리 능력 차이가 나는 경우 사용되는 부하 분산 방식이다.
  3. IP 해시 방식 (IP Hash Method) 클라이언트의 IP 주소를 특정 서버로 매핑하여 처리하는 방식이다. 요청이 들어온 사용자의 IP를 해싱(Hashing : 임의 길이의 데이터를 고정 길이 데이터로 매핑하는 것)하여 분배하므로 사용자가 항상 동일한 서버로 연결되는 것을 보장한다.
  4. 최소 연결 방식 (Least Connection Method) 요청이 들어온 시점에 가장 연결 상태가 적은 서버에 우선적으로 분배하는 방식이다. 서버에 분배된 트래픽이 일정하지 않은 경우에 적합하다.
  5. 최소 응답 시간 방식 (Least Response Time Method) 서버의 현재 연결 상태와 응답 시간을 모두 고려하여 트래픽을 분배하는 방식. 가장 적은 연결 상태와 짧은 응답 시간을 보이는 서버에 우선적으로 분배한다.

Nginx의 로드밸런싱 알고리즘 중 가장 효과적인 것은?

upstream backend {
    least_conn;
    server spring-app-1:8080;
    server spring-app-2:8083;
}

결론적으로, 대부분의 경우 Least Connections 방식이 가장 효과적이다.
그 이유는:

  • 실시간 서버 부하를 고려한다.
  • 자동으로 부하를 분산한다.
  • 다양한 처리 시간을 가진 요청을 효과적으로 처리한다.
  • 서버 간 성능 차이가 있을 때도 잘 작동한다.
    하지만 세션 지속성이 필요한 경우는 IP Hash를, 서버 간 성능 차이가 큰 경우는 Weighted Load Balancing을 고려해볼 수 있다.


nGrinder(aws EC2 테스트)

Architecture

Controller(테스트 관리 및 모니터링)

  • 전반적인 작업이 Controller를 통해 수행된다.
  • 스트레스 테스트를 위한 웹 인터페이스를 제공한다.
  • 테스트 프로세스를 체계화하며, 테스트 결과를 수집해 통계를 보여준다.
  • Controller를 통해 사용자는 테스트 수행을 위한 스크립트를 생성 및 수정할 수 있다.
  • 서버 사양 : t3a.large / 2vCPU, 8GB 메모리

Agent(실제 부하 생성)

  • Controller의 명령을 받아 작업을 수행한다.
  • Agent 모드시 프로세스 및 스레드를 실행시켜 Target 머신에 부하를 발생시킨다.
  • Monitor 모드시 대상 시스템의 CPU 및 Memory 등을 모니터링한다.
  • 서버 사양 :t3a.large / 2vCPU, 8GB 메모리

Target

  • 테스트 대상이 되는 머신이다.
  • 서버 사양 : t3a.large / 2vCPU, 8GB 메모리

RDS : t3.large / 2vCPUs, 8GB 메모리
ElastiCache : r6g.large 2vCPUs, 13GB 메모리


설치 방법

Docker에서 nGrinder 사용하기

controller 설치

docker pull ngrinder/controller:3.5.3

controller 컨테이너 생성 후 실행

docker run -d -v ~/ngrinder-controller:/opt/ngrinder-controller -p 80:80 -p 16001:16001 -p 12000-12009:12000-12009 ngrinder/controller:3.5.3

컨테이너 생성 후 백그라운드로 접속한다.
접속 포트는 80으로 설정

agent 설치

docker pull ngrinder/agent:3.5.3

agent 컨테이너 생성

docker run -v ~/ngrinder-agent:/opt/ngrinder-agent -d ngrinder/agent:3.5.3 {controller-ec2-ip}:{controller-ec2-web-port}
docker run -v ~/ngrinder-agent:/opt/ngrinder-agent -d ngrinder/agent:3.5.3 3.36.95.133:80

보안그룹 설정

http://{controller-ec2-ip}로 nGrinder Controller 웹 페이지로 접속

admin 로그인

agent 확인

agent 추가


프로메테우스

sudo mkdir -p /etc/prometheus
sudo vi /etc/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090'] # 프로메테우스 자체 모니터링

  - job_name: 'spring-app'
    metrics_path: '/actuator/prometheus' # Spring Boot 애플리케이션
    static_configs:
      - targets: ['172.31.11.227:8080']
    tls_config:
      insecure_skip_verify: true
docker run -d \
  --name prometheus \
  -p 9090:9090 \
  -v /etc/prometheus:/etc/prometheus \
  prom/prometheus      

접속 확인


웹 브라우저에서 http://{프로메테우스설치IP}:9090 접속

Status > Targets 메뉴에서 타겟 상태 확인


이렇게 하면 Prometheus가 Docker 컨테이너로 실행되며, Spring Boot 애플리케이션의 메트릭을 수집할 수 있다.

그라파나와 프로메테우스 연결

그리고 Grafana에서 프로메테우스 데이터 소스를 추가할 때는 http://{프로메테우스설치IP}:9090를 사용하면 된다.
그라파나와 프로메테우스 연결 성공!

그라파나 대시보드 템플릿

https://grafana.com/grafana/dashboards/?search=spring


완료!


서버 테스트

default.conf 수정

서버 1대 추가

upstream spring-app {
    least_conn;  # 최소 연결 수 기반 로드 밸런싱
    server 172.31.11.227:8080;
    server 172.31.34.63:8080;
    keepalive 32;  # Maximum keepalive connections per server
}

server {
    listen 80;
    listen [::]:80;
    server_name tmarket.store;
    # 모든 HTTP 요청을 HTTPS로 리디렉션
    return 301 https://$host$request_uri;
}

server {
     listen 443 ssl;
     server_name tmarket.store;
     root /usr/share/nginx/html;

     include /etc/nginx/default.d/*.conf;
     
     ssl_certificate /home/ubuntu/certificate.crt;
     ssl_certificate_key /home/ubuntu/private.key;

     ...

    location /api/ {
        proxy_pass http://spring-app;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

   ...
      }

   ...
 }

compose.yml 분리


테스트 정리

레디스 캐싱 상태로 진행하겠습니다.

Local Mac M1 Pro (CPU : 8코어 / RAM : 16GB)

100명

Vuser 증가

500명 -> 에러 발생

500명 이상으로 사용자를 늘릴수록 에러가 급증하고 서버에서 Read Time Out이 나는등 트래픽을 견뎌낼 수 없었습니다.

1000명 -> 에러 발생



AWS t3.micro 서버 (2vCPU / RAM : 1GB)

100명

Vuser 증가

500명 -> 에러 발생

1000명 -> 에러 발생

1000명 이상으로 사용자를 늘릴수록 에러가 급증하고 서버에서 Read Time Out이 나는등 트래픽을 견뎌낼 수 없었습니다. 프로젝트 초기에 서버 설계를 할 때사용자가 늘어날 것을 대비해 서버의 대수를 늘리는 Scale Out방식을 적용하기로 결정했습니다. 또한 micro 로는 테스트가 역부족일거라 생각하여 인스턴스 유형을 t3a.large로 Scale Up하였습니다. 따라서 Nginx 웹서버 한대 띄워 이 웹서버를 거쳐 2vCPU 8GB RAM 서버 두대에 로드밸런싱을 하는 방식으로 서버 구조를 변경하고 성능테스트를 진행했습니다.



AWS t3.large 서버 (2vCPU / RAM : 8GB) Nginx로 로드밸런싱

테스트 아키텍처

JMeter 테스트 상황

1초 동안 50명의 사용자가 거의 동시에 시스템에 액세스하고 각 사용자가 동일한 작업 세트를 100회 수행

서버 1개 (ElastiCache)

단일 서버 환경

5000개의 요청 처리
평균 응답시간: 78ms
최소/최대 응답시간: 12ms / 1144ms
처리량: 568.6 요청/초
표준편차: 80.20 (응답시간의 변동이 큼)
수신 데이터: 3708.05 KB/sec

서버 2개 (ElastiCache)

Nginx 로드밸런싱 환경

동일하게 5000개의 요청 처리
평균 응답시간: 24ms (약 3배 개선)
최소/최대 응답시간: 8ms / 73ms
처리량: 1441.8 요청/초 (약 2.5배 향상)
표준편차: 6.83 (응답시간이 매우 안정적)
수신 데이터: 9399.79 KB/sec


Vuser 증가 : 1000~4000명 테스트에서 에러율 0% 달성

서버 2대 1000명

서버 2대 2000명

서버 2대 3000명

서버 2대 4000명

안정성 개선

에러율

  • t3.micro 1대, M1 Pro 1대: 1000명 테스트에서 에러 발생
  • t3a.large 2대: 1000~4000명 테스트에서 에러율 0% 달성

응답시간 안정성

  • 1000명 테스트: 평균 응답시간 409.31ms
  • 2000명 테스트: 평균 응답시간 889.94ms
  • 3000명 테스트: 평균 응답시간 660.10ms
  • 4000명 테스트: 평균 응답시간 661.38ms

성능 개선

TPS(처리량) 안정성

  • 1000명: 442.8 TPS (Peak 563.0)
  • 2000명: 424.4 TPS (Peak 668.5)
  • 3000명: 444.0 TPS (Peak 612.5)
  • 4000명: 443.4 TPS (Peak 651.0)
    이는 부하가 증가해도 일정한 TPS를 유지함을 보여준다.
profile
어제보다 나은 오늘

0개의 댓글