서비스의 한계를 실험해봐야 추후 프리즈 되는 상황을 대처한 개발을 가능
export API_SERVER_IP="[본인의_API_SERVER_퍼블릭_IP]"
export MONITORING_SERVER_IP="[본인의_MONITORING_SERVER_퍼블릭_IP]"
#!/bin/bash
set -e
echo "=== Spring Boot API Server Setup (병렬 처리) ==="
# 기본 패키지 설치
sudo apt update -y
sudo apt install -y openjdk-17-jdk maven git curl
# 프로젝트 구조 생성
mkdir -p ~/loadtest-app && cd ~/loadtest-app
mkdir -p loadtest-api/src/main/java/com/loadtest
mkdir -p loadtest-api/src/main/resources
cd loadtest-api
# pom.xml 생성
cat > pom.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.loadtest</groupId>
<artifactId>loadtest-api</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
EOF
# Application 클래스 생성
cat > src/main/java/com/loadtest/LoadTestApplication.java << 'EOF'
package com.loadtest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LoadTestApplication {
public static void main(String[] args) {
SpringApplication.run(LoadTestApplication.class, args);
}
}
EOF
# Controller 생성 (병렬 처리 + 병목 시나리오)
cat > src/main/java/com/loadtest/LoadTestController.java << 'EOF'
package com.loadtest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@RestController
public class LoadTestController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private Executor taskExecutor;
// API 1 - 병목 지점 (50ms)
@GetMapping("/api/1")
public Map<String, Object> api1() throws InterruptedException {
Thread.sleep(50); // 50ms - 심각한 병목
Map<String, Object> response = new HashMap<>();
response.put("endpoint", "api1");
response.put("processingTime", "50ms");
response.put("timestamp", System.currentTimeMillis());
response.put("role", "심각한 병목 - 50ms");
return response;
}
// API 2 - 중간 성능 (2ms)
@GetMapping("/api/2")
public Map<String, Object> api2() throws InterruptedException {
Thread.sleep(2); // 2ms
Map<String, Object> response = new HashMap<>();
response.put("endpoint", "api2");
response.put("processingTime", "2ms");
response.put("timestamp", System.currentTimeMillis());
return response;
}
// API 3 - 빠른 성능 (1ms)
@GetMapping("/api/3")
public Map<String, Object> api3() throws InterruptedException {
Thread.sleep(1); // 1ms
Map<String, Object> response = new HashMap<>();
response.put("endpoint", "api3");
response.put("processingTime", "1ms");
response.put("timestamp", System.currentTimeMillis());
return response;
}
// 비즈니스 플로우 - 병렬 호출
@GetMapping("/api/business-flow")
public Map<String, Object> businessFlow() {
long startTime = System.currentTimeMillis();
String baseUrl = "http://localhost:8080";
try {
// 3개 API를 병렬로 동시 호출
CompletableFuture<Map> future1 = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject(baseUrl + "/api/1", Map.class), taskExecutor);
CompletableFuture<Map> future2 = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject(baseUrl + "/api/2", Map.class), taskExecutor);
CompletableFuture<Map> future3 = CompletableFuture.supplyAsync(() ->
restTemplate.getForObject(baseUrl + "/api/3", Map.class), taskExecutor);
// 가장 오래 걸리는 API까지 대기 (api/1의 50ms)
CompletableFuture.allOf(future1, future2, future3).join();
} catch (Exception e) {
System.err.println("Parallel API call failed: " + e.getMessage());
}
long endTime = System.currentTimeMillis();
Map<String, Object> response = new HashMap<>();
response.put("endpoint", "business-flow");
response.put("executionType", "병렬 처리");
response.put("description", "api/1 || api/2 || api/3 동시 실행");
response.put("totalProcessingTime", (endTime - startTime) + "ms");
response.put("timestamp", endTime);
response.put("bottleneck", "api/1 (50ms) - 심각한 성능 저하");
response.put("theoreticalMaxTPS", "약 20 TPS (1000ms / 50ms)");
response.put("improvementPotential", "api/1을 2ms로 개선 시 500 TPS 달성 (25배 향상!)");
return response;
}
@GetMapping("/health")
public Map<String, String> health() {
Map<String, String> response = new HashMap<>();
response.put("status", "UP");
return response;
}
}
@Configuration
@EnableAsync
class RestConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("parallel-api-");
executor.initialize();
return executor;
}
}
EOF
# application.yml 설정
cat > src/main/resources/application.yml << 'EOF'
server:
port: 8080
tomcat:
threads:
max: 300
min-spare: 20
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
base-path: /actuator
endpoint:
health:
show-details: always
metrics:
enabled: true
prometheus:
enabled: true
server:
port: 8081
logging:
level:
com.loadtest: INFO
org.springframework: WARN
org.springframework.web: DEBUG
EOF
# 빌드 및 실행
echo "=== Building Application ==="
mvn clean package -DskipTests
if [ $? -eq 0 ]; then
echo "Build SUCCESS"
nohup java -Xmx512m -Xms256m -jar target/loadtest-api-1.0.0.jar > app.log 2>&1 &
echo $! > app.pid
echo "=== Waiting for Application Startup ==="
sleep 15
echo "=== Testing Endpoints ==="
if curl -s http://localhost:8080/health | grep -q "UP"; then
echo "Health Check OK"
# 각 API 개별 테스트
echo "개별 API 테스트:"
echo -n "api/1: "; curl -s http://localhost:8080/api/1 | jq -r '.processingTime // "OK"'
echo -n "api/2: "; curl -s http://localhost:8080/api/2 | jq -r '.processingTime // "OK"'
echo -n "api/3: "; curl -s http://localhost:8080/api/3 | jq -r '.processingTime // "OK"'
echo ""
echo "Spring Boot API Server Ready!"
echo ""
echo "병렬 처리 시나리오: 가장 느린 api/1이 전체 성능 결정"
else
echo "Application failed to start"
cat app.log
exit 1
fi
else
echo "Build FAILED"
exit 1
fi
# 성능 개선 시뮬레이션 스크립트 생성
cat > improve-api1.sh << 'EOF'
#!/bin/bash
echo "=== API/1 성능 개선 시뮬레이션 ==="
./stop.sh
# api/1의 성능을 50ms에서 2ms로 개선
sed -i 's/Thread.sleep(50); \/\/ 50ms - 심각한 병목/Thread.sleep(2); \/\/ 2ms - 개선완료/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"processingTime", "50ms"/"processingTime", "2ms"/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"role", "심각한 병목 - 50ms"/"role", "개선완료 - 최적화됨"/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"bottleneck", "api\/1 (50ms) - 심각한 성능 저하"/"bottleneck", "없음 - 모든 API 최적화됨"/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"theoreticalMaxTPS", "약 20 TPS (1000ms \/ 50ms)"/"theoreticalMaxTPS", "약 500 TPS (1000ms \/ 2ms)"/g' src/main/java/com/loadtest/LoadTestController.java
sed -i 's/"improvementPotential", "api\/1을 2ms로 개선 시 500 TPS 달성 (25배 향상!)"/"improvement", "api\/1 최적화 완료 - 25배 성능 향상 달성"/g' src/main/java/com/loadtest/LoadTestController.java
mvn clean package -DskipTests
nohup java -Xmx512m -Xms256m -jar target/loadtest-api-1.0.0.jar > app.log 2>&1 &
echo $! > app.pid
sleep 10
EOF
# 관리 스크립트들 생성
cat > stop.sh << 'EOF'
#!/bin/bash
if [ -f app.pid ]; then
sudo kill $(cat app.pid) 2>/dev/null
rm app.pid
echo "Application stopped"
else
echo "No application running"
fi
EOF
cat > status.sh << 'EOF'
#!/bin/bash
echo "=== Application Status ==="
if [ -f app.pid ]; then
PID=$(cat app.pid)
if ps -p $PID > /dev/null; then
echo "Application running (PID: $PID)"
echo "Health: $(curl -s http://localhost:8080/health)"
else
echo "PID file exists but process not running"
fi
else
echo "Application not running"
fi
EOF
chmod +x stop.sh improve-api1.sh status.sh
echo ""
echo "Management Commands:"
echo "- Status: ./status.sh"
echo "- Stop: ./stop.sh"
echo "- Improve API1: ./improve-api1.sh (333→500 TPS)"
echo "- Logs: tail -f app.log"
#!/bin/bash
set -e
echo "=== Monitoring Server Setup ==="
# 기본 패키지 설치
sudo apt update -y
sudo apt install -y wget curl apt-transport-https ca-certificates gnupg lsb-release gettext-base jq
# 작업 디렉토리 생성
mkdir -p ~/monitoring && cd ~/monitoring
# Docker 설치
echo "=== Installing Docker ==="
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update -y
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ubuntu
# k6 설치
echo "=== Installing k6 ==="
sudo snap install k6
k6 version
# API 서버 연결성 확인
echo "=== Testing API Server Connection ==="
if curl -s --connect-timeout 10 http://${API_SERVER_IP}:8080/health | grep -q "UP"; then
echo "API Server is accessible"
# 각 API 응답시간 확인
echo "API 응답시간 확인:"
curl -s http://${API_SERVER_IP}:8080/api/1 | jq -r '"api/1: " + .processingTime'
curl -s http://${API_SERVER_IP}:8080/api/2 | jq -r '"api/2: " + .processingTime'
curl -s http://${API_SERVER_IP}:8080/api/3 | jq -r '"api/3: " + .processingTime'
else
echo "API Server not accessible - check security group"
exit 1
fi
# Prometheus 설정
cat > prometheus.yml.template << 'EOF'
global:
scrape_interval: 5s
evaluation_interval: 5s
scrape_configs:
- job_name: 'spring-boot-app'
static_configs:
- targets: ['${API_SERVER_IP}:8081']
metrics_path: /actuator/prometheus
scrape_interval: 2s
EOF
envsubst < prometheus.yml.template > prometheus.yml
# Docker Compose 설정
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-storage:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-storage:/var/lib/grafana
depends_on:
- prometheus
restart: unless-stopped
volumes:
prometheus-storage:
grafana-storage:
EOF
# k6 테스트 스크립트 생성
cat > business-flow-test.js << 'EOF'
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 50 }, // 워밍업
{ duration: '2m', target: 100 }, // 점진적 증가
{ duration: '3m', target: 200 }, // 높은 부하
{ duration: '2m', target: 300 }, // 피크 부하 (병목 확인)
{ duration: '1m', target: 400 }, // 한계 테스트
{ duration: '1m', target: 0 }, // 종료
],
thresholds: {
http_req_duration: ['p(95)<100'], // 엄격한 기준
http_req_failed: ['rate<0.01'],
},
};
const API_BASE_URL = `http://${__ENV.API_SERVER_IP}:8080`;
export default function () {
const response = http.get(`${API_BASE_URL}/api/business-flow`);
check(response, {
'business-flow status is 200': (r) => r.status === 200,
'business-flow response time < 50ms': (r) => r.timings.duration < 50,
});
sleep(0.01); // 매우 짧은 think time으로 높은 부하 생성
}
EOF
# 실행 스크립트들 생성
cat > run-business-flow-test.sh << 'EOF'
#!/bin/bash
echo "======================================================"
echo "병렬 처리 병목 분석 부하테스트"
echo "======================================================"
echo "Target: http://${API_SERVER_IP}:8080/api/business-flow"
echo "Monitor: http://${MONITORING_SERVER_IP}:3000"
read -p "Press Enter to start load test..."
API_SERVER_IP=${API_SERVER_IP} k6 run business-flow-test.js
EOF
cat > run-with-dashboard.sh << 'EOF'
#!/bin/bash
echo "======================================================"
echo "k6 실시간 대시보드 포함 테스트"
echo "======================================================"
echo ""
echo "k6 Dashboard: http://${MONITORING_SERVER_IP}:5665"
echo "Grafana: http://${MONITORING_SERVER_IP}:3000"
API_SERVER_IP=${API_SERVER_IP} k6 run --out web-dashboard=port=5665 business-flow-test.js
EOF
chmod +x run-business-flow-test.sh run-with-dashboard.sh
# 모니터링 스택 시작
echo "=== Starting Monitoring Stack ==="
sudo docker compose up -d
echo "=== Waiting for services to start ==="
sleep 30
echo "=== Service Status ==="
sudo docker ps
# 초기 메트릭 생성
echo ""
echo "=== Generating Initial Metrics ==="
echo "API 호출로 Grafana 메트릭 생성..."
# 초기 메트릭 생성
echo ""
echo "=== Generating Initial Metrics ==="
echo "API 호출로 Grafana 메트릭 생성..."
for i in {1..10}; do
curl -s http://${API_SERVER_IP}:8080/api/business-flow > /dev/null
echo "API call $i completed"
sleep 1
done
echo ""
echo "======================================================"
echo "Monitoring Server Setup Complete!"
echo "======================================================"
echo ""
echo "Access URLs:"
echo "- Prometheus: http://${MONITORING_SERVER_IP}:9090"
echo "- Grafana: http://${MONITORING_SERVER_IP}:3000 (admin/admin)"
echo ""
echo "Load Test Commands:"
echo "- ./run-with-dashboard.sh"
echo ""

rate(http_server_requests_seconds_count{uri="/api/business-flow"}[1m])rate(http_server_requests_seconds_sum{uri="/api/business-flow"}[1m]) / rate(http_server_requests_seconds_count{uri="/api/business-flow"}[1m]) * 1000/monitoring/run-with-dashboard.sh 를 실행rate(http_server_requests_seconds_sum[1m]) / rate(http_server_requests_seconds_count[1m]) * 1000

API마다 몇ms걸렸는지 부하테스트를 걸어서, 특정 api에서 어떤 상황에서 오래걸리는지를 미리 파악하여 성능을 개선할 수 있음
대시보드에서 import dashboard, 대시보드로 가면 사람들이 만든 그라파나 대시보드를 다양하게 있음. 이걸 다운 가능
