Java 성능 최적화 교육을 위한 실전 Case Study 시스템입니다.
"어제까지 잘 돌던 배치가 왜 터졌을까?"
- 상황: 매일 새벽 제휴사 CSV 파일을 DB에 적재하는 배치
- 문제: 마케팅 행사로 데이터가 1만 → 10만 건 증가 → OutOfMemoryError
- 개선 전 (Slow V1-V3):
- V1: 파일 전체 메모리 로드 + 단일 INSERT
- V2: Auto-commit (건마다 커밋)
- V3: JPA saveAll() + IDENTITY 전략
- 개선 후 (Fast):
- Streaming 읽기 (BufferedReader)
- JDBC Batch INSERT (1,000건 단위)
rewriteBatchedStatements=true옵션
- 결과: 2,389초 → 5초 (약 478배 개선!)
대시보드: http://localhost:8081/batch-dashboard
"외부기관 API 응답이 너무 느려요!"
- 상황: 외부기관이 서비스 해지 통보 API 호출
- 문제: 동기 처리로 인해 트래픽 몰릴 때 응답 지연 → 외부기관 타임아웃
- 요건: 제한 시간 내 반드시 응답 필요
- 개선 전: 동기 처리 (DB 저장 완료 후 응답)
- 개선 후: RabbitMQ 비동기 처리 (즉시 응답)
- 결과: 응답시간 99% 단축 + 안정적 처리
대시보드: http://localhost:8081/async-dashboard
"인기 상품 조회 시 DB가 다운됐다"
- 상황: 인기 상품 페이지 조회 시 조회수 UPDATE 발생
- 문제: 동일 상품 조회 몰림 → Row Lock 경합 → SELECT도 대기 → 응답시간 급증
- 증상: 100 TPS 상황에서 응답시간 500~1,000ms, DB CPU 100%
- 개선 전: 매 조회마다 UPDATE + SELECT (Row Lock)
- 개선 후: Redis INCR (Lock 없음) + 스케줄러 배치 반영
- 결과: 응답시간 10ms 이하, DB 부하 98% 감소
대시보드: http://localhost:8081/cache-dashboard
# 기본 시스템 시작 (Pinpoint + 인프라 + Spring Boot App)
./start.sh
# 브라우저에서 접속
open http://localhost:8081- 메인 홈: http://localhost:8081
- 배치 성능 비교: http://localhost:8081/batch-dashboard
- 비동기 성능 비교: http://localhost:8081/async-dashboard
- Redis 캐시 비교: http://localhost:8081/cache-dashboard
⚠️ 포트 주의:
- Performance App: 8081 포트
- nGrinder: 8080 포트
- Pinpoint: 8079 포트
URL: http://localhost:8081/batch-dashboard
사용 방법:
- 샘플 데이터 생성: 건수 입력 후 "샘플 CSV 생성" 클릭 (기본 10,000건)
- 성능 비교 실행: "배치 성능 비교 실행" 클릭
- 결과 확인:
- Slow V1: 파일 전체 메모리 로드 + 단일 INSERT
- Slow V2: Auto-commit (건마다 커밋) → 가장 느림!
- Slow V3: JPA saveAll() + IDENTITY 전략
- Fast: Streaming + JDBC Batch INSERT
교육 포인트:
- 메모리 사용량 비교 (전체 로드 vs 스트리밍)
- Batch INSERT의 중요성 (
rewriteBatchedStatements=true) - Auto-commit의 성능 영향
URL: http://localhost:8081/async-dashboard
사용 방법:
- 동기 API 호출: "동기 처리 호출" 버튼 클릭 → 느린 응답
- 비동기 API 호출: "비동기 처리 호출" 버튼 클릭 → 즉시 응답
- 통계 확인: 평균 응답시간, 처리량 비교
- 메시지 확인: RabbitMQ 대기 중인 메시지 수 확인
교육 포인트:
- 동기 vs 비동기 응답 시간 차이 (수백 ms → 수 ms)
- RabbitMQ를 통한 메시지 큐 처리
- 외부 API 타임아웃 대응 방법
nGrinder 부하 테스트:
CancellationSyncTest.groovy: 동기 API 부하 테스트CancellationAsyncTest.groovy: 비동기 API 부하 테스트
URL: http://localhost:8081/cache-dashboard
사용 방법:
- 테스트 상품 생성: "📦 테스트 상품 생성" 클릭 (최초 1회)
- DB 직접 방식: "❌ DB 직접 방식 호출" 클릭
- Redis Write-Back: "✅ Redis Write-Back 호출" 클릭
- 성능 비교: 응답시간, 성능 개선율 확인
- 강제 동기화: "⚡ 강제 동기화" 클릭 → Redis 조회수 즉시 DB 반영
교육 포인트:
- Row Lock 경합 문제 (UPDATE + SELECT 동시 요청)
- Redis Write-Back 패턴의 이해
- 스케줄러를 통한 배치 동기화 (1분 주기)
- 단일 요청 vs 부하 테스트 시 성능 차이
nGrinder 부하 테스트:
ProductViewDbTest.groovy: DB 직접 방식 부하 테스트 (100 TPS)ProductViewRedisTest.groovy: Redis Write-Back 부하 테스트 (100 TPS)
Redis 관리:
- "⚡ 강제 동기화": Redis → DB 즉시 반영
- "📋 통계 초기화": 성능 측정 데이터 리셋
- 자동 동기화: 1분마다 스케줄러가 자동으로 Redis → DB 반영
# 배치 성능 테스트
curl -X POST "http://localhost:8081/api/batch/sample-data?count=10000"
curl -X POST "http://localhost:8081/api/batch/compare"
# 비동기 처리 테스트
curl -X POST "http://localhost:8081/api/cancellation/sync" \
-H "Content-Type: application/json" \
-d '{"memberId":"M001","reason":"테스트"}'
curl -X POST "http://localhost:8081/api/cancellation/async" \
-H "Content-Type: application/json" \
-d '{"memberId":"M001","reason":"테스트"}'
# Redis 캐시 테스트
curl "http://localhost:8081/api/product/view/db/1"
curl "http://localhost:8081/api/product/view/redis/1"
curl "http://localhost:8081/api/product/view/stats"- Spring Boot 3.4.2
- Java 21
- Spring Data JPA - 엔티티 관리
- JDBC Template - Batch INSERT 최적화
- Spring AMQP - RabbitMQ 메시지 처리
- Spring Data Redis - Redis 캐시 및 Write-Back
- MySQL 8.0 - 메인 데이터베이스
- Redis 7 - 캐시 및 조회수 임시 저장
- RabbitMQ 3 - 비동기 메시지 큐
- Pinpoint 2.5.4 - APM, 분산 트레이싱
- nGrinder 3.5.9 - 부하 테스트
- Spring Actuator - 헬스 체크 및 메트릭
- Thymeleaf - 서버 사이드 렌더링
- Chart.js - 성능 비교 차트
- Vanilla JavaScript - 인터랙티브 UI
src/main/java/com/skplanet/performance/
├── batch/
│ ├── controller/BatchCompareController.java # 배치 비교 API
│ ├── service/BatchCompareService.java # 배치 성능 비교 로직
│ ├── service/ProductBatchSlowV1Service.java # Slow V1 (전체 메모리 로드)
│ ├── service/ProductBatchSlowV2Service.java # Slow V2 (Auto-commit)
│ ├── service/ProductBatchSlowV3Service.java # Slow V3 (JPA saveAll)
│ └── service/ProductBatchFastService.java # Fast (Streaming + JDBC Batch)
└── entity/Product.java
src/main/resources/templates/
└── batch-dashboard.html # 배치 대시보드 UI
products.csv # 샘플 CSV 파일 (자동 생성)
src/main/java/com/skplanet/performance/
├── async/
│ ├── controller/CancellationController.java # 해지 처리 API
│ ├── service/CancellationSyncService.java # 동기 처리 (Before)
│ ├── service/CancellationAsyncService.java # 비동기 처리 (After)
│ ├── consumer/CancellationConsumer.java # RabbitMQ Consumer
│ └── config/RabbitMQConfig.java # RabbitMQ 설정
├── entity/Cancellation.java
└── repository/CancellationRepository.java
src/main/resources/templates/
└── async-dashboard.html # 비동기 대시보드 UI
ngrinder-scripts/
├── CancellationSyncTest.groovy # 동기 API 부하 테스트
└── CancellationAsyncTest.groovy # 비동기 API 부하 테스트
src/main/java/com/skplanet/performance/
├── cache/
│ ├── controller/ProductViewController.java # 상품 조회 API
│ ├── service/ProductViewService.java # DB 직접 방식 (Before)
│ ├── service/ProductViewCacheService.java # Redis Write-Back (After)
│ ├── scheduler/ViewCountSyncScheduler.java # 1분마다 Redis → DB 동기화
│ └── config/RedisConfig.java # Redis 설정
└── repository/ProductRepository.java
src/main/resources/templates/
└── cache-dashboard.html # 캐시 대시보드 UI
ngrinder-scripts/
├── ProductViewDbTest.groovy # DB 직접 방식 부하 테스트
└── ProductViewRedisTest.groovy # Redis Write-Back 부하 테스트
src/main/java/com/skplanet/performance/
├── controller/HomeController.java # 메인 홈 페이지
├── config/DataSourceConfig.java # MySQL 설정
└── PerformanceApplication.java # Spring Boot 메인
src/main/resources/
├── templates/
│ └── index.html # 메인 홈페이지
├── application.yml # 애플리케이션 설정
└── schema.sql # DB 스키마
docker/
└── mysql/init.sql # MySQL 초기화 스크립트
docker-compose.yaml # Docker Compose 설정
Dockerfile # Spring Boot 이미지 빌드
| 서비스 | 설명 | 포트 | URL | start.sh | start-ngrinder.sh | docker-compose |
|---|---|---|---|---|---|---|
| Spring Boot App | 백엔드 애플리케이션 | 8081 | http://localhost:8081 | ✅ | - | ✅ |
| MySQL | 관계형 데이터베이스 | 3306 | - | ✅ | - | ✅ |
| Redis | 캐시 서버 | 6379 | - | ✅ | - | ✅ |
| RabbitMQ | 메시지 큐 | 5672, 15672 | http://localhost:15672 | ✅ | - | ✅ |
| Pinpoint Web | APM 대시보드 | 8079 | http://localhost:8079 | ✅ | - | ❌ |
| Pinpoint Collector | APM 데이터 수집기 | 9991-9996 | - | ✅ | - | ❌ |
| HBase Master | Pinpoint 데이터 저장소 | 16010 | http://localhost:16010 | ✅ | - | ❌ |
| nGrinder Controller | 부하 테스트 컨트롤러 | 8080 | http://localhost:8080 | ❌ | ✅ | ✅ |
| nGrinder Agent | 부하 테스트 에이전트 | - | - | ❌ | ✅ | ✅ |
💡 참고:
./start.sh: 기본 시스템 시작 (Pinpoint + 인프라 + App, nGrinder 제외)./start-ngrinder.sh: nGrinder 시작 (부하 테스트 필요 시)docker-compose up -d: 모든 Docker 서비스 (Pinpoint 제외)- Pinpoint는
../pinpoint-docker-official디렉토리에 설치 필요
완전히 깨끗한 상태에서 전체 시스템을 시작하는 가장 간단한 방법입니다.
기존 컨테이너와 볼륨을 모두 삭제합니다:
# 현재 프로젝트의 모든 컨테이너 및 볼륨 삭제
docker compose down -v
# Pinpoint가 설치되어 있다면 함께 정리
./stop-pinpoint.sh # 옵션 3 선택 (완전 삭제)Pinpoint가 아직 설치되지 않았다면 다음 스크립트를 실행합니다:
# Pinpoint 설치 (MySQL, Batch, Flink 제외)
./install-pinpoint.sh설치 내용:
- ✅ Pinpoint 공식 저장소 클론
- ✅ 2.5.4 버전 체크아웃 (M4 Mac 호환)
- ✅ 포트 변경 (8080 → 8079)
- ✅ 설치만 수행 (컨테이너 시작 안 함)
소요 시간: 약 1분 (다운로드만)
💡 이미 Pinpoint가 설치되어 있다면 이 단계를 건너뛰세요.
start.sh 스크립트로 기본 시스템을 시작합니다:
# 기본 시스템 시작 (Pinpoint + 인프라 + Spring Boot App)
./start.sh자동으로 시작되는 서비스:
- ✅ Pinpoint (필수 구성만: Zookeeper, HBase, Collector, Web, Agent, QuickStart)
- ✅ MySQL (3306 포트)
- ✅ Redis (6379 포트)
- ✅ RabbitMQ (5672, 15672 포트)
- ✅ Spring Boot App (8081 포트)
소요 시간: 약 5분 (Pinpoint HBase 초기화 포함)
부하 테스트가 필요하다면 nGrinder를 별도로 시작합니다:
# nGrinder 시작 (별도)
./start-ngrinder.sh시작되는 서비스:
- ✅ nGrinder Controller (8080 포트)
- ✅ nGrinder Agent
소요 시간: 약 3-4분 (M4 Mac에서 AMD64 에뮬레이션)
모든 서비스가 정상적으로 시작되었는지 확인합니다:
# 성능 테스트 대시보드
open http://localhost:8081
# Pinpoint (3-5분 후 사용 가능)
open http://localhost:8079
# RabbitMQ Management (perfuser/perfpass)
open http://localhost:15672
# HBase Master
open http://localhost:16010
# nGrinder (start-ngrinder.sh 실행 시)
open http://localhost:8080# Spring Boot 앱 헬스 체크
curl http://localhost:8081/api/health
# 성능 테스트 실행 (간단한 테스트)
curl "http://localhost:8081/api/performance-test/object-creation?iterations=100000"# 기본 시스템 종료 (start.sh로 시작한 서비스)
./stop.shnGrinder 종료 (별도):
# nGrinder 종료 스크립트
./stop-ngrinder.sh
# 옵션:
# 1) 컨테이너만 중지 (데이터 보존)
# 2) 컨테이너 삭제 (데이터 보존)
# 3) 완전 삭제 (데이터 포함)Pinpoint 완전 종료 (선택 사항):
# Pinpoint 종료 스크립트
./stop-pinpoint.sh
# 옵션:
# 1) 컨테이너만 중지 (데이터 보존)
# 2) 컨테이너 삭제 (데이터 보존)
# 3) 완전 삭제 (데이터 포함)Pinpoint 설치와 실행이 분리되어 있습니다:
| 스크립트 | 용도 | 실행 시기 |
|---|---|---|
./install-pinpoint.sh |
Pinpoint 설치 (저장소 클론, 버전 설정) | 최초 1회만 |
./start-pinpoint.sh |
Pinpoint 시작 (필수 구성만) | 필요할 때마다 |
./stop-pinpoint.sh |
Pinpoint 종료 (옵션 선택) | 종료 시 |
nGrinder 시작과 종료를 별도로 관리할 수 있습니다:
| 스크립트 | 용도 | 실행 시기 |
|---|---|---|
./start-ngrinder.sh |
nGrinder 시작 (Controller + Agent) | 부하 테스트 필요 시 |
./stop-ngrinder.sh |
nGrinder 종료 (옵션 선택) | 종료 시 |
| 스크립트 | 용도 | 시작되는 서비스 |
|---|---|---|
./start.sh |
기본 시스템 시작 | Pinpoint + MySQL + Redis + RabbitMQ + App |
./stop.sh |
기본 시스템 종료 | start.sh로 시작한 모든 서비스 |
Pinpoint만 별도로 관리:
# Pinpoint만 설치 (최초 1회)
./install-pinpoint.sh
# Pinpoint만 시작
./start-pinpoint.sh
# Pinpoint만 종료
./stop-pinpoint.shstart.sh 대신 수동으로 서비스를 선택적으로 시작:
# 1. 인프라만 시작 (MySQL, Redis, RabbitMQ)
docker compose up -d mysql redis rabbitmq
# 2. Spring Boot 앱만 시작
docker compose up -d app
# 또는 Gradle로 직접 실행
./gradlew bootRun
# 3. nGrinder만 시작
docker compose up -d ngrinder-controller ngrinder-agent하지만 처음 사용한다면 start.sh를 사용하는 것을 강력히 권장합니다!
프로젝트는 여러 가지 시작 스크립트를 제공합니다. 목적에 따라 선택하세요:
| 스크립트 | 용도 | 시작되는 서비스 | 포트 | 시작 시간 |
|---|---|---|---|---|
./start.sh |
🔥 기본 시스템 (권장) | MySQL, Redis, RabbitMQ, Spring Boot App, Pinpoint | 8079, 8081 | ~5분 |
./start-pinpoint.sh |
🔍 Pinpoint만 | Zookeeper, HBase, Collector, Web | 8079 | ~3-5분 |
./start-ngrinder.sh |
📊 nGrinder만 | Controller, Agent | 8080 | ~3분 |
docker-compose up -d |
📚 전체 Docker | MySQL, Redis, RabbitMQ, Spring Boot App, nGrinder | 8080, 8081 | ~2분 |
1️⃣ 기본 사용 (권장)
./start.sh # Pinpoint + 인프라 + Spring Boot App
./start-ngrinder.sh # nGrinder (필요 시)
./stop.sh # 전체 종료- ✅ 핵심 기능 모두 포함
- ✅ APM 분산 트레이싱 포함
- ✅ nGrinder는 필요할 때만 시작
- ⏱️ 시작 시간: ~5분 (Pinpoint 초기화)
2️⃣ 전체 Docker 실행
docker-compose up -d # 모든 서비스 (Pinpoint 제외)
docker-compose down # 종료- ✅ Spring Boot 앱: http://localhost:8081
- ✅ nGrinder: http://localhost:8080 (admin/admin)
- ⏱️ 시작 시간: ~2분
3️⃣ 개발 모드 (코드 수정 및 재시작)
# 인프라만 시작
docker-compose up -d mysql redis rabbitmq
# Gradle로 앱 실행 (빠른 재시작)
./gradlew bootRun4️⃣ 선택적 시작
# Pinpoint만 시작
./start-pinpoint.sh
# nGrinder만 시작
./start-ngrinder.sh
# 인프라 + 앱만 시작
docker-compose up -d mysql redis rabbitmq app./stop.sh # start.sh로 시작한 전체 시스템 종료
docker-compose down # docker-compose로 시작한 서비스 종료
docker-compose down -v # 데이터까지 완전 삭제nGrinder와 Pinpoint 이미지는 AMD64 기반이므로 M4 Mac에서 에뮬레이션으로 실행됩니다.
- 시작 시간: nGrinder 시작에 60-90초, Pinpoint HBase는 3-5분 소요
- 메모리: Docker Desktop에서 메모리 8GB 이상 할당 권장
- CPU: Rosetta 2 에뮬레이션으로 인해 CPU 사용량이 높을 수 있습니다
💡 Pinpoint 설치가 처음이라면 위의 🆕 처음부터 시작하기 섹션을 참조하세요!
# 전체 시스템 시작 (권장) - Pinpoint 포함
./start.sh
# Pinpoint 없이 시작 (빠른 시작)
docker-compose up -d
# 로그 확인
docker-compose logs -f app# 전체 시스템 종료 (Pinpoint 포함)
./stop.sh
# docker-compose 서비스만 종료
docker-compose down
# 데이터까지 완전 삭제
docker-compose down -v
cd ../pinpoint-docker-official && docker compose down -v# 서비스 상태
docker-compose ps
# Pinpoint 상태
cd ../pinpoint-docker-official && docker compose ps
# 특정 서비스 로그
docker-compose logs -f mysql
docker-compose logs -f app- URL: http://localhost:8080
- 계정: admin / admin
- 용도: 부하 테스트 시나리오 생성 및 실행
- URL: http://localhost:8079
- 애플리케이션 이름: EffectiveJavaPerformance
- Agent ID: perf-app-docker
- 용도: 분산 트레이싱, 서비스 맵, 응답 시간 분석, JVM 모니터링
Pinpoint는 실제 트래픽이 발생해야만 서비스 맵과 연결 정보를 표시합니다:
- 트래픽 없음 → 아무것도 표시 안됨
- 트래픽 발생 → 서비스 맵, 연결 정보 표시
데이터 수집 및 표시까지 1-2분 정도 소요될 수 있습니다.
# MySQL 사용 API (전체 상품 조회)
curl http://localhost:8081/api/products
# Redis 캐시 사용 API (상품 상세 조회)
for i in {1..10}; do
curl http://localhost:8081/api/products/$i
done
# RabbitMQ 사용 API (메시지 전송)
for i in {1..5}; do
curl -X POST http://localhost:8081/api/messages \
-H "Content-Type: application/json" \
-d "{\"type\":\"test\",\"data\":\"message $i\"}"
done
# 모든 서비스 사용 (통합 테스트)
curl http://localhost:8081/api/products # MySQL
curl http://localhost:8081/api/products/1 # Redis
curl -X POST http://localhost:8081/api/messages \
-H "Content-Type: application/json" \
-d '{"type":"test","data":"hello"}' # RabbitMQ- Pinpoint 접속: http://localhost:8079
- 애플리케이션 선택: 왼쪽 상단 드롭다운에서 "EffectiveJavaPerformance" 선택
- 시간 범위 설정: 오른쪽 상단에서 최근 5분/1시간 등 선택
- 서비스 맵 확인:
- 중앙의 서비스 맵에서
EffectiveJavaPerformance노드 확인 - MySQL (MYSQL_EXECUTE_QUERY), Redis (REDIS), RabbitMQ (RABBITMQ) 연결 확인
- 중앙의 서비스 맵에서
- Scatter Chart: 하단에서 응답 시간 분포 확인
- 트랜잭션 상세: Scatter Chart의 점을 클릭하여 상세 호출 스택 분석
트래픽 생성 후 다음과 같은 연결이 표시됩니다:
[USER] → [EffectiveJavaPerformance] → [MYSQL_EXECUTE_QUERY]
→ [REDIS]
→ [RABBITMQ]
# 1. 트래픽을 충분히 생성했는지 확인
curl http://localhost:8081/api/products # MySQL
curl http://localhost:8081/api/products/1 # Redis
curl -X POST http://localhost:8081/api/messages \
-H "Content-Type: application/json" \
-d '{"type":"test","data":"test"}' # RabbitMQ
# 2. 1-2분 대기 후 Pinpoint 새로고침
# 3. Pinpoint Collector 로그 확인
cd pinpoint-docker-official
docker logs pinpoint-collector | tail -50
# 4. App에서 데이터 전송 확인
docker logs perf-app | grep -i "grpc\|span\|trace"- URL: http://localhost:15672
- 계정: perfuser / perfpass
- 용도: 메시지 큐 모니터링
- URL: http://localhost:16010
- 용도: Pinpoint 데이터 저장소 모니터링
# 샘플 CSV 데이터 생성 (10,000건)
curl -X POST "http://localhost:8081/api/batch/sample-data?count=10000"
# 50,000건 생성
curl -X POST "http://localhost:8081/api/batch/sample-data?count=50000"
# 배치 성능 비교 실행 (4가지 방식)
curl -X POST "http://localhost:8081/api/batch/compare" | jq
# 개별 배치 실행
curl -X POST "http://localhost:8081/api/batch/slow-v1" # 전체 메모리 로드 + 단일 INSERT
curl -X POST "http://localhost:8081/api/batch/slow-v2" # Auto-commit
curl -X POST "http://localhost:8081/api/batch/slow-v3" # JPA saveAll()
curl -X POST "http://localhost:8081/api/batch/fast" # Streaming + JDBC Batch# 동기 처리 (느림)
curl -X POST "http://localhost:8081/api/cancellation/sync" \
-H "Content-Type: application/json" \
-d '{"memberId":"M001","reason":"서비스 불만족"}'
# 비동기 처리 (빠름)
curl -X POST "http://localhost:8081/api/cancellation/async" \
-H "Content-Type: application/json" \
-d '{"memberId":"M002","reason":"이사"}'
# 통계 조회
curl "http://localhost:8081/api/cancellation/stats" | jq
# 통계 초기화
curl -X DELETE "http://localhost:8081/api/cancellation/stats"
# RabbitMQ 대기 메시지 수 확인
curl "http://localhost:8081/api/cancellation/queue/count" | jq# 테스트 상품 생성 (ID 1번)
curl -X POST "http://localhost:8081/api/product/view/create-test-product" | jq
# DB 직접 방식 (Row Lock 발생)
curl "http://localhost:8081/api/product/view/db/1" | jq
# Redis Write-Back 방식 (Lock 없음)
curl "http://localhost:8081/api/product/view/redis/1" | jq
# 통계 조회
curl "http://localhost:8081/api/product/view/stats" | jq
# Redis 대기 중인 조회수 확인
curl "http://localhost:8081/api/product/view/pending/1" | jq
# 강제 동기화 (Redis → DB)
curl -X POST "http://localhost:8081/api/product/view/sync"
# 통계 초기화
curl -X DELETE "http://localhost:8081/api/product/view/stats"# nGrinder 시작 (Controller + Agent)
./start-ngrinder.sh
# nGrinder 접속
open http://localhost:8080
# 계정: admin / admin스크립트 위치: ngrinder-scripts/
-
CancellationSyncTest.groovy - 동기 처리 API 테스트
// 동기 방식: DB 저장 완료 후 응답 // 예상 결과: TPS 낮음, 응답시간 높음 @Test public void test() { HTTPResponse response = request.POST("http://perf-app:8081/api/cancellation/sync", ...) // Mean Test Time: ~1,500ms (부하 시) // TPS: ~10-20 }
-
CancellationAsyncTest.groovy - 비동기 처리 API 테스트
// 비동기 방식: 즉시 응답, RabbitMQ로 전달 // 예상 결과: TPS 높음, 응답시간 낮음 @Test public void test() { HTTPResponse response = request.POST("http://perf-app:8081/api/cancellation/async", ...) // Mean Test Time: ~10ms // TPS: 100+ }
테스트 시나리오:
- Virtual Users: 50명
- Duration: 3분
- Ramp-Up: 처음 1분간 점진적 증가
- 목표: 동기 vs 비동기 응답시간 및 TPS 비교
스크립트 위치: ngrinder-scripts/
-
ProductViewDbTest.groovy - DB 직접 방식 부하 테스트
// DB 직접: UPDATE + SELECT (Row Lock 발생) // 예상 결과: Lock 경합으로 인한 응답시간 증가 @Test public void test() { HTTPResponse response = request.GET("http://perf-app:8081/api/product/view/db/1") // Mean Test Time: 500~1,000ms (100 TPS 시) // Row Lock 대기 시간 증가 }
-
ProductViewRedisTest.groovy - Redis Write-Back 부하 테스트
// Redis Write-Back: INCR + SELECT (Lock 없음) // 예상 결과: Lock 경합 없어 응답시간 안정적 @Test public void test() { HTTPResponse response = request.GET("http://perf-app:8081/api/product/view/redis/1") // Mean Test Time: 5~10ms (100 TPS 시) // 일정한 응답시간 유지 }
테스트 시나리오:
- Virtual Users: 100명
- Duration: 5분
- Target TPS: 100 (동일 상품에 집중)
- 목표: Row Lock 경합 효과 및 Redis Write-Back의 성능 개선 확인
- nGrinder 접속: http://localhost:8080 (admin/admin)
- 좌측 메뉴 Script 클릭
- Create a script 클릭
ngrinder-scripts/폴더의 Groovy 파일 내용 복사 붙여넣기- Validate → Save 클릭
- 좌측 메뉴 Performance Test 클릭
- Create Test 클릭
- 설정:
- Agent: 1
- Vuser per agent: 50~100
- Script: 등록한 스크립트 선택
- Duration: 3~5분
- Run Count: Infinite 체크
- Save and Start 클릭
비교 지표:
- TPS (Transaction Per Second): 처리량
- Mean Test Time: 평균 응답시간
- Peak TPS: 최대 처리량
- Error Rate: 에러 발생률
예상 결과:
| Case Study | 방식 | TPS | 응답시간 | 에러율 |
|---|---|---|---|---|
| 비동기 처리 | 동기 | ~10-20 | 1,500ms | ~5% |
| 비동기 | 100+ | 10ms | 0% | |
| Redis 캐시 | DB 직접 | 50-70 | 500~1,000ms | ~10% |
| Redis Write-Back | 100+ | 5~10ms | 0% |
-H "Content-Type: application/json"
-d '{"to":"user@example.com","subject":"test"}'
curl -X POST "http://localhost:8081/api/messages/bulk?count=100"
curl -X POST "http://localhost:8081/api/messages/parallel?count=100"
## 📝 nGrinder 테스트 스크립트
### 📂 제공되는 스크립트
프로젝트는 **3가지 nGrinder 테스트 스크립트**를 제공합니다 (`ngrinder-scripts/` 디렉토리):
| 스크립트 | 설명 | 추천 설정 | 난이도 |
|---------|------|----------|--------|
| **ProductInsertSimpleTest.groovy** | Product INSERT 간단 테스트 | VUser 1, 1분 | 🟢 초급 |
| **ProductInsertTest.groovy** | Product INSERT 고급 테스트 | VUser 10, 5분 | 🟡 중급 |
| **ProductMixedScenarioTest.groovy** | CRUD 혼합 시나리오 | VUser 50, 10분 | 🔴 고급 |
### 🚀 사용법
#### 1. nGrinder 접속
```bash
open http://localhost:8080
# ID: admin, PW: admin
- Script 메뉴 →
Create a script - Script Name:
ProductInsertTest ngrinder-scripts/ProductInsertTest.groovy파일 내용 복사- Validate → Save
- Performance Test →
Create Test - 설정:
- Agent: 1
- Vuser per agent: 10
- Script: ProductInsertTest
- Duration: 5m
- Save and Start
- 실시간 TPS, 응답 시간 모니터링
- Pinpoint (http://localhost:8079)에서 트레이싱 확인
- Detailed Report에서 상세 분석
| 시나리오 | VUser | 예상 TPS | 응답 시간 | 목적 |
|---|---|---|---|---|
| Simple (Smoke) | 1 | 60-100 | 10-20ms | 기본 동작 확인 |
| Insert (Load) | 10 | 500-1000 | 20-50ms | 일반 부하 테스트 |
| Mixed (Stress) | 50 | 3000-8000 | 30-80ms | 실전 시나리오 |
NGRINDER_PRODUCT_TEST_GUIDE.md: 전체 테스트 가이드ngrinder-scripts/README.md: 스크립트 요약
# 캐시 사용 (Redis)
curl http://localhost:8081/api/products/1
# 캐시 미사용 (DB 직접)
curl http://localhost:8081/api/products/1/no-cache# 동기 처리
curl -X POST "http://localhost:8081/api/messages/bulk?count=100"
# 비동기 병렬 처리
curl -X POST "http://localhost:8081/api/messages/parallel?count=100"- 서비스 호출 체인 확인
- 슬로우 쿼리 감지
- 예외 발생 지점 추적
# 1. 컨테이너 시작 상태 확인
docker-compose ps | grep ngrinder
# 2. 로그에서 "Started NGrinderControllerStarter" 확인
docker-compose logs ngrinder-controller 2>&1 | grep -i "started"
# 3. M4 Mac에서는 시작에 3-4분 소요됨 - 충분히 기다리기
# healthcheck start_period: 180초 설정됨
# 4. start-ngrinder.sh 사용 (권장)
./start-ngrinder.sh # 자동으로 대기 및 상태 확인
# 5. 시작 완료 후에도 접속 안되면 재시작
docker-compose restart ngrinder-controller# Docker Desktop 설정에서 메모리 8GB 이상 권장
# 또는 불필요한 서비스 중지
docker-compose stop pinpoint-hbase pinpoint-collector pinpoint-web# 1. Pinpoint 디렉토리로 이동
cd pinpoint-docker-official
# 2. 서비스 상태 확인
docker compose ps
# 3. HBase가 정상 실행 중인지 확인 (테이블 15개 생성 확인)
docker logs pinpoint-hbase | tail -20
# 4. Pinpoint Web 로그 확인
docker logs pinpoint-web | tail -50
# 5. Collector 상태 확인
docker logs pinpoint-collector | tail -50
# 6. 모든 서비스 재시작
docker compose restart pinpoint-hbase pinpoint-collector pinpoint-web
# 7. HBase 초기화 대기 (3-5분)
# 이후 Collector, Web 자동 연결됨원인: 최신 버전의 Pinpoint는 M4 Mac에서 HBase Zookeeper 설정 문제 발생
해결 방법:
# 1. 완전히 삭제
cd pinpoint-docker-official
docker compose down -v
# 2. 반드시 2.5.4 버전 사용
git fetch --all --tags
git checkout 2.5.4
# 3. 재시작
docker compose pull
docker compose up -d
# 4. HBase 초기화 확인 (3-5분 소요)
docker logs -f pinpoint-hbase# 1. Spring Boot App이 pinpoint 네트워크에 연결되었는지 확인
docker inspect perf-app | grep -A 10 Networks
# 2. Collector IP 확인
docker inspect pinpoint-collector | grep IPAddress
# 3. App 로그에서 Pinpoint 연결 확인
docker logs perf-app | grep -i pinpoint
# 4. Collector가 정상인지 확인
docker logs pinpoint-collector | grep -i "started"# Controller 먼저 완전히 시작될 때까지 대기
docker-compose logs ngrinder-controller
# Agent 재시작
docker-compose restart ngrinder-agentperformance/
├── docker-compose.yaml # 메인 서비스 (App, MySQL, Redis, RabbitMQ, nGrinder)
├── Dockerfile # Spring Boot 앱 + Pinpoint Agent
├── build.gradle.kts # Gradle 빌드 설정
├── start.sh # 기본 시스템 시작 (Pinpoint + 인프라 + App)
├── stop.sh # 기본 시스템 종료
├── install-pinpoint.sh # Pinpoint 설치 (최초 1회)
├── start-pinpoint.sh # Pinpoint 시작
├── stop-pinpoint.sh # Pinpoint 종료
├── start-ngrinder.sh # nGrinder 시작
├── stop-ngrinder.sh # nGrinder 종료
├── docker/
│ ├── mysql/
│ │ └── init/
│ │ └── 01-init.sql # DB 초기화 스크립트
│ └── pinpoint/
│ └── hbase-site.xml # HBase 설정 (레거시, 사용 안함)
└── src/
└── main/
├── java/com/skplanet/performance/
│ ├── PerformanceApplication.java
│ ├── config/ # Redis, RabbitMQ 설정
│ ├── controller/ # REST API 컨트롤러
│ │ ├── PerformanceTestController.java # 통합 컨트롤러
│ │ ├── ProductController.java
│ │ ├── MessageController.java
│ │ └── HealthController.java
│ ├── entity/ # JPA 엔티티
│ ├── repository/ # 데이터 접근 계층
│ └── service/ # 비즈니스 로직
└── resources/
├── application.properties # 로컬 설정
├── application-docker.properties # Docker 환경 설정
├── templates/
│ ├── index.html # 메인 대시보드
│ └── result.html # 결과 페이지
└── static/
└── quickstart.html # 빠른 시작 가이드
pinpoint-docker-official/ # Pinpoint 공식 저장소 (git clone 필요)
├── docker-compose.yml # Pinpoint 서비스 정의
├── .env # 환경 변수 (WEB_SERVER_PORT=8079)
├── pinpoint-hbase/
├── pinpoint-collector/
├── pinpoint-web/
└── ...
Pinpoint 설치 방법:
git clone https://github.com/pinpoint-apm/pinpoint-docker.git pinpoint-docker-official
cd pinpoint-docker-official
git checkout 2.5.4주요 변경사항:
-
✅ install-pinpoint.sh: Pinpoint 설치만 수행 (최초 1회)
- 저장소 클론, 버전 체크아웃, 포트 변경
- 컨테이너 시작 안 함
-
✅ start-pinpoint.sh: Pinpoint 시작 (필수 구성만)
- MySQL, Batch, Flink 자동 제외
- 3306 포트 충돌 방지
-
✅ stop-pinpoint.sh: Pinpoint 종료 (3가지 옵션)
-
- 중지만 2) 삭제 3) 완전삭제
-
-
✅ start-ngrinder.sh: nGrinder 별도 시작
- start.sh에서 분리
- 240초 대기 (M4 Mac AMD64 에뮬레이션 대응)
- 자동 healthcheck 및 상태 확인
-
✅ stop-ngrinder.sh: nGrinder 종료 (3가지 옵션)
- 데이터 보존/삭제 선택 가능
-
✅ nGrinder 제거: start.sh는 기본 시스템만 시작
- Pinpoint + MySQL + Redis + RabbitMQ + App
- nGrinder는
./start-ngrinder.sh로 별도 실행
-
✅ 헬스체크 강화: Spring Boot 앱 시작 확인
- 최대 60초 대기
/actuator/health엔드포인트 체크
- ✅ Whitelabel Error Page 해결
- templates 파일이 JAR에 정상 포함
- http://localhost:8081 정상 작동
- index.html, result.html 표시 정상
- ✅ start_period: 60초 → 180초
- M4 Mac AMD64 에뮬레이션 시간 고려
- Controller healthy 상태까지 3-4분 대기
주요 변경사항:
-
✅ 컨트롤러 통합:
PerformanceTestViewController→PerformanceTestController로 통합- 웹 페이지와 REST API를 하나의 컨트롤러에서 처리
- 코드 중복 제거 및 유지보수성 향상
-
✅ Product 성능 테스트 추가: ProductController의 성능 테스트 기능을 웹 대시보드에 통합
- CPU 집약적 연산 테스트
- 메모리 집약적 연산 테스트
- 느린 쿼리 시뮬레이션
-
✅ 포트 정보:
- 8079: Pinpoint Web
- 8080: nGrinder Controller
- 8081: Performance Test App ⭐
- 15672: RabbitMQ Management
-
✅ 데이터베이스 의존성 제거: 성능 테스트만 사용 시 DB 없이 실행 가능
- MySQL, Redis, RabbitMQ 없이도 Effective Java 테스트 가능
spring.autoconfigure.exclude설정으로 선택적 비활성화
# 성능 테스트만 사용 (DB 불필요)
./gradlew bootRun
# 브라우저 접속
open http://localhost:8081Effective Java 테스트 (4가지)
- Item 6: 불필요한 객체 생성
- Item 6: Pattern 캐싱
- Item 61: 기본 타입 vs 박싱
- Item 63: 문자열 연결
Product 성능 테스트 (3가지)
- CPU 집약적 연산
- 메모리 집약적 연산
- 느린 쿼리 (DB 필요)
WEB_GUIDE.md: 웹 대시보드 상세 가이드URL_GUIDE.md: 접속 URL 및 포트 정보
START_SCRIPT_GUIDE.md: 시작 스크립트 선택 가이드PINPOINT_SCRIPTS_GUIDE.md: Pinpoint 관리 스크립트 상세
PROJECT_SUMMARY.md: 전체 프로젝트 요약PERFORMANCE_TEST_README.md: 성능 테스트 상세 문서
| URL | 설명 |
|---|---|
| http://localhost:8081 | 메인 대시보드 |
| http://localhost:8081/api/performance-test/all-tests | REST API |
| http://localhost:8081/quickstart.html | 빠른 시작 가이드 |
Happy Learning! 🚀