nGrinder 는 Grinder 라는 내부 엔진을 사용하며 nGrinder 는 이 엔진을 controller 와 agent로 감싸 다수의 테스트를 병렬처리할 수 있도록 한다. controller 와 agent 는 nGrinder 의 주요 요소이다.
성능테스트를 위한 웹 인터페이스를 제공하는 역할을 한다. agent 를 관리하는 것이 controller 이며 테스트 통계를 보여주거나 사용자가 스크립트를 생성 또는 변경하는 것을 가능하게 한다.
cd /Users/choihyewon/Desktop/Work/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
스크립트를 기반으로 프로세스를 실행하고 지정한 타겟에 실제로 트래픽을 발생시켜 스레드를 동작시키게 한다. Monitoring 모드에서 타겟 시스템의 성능을 모니터링 하는 것도 agent의 역할이다.
cd /Users/choihyewon/Desktop/Work/ngrinder/ngrinder-agent
./run_agent.sh
주의 ! localhost(X) -> 127.0.0.1
http://127.0.0.1:8080/api/notifications/longpoll
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: 실행 시간
간단하게 설명하면 서버가 부담하는 부하를 분산해주는 장치 혹은 기술이다.
많은 트래픽을 어떻게 대처할까?
크게 두가지로 나타낼 수 있다.
무중단 서비스 & 배포
서버는 갑자기 다운될 수도 있고 배포를 하게 되면 다운이 된다.
당연스럽게도 여러 대의 서버로 나누게 되면 하나의 서버가 다운되어도 실제 클라이언트는 알 수 없다.
주의: 이것은 알고리즘에 따라 달라질 수 있다.
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로 전달된다.
이런 식으로 계속 번갈아가며 요청을 분배한다.
upstream backend {
least_conn;
server spring-app-1:8080;
server spring-app-2:8083;
}
결론적으로, 대부분의 경우 Least Connections 방식이 가장 효과적이다.
그 이유는:
t3a.large
/ 2vCPU, 8GB 메모리t3a.large
/ 2vCPU, 8GB 메모리 t3a.large
/ 2vCPU, 8GB 메모리RDS : t3.large
/ 2vCPUs, 8GB 메모리
ElastiCache : r6g.large
2vCPUs, 13GB 메모리
docker pull ngrinder/controller:3.5.3
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으로 설정
docker pull ngrinder/agent:3.5.3
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
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 접속
이렇게 하면 Prometheus가 Docker 컨테이너로 실행되며, Spring Boot 애플리케이션의 메트릭을 수집할 수 있다.
그리고 Grafana에서 프로메테우스 데이터 소스를 추가할 때는 http://{프로메테우스설치IP}:9090를 사용하면 된다.
그라파나와 프로메테우스 연결 성공!
https://grafana.com/grafana/dashboards/?search=spring
완료!
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;
...
}
...
}
레디스 캐싱 상태로 진행하겠습니다.
Mac M1 Pro
(CPU : 8코어 / RAM : 16GB)500명 이상으로 사용자를 늘릴수록 에러가 급증하고 서버에서 Read Time Out이 나는등 트래픽을 견뎌낼 수 없었습니다.
t3.micro
서버 (2vCPU / RAM : 1GB)1000명 이상으로 사용자를 늘릴수록 에러가 급증하고 서버에서 Read Time Out이 나는등 트래픽을 견뎌낼 수 없었습니다. 프로젝트 초기에 서버 설계를 할 때사용자가 늘어날 것을 대비해 서버의 대수를 늘리는 Scale Out방식을 적용하기로 결정했습니다. 또한 micro 로는 테스트가 역부족일거라 생각하여 인스턴스 유형을 t3a.large로 Scale Up하였습니다. 따라서 Nginx 웹서버 한대 띄워 이 웹서버를 거쳐 2vCPU 8GB RAM 서버 두대에 로드밸런싱을 하는 방식으로 서버 구조를 변경하고 성능테스트를 진행했습니다.
t3.large
서버 (2vCPU / RAM : 8GB) Nginx로 로드밸런싱1초 동안 50명의 사용자가 거의 동시에 시스템에 액세스하고 각 사용자가 동일한 작업 세트를 100회 수행
단일 서버 환경
총 5000개의 요청 처리
평균 응답시간: 78ms
최소/최대 응답시간: 12ms / 1144ms
처리량: 568.6 요청/초
표준편차: 80.20 (응답시간의 변동이 큼)
수신 데이터: 3708.05 KB/sec
Nginx 로드밸런싱 환경
동일하게 5000개의 요청 처리
평균 응답시간: 24ms (약 3배 개선)
최소/최대 응답시간: 8ms / 73ms
처리량: 1441.8 요청/초 (약 2.5배 향상)
표준편차: 6.83 (응답시간이 매우 안정적)
수신 데이터: 9399.79 KB/sec
안정성 개선
에러율
- 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를 유지함을 보여준다.