signal-batch/scripts/run-load-test.sh
htlee 2e9361ee58 refactor: SNP API 전환 및 레거시 코드 전면 정리
- CollectDB 다중 신호 수집 → S&P Global AIS API 단일 수집으로 전환
- sig_src_cd + target_id 이중 식별자 → mmsi(VARCHAR) 단일 식별자
- t_vessel_latest_position → t_ais_position 테이블 전환
- 레거시 배치/유틸 ~30개 클래스 삭제 (VesselAggregationJobConfig, ShipKindCodeConverter 등)
- AisTargetCacheManager 기반 캐시 이중 구조 (최신위치 + 트랙 버퍼)
- CacheBasedVesselTrackDataReader + CacheBasedTrackJobListener 신규 추가
- VesselStaticStepConfig: 정적정보 CDC 변경 검출 + hourly job 편승
- SignalKindCode enum: vesselType/extraInfo 기반 선종 자동 분류
- WebSocket/STOMP 전체 mmsi 전환 (StompTrackStreamingService ~40곳)
- 모니터링/성능 최적화 코드 mmsi 기반 전환
- DataSource 설정 통합 (snpdb 단일 DB)
- AreaBoundaryCache Polygon→Geometry 캐스트 수정 (MULTIPOLYGON 지원)
- ConcurrentHashMap 적용 (VesselTrackStepConfig 동시성 버그 수정)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:59:49 +09:00

289 lines
9.8 KiB
Bash

#!/bin/bash
# 선박 궤적 집계 시스템 부하 테스트 실행 스크립트
# 실행 전 JMeter가 설치되어 있어야 합니다.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
JMETER_HOME="${JMETER_HOME:-/opt/jmeter}"
RESULTS_DIR="$PROJECT_ROOT/load-test-results"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 색상 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 함수: 메시지 출력
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# JMeter 설치 확인
check_jmeter() {
if [ ! -d "$JMETER_HOME" ]; then
log_error "JMeter가 설치되어 있지 않습니다. JMETER_HOME을 설정하세요."
exit 1
fi
if [ ! -f "$JMETER_HOME/bin/jmeter" ]; then
log_error "JMeter 실행 파일을 찾을 수 없습니다: $JMETER_HOME/bin/jmeter"
exit 1
fi
log_info "JMeter 경로: $JMETER_HOME"
}
# 결과 디렉토리 생성
create_results_dir() {
mkdir -p "$RESULTS_DIR/$TIMESTAMP"
log_info "결과 디렉토리 생성: $RESULTS_DIR/$TIMESTAMP"
}
# 시스템 상태 모니터링 시작
start_monitoring() {
log_info "시스템 모니터링 시작..."
# CPU, 메모리, 네트워크 사용률 모니터링
nohup vmstat 5 > "$RESULTS_DIR/$TIMESTAMP/vmstat.log" 2>&1 &
VMSTAT_PID=$!
nohup iostat -x 5 > "$RESULTS_DIR/$TIMESTAMP/iostat.log" 2>&1 &
IOSTAT_PID=$!
# 데이터베이스 연결 모니터링
nohup watch -n 5 "psql -h 10.26.252.48 -U mdauser -d mdadb -c 'SELECT count(*) FROM pg_stat_activity;'" > "$RESULTS_DIR/$TIMESTAMP/db_connections.log" 2>&1 &
DB_MON_PID=$!
echo "$VMSTAT_PID $IOSTAT_PID $DB_MON_PID" > "$RESULTS_DIR/$TIMESTAMP/monitoring.pids"
}
# 시스템 모니터링 중지
stop_monitoring() {
log_info "시스템 모니터링 중지..."
if [ -f "$RESULTS_DIR/$TIMESTAMP/monitoring.pids" ]; then
while read pid; do
kill $pid 2>/dev/null
done < "$RESULTS_DIR/$TIMESTAMP/monitoring.pids"
rm "$RESULTS_DIR/$TIMESTAMP/monitoring.pids"
fi
}
# JMeter 테스트 실행
run_jmeter_test() {
local test_file=$1
local test_name=$(basename "$test_file" .jmx)
log_info "JMeter 테스트 실행: $test_name"
# JMeter 실행
"$JMETER_HOME/bin/jmeter" \
-n \
-t "$test_file" \
-l "$RESULTS_DIR/$TIMESTAMP/${test_name}-results.jtl" \
-e \
-o "$RESULTS_DIR/$TIMESTAMP/${test_name}-report" \
-Jjmeter.save.saveservice.output_format=csv \
-Jjmeter.save.saveservice.assertion_results_failure_message=true \
-Jjmeter.save.saveservice.data_type=true \
-Jjmeter.save.saveservice.label=true \
-Jjmeter.save.saveservice.response_code=true \
-Jjmeter.save.saveservice.response_data.on_error=true \
-Jjmeter.save.saveservice.response_message=true \
-Jjmeter.save.saveservice.successful=true \
-Jjmeter.save.saveservice.thread_name=true \
-Jjmeter.save.saveservice.time=true \
-Jjmeter.save.saveservice.connect_time=true \
-Jjmeter.save.saveservice.latency=true \
-Jjmeter.save.saveservice.bytes=true \
-Jjmeter.save.saveservice.sent_bytes=true \
-Jjmeter.save.saveservice.url=true
if [ $? -eq 0 ]; then
log_info "테스트 완료: $test_name"
log_info "결과 파일: $RESULTS_DIR/$TIMESTAMP/${test_name}-results.jtl"
log_info "HTML 리포트: $RESULTS_DIR/$TIMESTAMP/${test_name}-report/index.html"
else
log_error "테스트 실패: $test_name"
return 1
fi
}
# WebSocket 부하 테스트
run_websocket_test() {
log_info "WebSocket 부하 테스트 준비..."
# Python 스크립트로 WebSocket 테스트 실행
cat > "$RESULTS_DIR/$TIMESTAMP/websocket_load_test.py" << 'EOF'
import asyncio
import websockets
import json
import time
from datetime import datetime, timedelta
import statistics
class WebSocketLoadTester:
def __init__(self, base_url, num_clients, queries_per_client):
self.base_url = base_url
self.num_clients = num_clients
self.queries_per_client = queries_per_client
self.metrics = {
'total_queries': 0,
'successful_queries': 0,
'failed_queries': 0,
'latencies': [],
'throughput': []
}
async def client_session(self, client_id):
async with websockets.connect(f"{self.base_url}/ws-tracks") as websocket:
for query_id in range(self.queries_per_client):
try:
# 쿼리 요청 생성
query = {
"startTime": (datetime.now() - timedelta(days=7)).isoformat(),
"endTime": datetime.now().isoformat(),
"viewport": {
"minLon": 124.0,
"maxLon": 132.0,
"minLat": 33.0,
"maxLat": 38.0
},
"chunkSize": 1000
}
start_time = time.time()
await websocket.send(json.dumps(query))
# 응답 수신
chunks_received = 0
while True:
response = await websocket.recv()
data = json.loads(response)
chunks_received += 1
if data.get('isLastChunk', False):
break
end_time = time.time()
latency = (end_time - start_time) * 1000 # ms
self.metrics['latencies'].append(latency)
self.metrics['successful_queries'] += 1
print(f"Client {client_id} - Query {query_id}: {latency:.2f}ms, {chunks_received} chunks")
except Exception as e:
print(f"Client {client_id} - Query {query_id} failed: {str(e)}")
self.metrics['failed_queries'] += 1
self.metrics['total_queries'] += 1
await asyncio.sleep(1) # 쿼리 간 딜레이
async def run_test(self):
print(f"Starting WebSocket load test with {self.num_clients} clients...")
start_time = time.time()
# 모든 클라이언트 동시 실행
tasks = []
for i in range(self.num_clients):
task = asyncio.create_task(self.client_session(i))
tasks.append(task)
await asyncio.gather(*tasks)
end_time = time.time()
total_duration = end_time - start_time
# 결과 분석
print("\n=== 부하 테스트 결과 ===")
print(f"총 실행 시간: {total_duration:.2f}초")
print(f"총 쿼리 수: {self.metrics['total_queries']}")
print(f"성공: {self.metrics['successful_queries']}")
print(f"실패: {self.metrics['failed_queries']}")
if self.metrics['latencies']:
print(f"평균 레이턴시: {statistics.mean(self.metrics['latencies']):.2f}ms")
print(f"최소 레이턴시: {min(self.metrics['latencies']):.2f}ms")
print(f"최대 레이턴시: {max(self.metrics['latencies']):.2f}ms")
print(f"중앙값 레이턴시: {statistics.median(self.metrics['latencies']):.2f}ms")
print(f"처리량: {self.metrics['total_queries'] / total_duration:.2f} queries/sec")
if __name__ == "__main__":
tester = WebSocketLoadTester(
base_url="ws://10.26.252.48:8090",
num_clients=10,
queries_per_client=5
)
asyncio.run(tester.run_test())
EOF
# Python WebSocket 테스트 실행
if command -v python3 &> /dev/null; then
python3 "$RESULTS_DIR/$TIMESTAMP/websocket_load_test.py" > "$RESULTS_DIR/$TIMESTAMP/websocket_test_results.log" 2>&1
else
log_warn "Python3가 설치되어 있지 않아 WebSocket 테스트를 건너뜁니다."
fi
}
# 메인 실행 함수
main() {
log_info "선박 궤적 집계 시스템 부하 테스트 시작"
log_info "타임스탬프: $TIMESTAMP"
# JMeter 확인
check_jmeter
# 결과 디렉토리 생성
create_results_dir
# 시스템 모니터링 시작
start_monitoring
# 애플리케이션 상태 확인
log_info "애플리케이션 상태 확인..."
curl -s "http://10.26.252.48:8090/actuator/health" > "$RESULTS_DIR/$TIMESTAMP/app_health_before.json"
# JMeter 테스트 실행
if [ -f "$PROJECT_ROOT/src/main/resources/jmeter/comprehensive-load-test.jmx" ]; then
run_jmeter_test "$PROJECT_ROOT/src/main/resources/jmeter/comprehensive-load-test.jmx"
fi
# WebSocket 테스트 실행
run_websocket_test
# 10분간 부하 테스트 실행
log_info "부하 테스트 진행 중... (10분)"
sleep 600
# 시스템 모니터링 중지
stop_monitoring
# 최종 애플리케이션 상태 확인
curl -s "http://10.26.252.48:8090/actuator/health" > "$RESULTS_DIR/$TIMESTAMP/app_health_after.json"
# 결과 요약
log_info "부하 테스트 완료!"
log_info "결과 디렉토리: $RESULTS_DIR/$TIMESTAMP"
# 간단한 결과 분석
if [ -f "$RESULTS_DIR/$TIMESTAMP/comprehensive-load-test-results.jtl" ]; then
log_info "JMeter 결과 요약:"
awk -F',' 'NR>1 {sum+=$2; count++} END {print "평균 응답 시간: " sum/count " ms"}' "$RESULTS_DIR/$TIMESTAMP/comprehensive-load-test-results.jtl"
fi
}
# 스크립트 실행
main "$@"