Skip to content

Conversation

ImGdevel
Copy link
Member

@ImGdevel ImGdevel commented Sep 13, 2025

Summary by CodeRabbit

  • 신기능

    • LoadTest 전용 성능 모니터링 엔드포인트 및 성능 카운터/대시보드/모니터링 스크립트 추가
    • 로드테스트 전용 환경 설정 및 최적화(웹서버·스레드풀·성능카운터) 도입
    • 더미 LLM/Memory/TTS 서비스 및 Node 기반 로드테스트 도구 추가
  • 버그 수정

    • 테스트 클라이언트 포트 관련 연결 안정성 개선
  • 문서

    • LoadTest README 및 Phase1 성능 리포트 추가
  • 작업

    • 미디어 파일 무시 규칙 제거 및 외부 서비스 URL을 환경으로 강제 구성 변경

Copy link
Contributor

coderabbitai bot commented Sep 13, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

LoadTest 전용 환경과 구성·런타임 튜닝, 성능 수집 서비스 및 모니터링 엔드포인트, 여러 로컬/도커 기반 부하테스트 도구·스크립트·더미 서비스와 대시보드를 추가/조정했습니다. .gitignore에서 미디어 파일 무시 규칙이 제거되었습니다.

Changes

Cohort / File(s) Summary
Ignore 규칙 정리
.gitignore
미디어 파일 확장자(*.wav, *.mp3, *.mp4 등)를 무시하던 블록 제거 — 미디어 파일이 더 이상 자동으로 무시되지 않음.
API 서비스 등록·런타임 튜닝
ProjectVG.Api/ApiServiceCollectionExtensions.cs, ProjectVG.Api/Program.cs, ProjectVG.Api/Configuration/LoadTestConfiguration.cs, ProjectVG.Api/appsettings.loadtest.json, env.loadtest
LoadTest 환경에서 Kestrel 및 ThreadPool 설정 적용, ThreadPool 최솟값 설정, LoadTest 전용 appsettings 추가, 서비스 컬렉션에 AddLoadTestPerformanceServices 확장 메서드 등록 및 조건부 호출.
모니터링 컨트롤러 및 성능 서비스
ProjectVG.Api/Controllers/MonitoringController.cs, ProjectVG.Api/Services/PerformanceCounterService.cs, ProjectVG.Api/wwwroot/.gitkeep
실시간/캐시 메트릭, 상세 헬스·GC·스레드풀 엔드포인트 추가. 타이머 기반 PerformanceCounterService 구현(수집, 캐시, dotnet-counters 명령 생성). .gitkeep 주석 제거.
인프라 클라이언트 베이스 URL 변경
ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs, ProjectVG.Infrastructure/Integrations/LLMClient/LLMClient.cs
LLM/MEMORY/TTS 베이스 URL을 환경/설정에서 필수로 읽도록 변경. LLMClient 내부에서 HttpClient.BaseAddress 설정 제거(외부에서 제공 전제).
로드테스트 스택 스크립트 모음
scripts/*.ps1 (start-loadtest.ps1, stop-loadtest.ps1, docker-monitor.ps1, monitor-performance.ps1, quick-monitor.ps1, loadtest-with-monitoring.ps1, monitor-performance.ps1 등)
도커 기반 시작/중지, 컨테이너 내부 dotnet-counters/trace/dump/gcdump 수집, 실시간 CSV/콘솔 모니터링, 통합 로드테스트 실행 및 결과 수집 스크립트 추가.
간이/진보된 부하 도구 및 리포트
test-loadtest/simple-loadtest.js, test-loadtest/chat-loadtest.js, test-loadtest/package.json, test-loadtest/LoadTest-Report-Phase1.md, test-loadtest/README.md, test-loadtest/monitoring-dashboard.html
Node 기반 단순·복합 부하 생성기와 대시보드, 결과 리포트·문서 추가. ws 의존성 추가.
더미 서비스 (LLM/Memory/TTS)
test-loadtest/dummy-llm-server/*, test-loadtest/dummy-memory-server/*, test-loadtest/dummy-tts-server/*
Express 기반 더미 서버 3종(코드, package.json, Dockerfile, healthcheck) 추가 — 지연 시뮬레이션, 간단한 API/헬스 구현.
테스트 클라이언트 및 설정 변경
test-clients/ai-chat-client/script.js, ProjectVG.Api/appsettings.Development.json, env.example
클라이언트 포트 판별 로직 변경으로 포트 7900 고정; 개발용 appsettings 외부 서비스 포트 업데이트; env.example에 TTS_BASE_URL 추가.
도구/모니터링 보조 파일
test-loadtest/monitoring-dashboard.html, test-loadtest/*.md, test-loadtest/package.json
웹 대시보드 및 문서/리포트 파일 추가.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant API as ASP.NET API
  participant MC as MonitoringController
  participant PCS as PerformanceCounterService

  Client->>API: GET /api/v1/monitoring/metrics
  API->>MC: 라우팅
  MC->>MC: 환경 검사 (LoadTest?)
  alt LoadTest && PCS 존재
    MC->>PCS: GetCurrentMetrics()
    PCS-->>MC: Metrics
    MC-->>Client: 200 + JSON
  else 비허용 또는 PCS 없음
    MC-->>Client: 400 / 503
  end
Loading
sequenceDiagram
  autonumber
  participant Host as Program/Main
  participant Kestrel as KestrelOptions
  participant LTC as LoadTestConfiguration
  participant DI as IServiceCollection

  Host->>Host: ASPNETCORE_ENVIRONMENT 확인
  alt LoadTest
    Host->>Kestrel: ConfigureKestrel(options)
    Host->>LTC: ConfigureKestrelForLoadTest(options)
    Host->>LTC: ConfigureThreadPoolForLoadTest()
    Host->>DI: AddLoadTestPerformanceServices()
  else 다른 환경
    Host->>Kestrel: 기본 구성 유지
  end
  Host-->>Host: 앱 빌드 & 시작
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

로드의 물결 속에 깡충깡충, 나는 토끼 🐇
스레드와 카운터를 귀로 두드리네, 찰칵찰칵
케스트럴을 다듬고, 타이머는 똑딱똑딱,
로그와 대시보드에 별을 떨어뜨리네 ✨
함께 뛰자 — 성능의 들판으로!

Pre-merge checks and finishing touches and finishing touches and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 현재 PR 제목 "Test: 부하테스트 환경 구축 및 부하테스트 실행"은 변경사항의 핵심인 로드테스트 환경 구성과 부하테스트 실행을 명확히 요약하고 있어 PR 내용과 직접적으로 일치합니다. 제목이 구체적이고 스캔하는 동료가 주요 변경점을 빠르게 이해할 수 있도록 도와줍니다. 다만 접두어 "Test:"는 약간의 잡음이 될 수 있지만 전체 의미를 흐리지는 않습니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test/load-testing

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6518bbc and 62710ed.

📒 Files selected for processing (7)
  • ProjectVG.Api/Services/PerformanceCounterService.cs (1 hunks)
  • ProjectVG.Api/appsettings.Development.json (1 hunks)
  • ProjectVG.Api/appsettings.loadtest.json (1 hunks)
  • ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs (2 hunks)
  • env.example (1 hunks)
  • scripts/dev-setup.ps1 (1 hunks)
  • scripts/start-loadtest.ps1 (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 31

♻️ Duplicate comments (1)
scripts/loadtest-with-monitoring.ps1 (1)

94-97: 프로세스 검색 로직 일관성 부족

monitor-performance.ps1과 동일한 프로세스 검색 로직이지만 구현이 약간 다릅니다. 공통 함수로 추출하여 재사용하는 것이 좋겠습니다.

🧹 Nitpick comments (39)
test-loadtest/dummy-llm-server/package.json (1)

11-12: Express 의존성 업데이트 권장 — express ^4.18.2 → ^4.21.2

Express 4.x 최신 안정 릴리스는 4.21.2(릴리스일: 2024-11-06)입니다. test-loadtest/dummy-llm-server/package.json(11–12행)의 "express": "^4.18.2"를 "^4.21.2"로 업데이트하세요.

test-loadtest/dummy-tts-server/server.js (2)

51-56: 오디오 데이터 생성 로직 최적화 가능

랜덤 노이즈 생성 시 매번 Math.random()을 호출하는 것보다 버퍼를 한 번에 생성하는 것이 더 효율적일 수 있습니다.

-// Generate random audio data (pure random noise)
-for (let i = 0; i < samples; i++) {
-  // Pure random noise - completely random audio data
-  const sample = (Math.random() - 0.5) * 32767;
-  
-  buffer.writeInt16LE(sample, 44 + i * 2);
-}
+// Generate random audio data (pure random noise) - 더 효율적인 방법
+const audioData = Buffer.allocUnsafe(dataSize);
+for (let i = 0; i < dataSize; i += 2) {
+  const sample = Math.floor((Math.random() - 0.5) * 32767);
+  audioData.writeInt16LE(sample, i);
+}
+audioData.copy(buffer, 44);

87-87: 로드 테스트용 오디오 길이 0 반환에 대한 설명 추가 권장

오디오 길이를 0으로 보고하는 것이 로드 테스트 목적임을 코드에 명확히 문서화하면 좋겠습니다.

-// For load testing: report audio length as 0 (but send real audio data)
-const reportedDuration = 0;
+// 로드 테스트 최적화: 실제 오디오 데이터는 전송하지만 길이는 0으로 보고
+// 이를 통해 클라이언트의 오디오 재생 처리를 건너뛸 수 있음
+const reportedDuration = 0; // 프로덕션에서는 actualDuration 사용
test-loadtest/dummy-tts-server/Dockerfile (1)

18-19: 헬스체크 타임아웃 완화 및 실패 원인 로깅 고려

3s 타임아웃은 콜드스타트 시 false-negative를 유발할 수 있습니다. 5–10s로 완화하거나 초기 start-period를 늘리세요. 또한 실패 시 응답코드/본문을 로그에 남기면 원인 파악이 수월합니다.

-HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+HEALTHCHECK --interval=30s --timeout=8s --start-period=10s --retries=3 \
test-loadtest/LoadTest-Report-Phase1.md (4)

84-88: CPU 17,920% 수치의 정의/단위 명시 필요

해당 값이 “전체 코어 합산 기준”인지, “프로세스 % of 총 CPU”인지 불분명합니다. 재현/해석 가능하도록 수집 소스(예: dotnet-counters/top/perf), 코어 수, 산식(합산/평균/최댓값)을 명시해주세요.

제안: “최대 CPU 사용률: 17920% (전체 64 vCPU 기준, 프로세스 합산)”처럼 기반 수치와 산식을 병기.


79-83: ThreadPool 지표 상한/정의 명시

“가용 스레드: 33,766개”는 .NET ThreadPool 기본 최대치(약 32,767)에 수렴하는 값으로 보입니다. ‘가용/최대/사용 중’의 정의, 측정 방식, 단위를 표기하고 그래프/시계열 스냅샷을 첨부하면 해석이 쉬워집니다.


130-142: 권장사항에 “부하 제어/백프레셔”와 “캐시 전략” 구체화 제안

현재 권장사항이 방향성 위주입니다. 큐/스로틀(예: Kestrel request queue, Channel, SemaphoreSlim), JWT 검증 결과 캐시(TimeWindow), DB 인덱스/쿼리 힌트 등 실행 단위로 구체화하면 즉시 액션이 가능합니다.

원하시면 각 항목별 체크리스트/PR 템플릿까지 만들어 드리겠습니다.


145-154: 재현성 강화를 위한 환경 스펙/커밋 해시 명시

하드웨어(vCPU/메모리/스토리지), OS/컨테이너 런타임 버전, .NET/Node 버전, 대상 API 커밋 SHA를 본문에 고정 기록해주세요. 동일 조건 비교에 필수입니다.

+### 테스트 환경 스펙
+- vCPU/메모리/스토리지:
+- OS/Container Runtime:
+- .NET/Node 버전:
+- 대상 API 커밋 SHA:
scripts/quick-monitor.ps1 (3)

55-56: ThreadPool 막대 그래프의 스케일 왜곡 가능성

고정 Max=100은 실제 스레드 수 범위와 불일치하여 항상 100%에 가깝게 표시될 수 있습니다. ‘사용 중 스레드 / 최대 스레드’로 정규화하거나 API에서 maxWorkerThreads를 노출하도록 바꾸는 것을 권장합니다.

예: used = max - available; Write-PerformanceBar used max "Worker Threads(used)".


46-46: 타임아웃/리프레시 간격의 상수 분리

타임아웃(3s)을 파라미터화하여 다양한 환경에서 재사용성을 높이세요.

-$metrics = Invoke-RestMethod -Uri "$ApiUrl/api/v1/monitoring/metrics" -TimeoutSec 3
+$timeoutSec = [Math]::Max(3, $RefreshSeconds)
+$metrics = Invoke-RestMethod -Uri "$ApiUrl/api/v1/monitoring/metrics" -TimeoutSec $timeoutSec

82-95: 경고 임계치의 근거/단위 주석 추가

500MB, 20개 등 임계치 기준이 문맥 의존적입니다. 단위/근거를 주석으로 고정하거나 스크립트 파라미터로 노출하세요.

test-loadtest/chat-loadtest.js (2)

452-453: RPS 계산도 ‘시도 기준’으로 일관화

chatRPS는 성공 수가 아닌 시도 수 기반이 타당합니다(위 수정과 일관).


203-213: 토큰을 쿼리스트링으로 전달 시 노출 리스크

로그/프록시/히스토리에서 토큰이 노출될 수 있습니다. 가능하다면 헤더(Authorization) 또는 Sec-WebSocket-Protocol 토큰 교환으로 대체하세요(서버 지원 필요).

test-loadtest/simple-loadtest.js (1)

151-160: 헬스체크 실패 시 종료 처리 OK — 메시지에 재시도 힌트 추가 제안

운영 환경에서 일시 실패가 흔합니다. 재시도 횟수/간격을 안내하면 UX가 좋아집니다.

scripts/monitor-performance.ps1 (2)

49-54: 헬스 체크 실패 시 더 구체적인 안내 필요

API 헬스 체크가 실패했을 때 LoadTest 환경 실행 외에도 API URL이 올바른지 확인하도록 안내를 추가하면 좋겠습니다.

 } catch {
     Write-ColorOutput "API Health Check Failed: $($_.Exception.Message)" "Red"
-    Write-ColorOutput "Make sure the API is running in LoadTest environment" "Yellow"
+    Write-ColorOutput "Make sure the API is running in LoadTest environment" "Yellow"
+    Write-ColorOutput "Verify the API URL is correct: $ApiUrl" "Yellow"
     exit 1
 }

206-214: dotnet-counters 프로세스 종료 시 예외 처리 개선

프로세스를 강제 종료할 때 Kill() 메서드가 실패할 수 있으므로, 먼저 정상 종료를 시도하는 것이 좋습니다.

 if ($dotnetCountersProcess -and !$dotnetCountersProcess.HasExited) {
     Write-ColorOutput "`nStopping dotnet-counters..." "Yellow"
     try {
-        $dotnetCountersProcess.Kill()
-        $dotnetCountersProcess.WaitForExit(5000)
+        # 먼저 정상 종료 시도
+        $dotnetCountersProcess.CloseMainWindow()
+        if (!$dotnetCountersProcess.WaitForExit(3000)) {
+            # 정상 종료 실패 시 강제 종료
+            $dotnetCountersProcess.Kill()
+            $dotnetCountersProcess.WaitForExit(2000)
+        }
     } catch {
         Write-ColorOutput "Failed to stop dotnet-counters gracefully" "Yellow"
     }
 }
scripts/loadtest-with-monitoring.ps1 (1)

169-173: 프로세스 종료 시 예외 처리 부재

성능 모니터 프로세스를 강제 종료할 때 실패할 경우를 대비한 예외 처리가 있지만, 실제 예외를 로깅하지 않습니다.

 try {
     $performanceMonitorProcess.Kill()
     Write-ColorOutput "Performance monitor stopped" "Green"
 } catch {
-    Write-ColorOutput "Failed to stop performance monitor" "Yellow"
+    Write-ColorOutput "Failed to stop performance monitor: $($_.Exception.Message)" "Yellow"
 }
test-loadtest/dummy-memory-server/server.js (2)

17-19: 지연 시뮬레이션 개선 제안

현재는 고정 지연만 사용하는데, 다른 더미 서버들처럼 랜덤 범위를 지원하면 더 현실적인 테스트가 가능합니다.

+const DELAY_MIN = parseInt(process.env.RESPONSE_DELAY_MIN || '50');
+const DELAY_MAX = parseInt(process.env.RESPONSE_DELAY_MAX || '150');
-const DELAY = parseInt(process.env.RESPONSE_DELAY || '100');

 // Helper function to simulate processing delay
 const simulateDelay = () => {
-  return new Promise(resolve => setTimeout(resolve, DELAY));
+  const delay = Math.floor(Math.random() * (DELAY_MAX - DELAY_MIN + 1)) + DELAY_MIN;
+  return new Promise(resolve => setTimeout(resolve, delay));
 };

253-276: 초기 더미 데이터 생성 로직 개선 가능

초기 데이터 생성 로직이 직관적이지만, 메모리 ID 생성 방식이 일관성이 없습니다. (예: sem_1, epi_2 vs 실제 엔드포인트에서는 sem_11, epi_12부터 시작)

 for (let i = 1; i <= 10; i++) {
     const isEpisodic = i % 2 === 0;
     const store = isEpisodic ? episodicMemories : semanticMemories;
     const memType = isEpisodic ? 'episodic' : 'semantic';
     
-    const memId = `${memType.substring(0,3)}_${i}`;
+    const memId = `${isEpisodic ? 'epi' : 'sem'}_${i}`;
     store.set(memId, {
test-loadtest/dummy-memory-server/Dockerfile (2)

9-9: npm ci 사용 권장

프로덕션 환경에서는 npm install --only=production보다 npm ci --only=production을 사용하는 것이 더 안전하고 빠릅니다.

 # Install dependencies
-RUN npm install --only=production
+RUN npm ci --only=production

18-19: 헬스체크 구현 개선 가능

Node.js를 매번 실행하는 대신 wget이나 curl을 사용하면 더 효율적입니다.

+# Install curl for health check
+RUN apk add --no-cache curl
+
 # Health check
 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
-  CMD node -e "require('http').get('http://localhost:7812/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"
+  CMD curl -f http://localhost:7812/health || exit 1
test-loadtest/dummy-llm-server/server.js (1)

25-33: 요청 본문 파라미터의 유효성 검증 필요

현재 요청 본문에서 필드들을 직접 추출하고 있지만, 필수 필드에 대한 검증이 없습니다. 실제 부하 테스트 시 잘못된 요청으로 인한 오류를 방지하기 위해 기본적인 유효성 검사를 추가하는 것이 좋습니다.

다음과 같이 필수 필드 검증을 추가할 수 있습니다:

+  // 필수 필드 검증
+  if (!user_prompt) {
+    return res.status(400).json({
+      error: 'Missing required field: user_prompt',
+      service: 'dummy-llm-server'
+    });
+  }
+
   const { 
     system_prompt, 
     user_prompt, 
     conversation_history, 
     model, 
     max_tokens, 
     temperature,
     instructions 
   } = req.body;
ProjectVG.Api/Controllers/MonitoringController.cs (2)

32-35: 환경 검증 로직 중복

모든 엔드포인트에서 LoadTest 환경 검증이 반복되고 있습니다. Action Filter를 사용하여 중복을 제거하는 것이 좋습니다.

환경 검증을 위한 커스텀 Action Filter를 만들어 적용할 수 있습니다:

public class LoadTestEnvironmentFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var env = context.HttpContext.RequestServices.GetRequiredService<IWebHostEnvironment>();
        if (!env.IsEnvironment("LoadTest"))
        {
            context.Result = new BadRequestObjectResult(new { error = "This endpoint is only available in LoadTest environment" });
        }
    }
}

그리고 컨트롤러 레벨에서 적용:

[LoadTestEnvironmentFilter]
public class MonitoringController : ControllerBase

344-347: ServiceUnavailable 헬퍼 메서드 명명 개선 필요

메서드명이 동사로 시작하지 않아 일반적인 C# 명명 규칙에 맞지 않습니다.

메서드명을 더 명확하게 변경하세요:

-private ActionResult ServiceUnavailable(object value)
+private ActionResult CreateServiceUnavailableResponse(object value)
 {
     return StatusCode(503, value);
 }

그리고 호출 부분도 업데이트:

-return ServiceUnavailable(new { error = "PerformanceCounterService not available" });
+return CreateServiceUnavailableResponse(new { error = "PerformanceCounterService not available" });
test-loadtest/monitoring-dashboard.html (3)

380-400: API 호출 실패 시 재시도 로직 부재

네트워크 오류나 일시적인 API 실패 시 즉시 오프라인으로 표시되며, 재시도 메커니즘이 없습니다. 부하 테스트 중 일시적인 네트워크 문제로 인한 모니터링 중단을 방지하기 위해 재시도 로직을 추가하는 것이 좋습니다.

재시도 로직을 추가하여 안정성을 향상시킬 수 있습니다:

+let retryCount = 0;
+const maxRetries = 3;

 async function fetchMetrics() {
     try {
         const response = await fetch(`${apiUrl}/api/v1/monitoring/metrics`);
         
         if (!response.ok) {
             throw new Error(`HTTP ${response.status}: ${response.statusText}`);
         }
         
         const metrics = await response.json();
         updateDashboard(metrics);
         updateConnectionStatus('online');
+        retryCount = 0; // 성공 시 재시도 카운트 리셋
         
         // Check for alerts
         checkAlerts(metrics);
         
         previousMetrics = metrics;
         
     } catch (error) {
-        logMessage(`Failed to fetch metrics: ${error.message}`, 'error');
-        updateConnectionStatus('offline');
+        retryCount++;
+        if (retryCount > maxRetries) {
+            logMessage(`Failed to fetch metrics after ${maxRetries} retries: ${error.message}`, 'error');
+            updateConnectionStatus('offline');
+            retryCount = 0;
+        } else {
+            logMessage(`Fetch failed, retry ${retryCount}/${maxRetries}: ${error.message}`, 'warning');
+        }
     }
 }

496-499: 로그 엔트리 제한이 하드코딩됨

로그 엔트리를 50개로 제한하는 값이 하드코딩되어 있습니다. 설정 가능한 상수로 관리하는 것이 좋습니다.

상수로 정의하여 관리하세요:

+const MAX_LOG_ENTRIES = 50;
+
 function logMessage(message, type = 'info') {
     const logContainer = document.getElementById('logContainer');
     const entry = document.createElement('div');
     entry.className = `log-entry log-${type}`;
     entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
     
     logContainer.appendChild(entry);
     logContainer.scrollTop = logContainer.scrollHeight;
     
-    // Keep only last 50 entries
-    while (logContainer.children.length > 50) {
+    // Keep only last MAX_LOG_ENTRIES entries
+    while (logContainer.children.length > MAX_LOG_ENTRIES) {
         logContainer.removeChild(logContainer.firstChild);
     }
 }

451-462: 경고 임계값이 하드코딩됨

메모리, 스레드, 작업 큐 등의 경고 임계값이 하드코딩되어 있어 다양한 테스트 시나리오에 유연하게 대응하기 어렵습니다.

설정 가능한 임계값 객체를 만들어 관리하세요:

+const alertThresholds = {
+    memoryMB: 500,
+    pendingWorkItems: 50,
+    minAvailableThreads: 20
+};
+
 function checkAlerts(metrics) {
     const alertsContainer = document.getElementById('alerts');
     alertsContainer.innerHTML = '';
     
     let alerts = [];
     
-    if (metrics.process.workingSetMemoryMB > 500) {
+    if (metrics.process.workingSetMemoryMB > alertThresholds.memoryMB) {
         alerts.push('High memory usage detected');
     }
     
-    if (metrics.threadPool.pendingWorkItems > 50) {
+    if (metrics.threadPool.pendingWorkItems > alertThresholds.pendingWorkItems) {
         alerts.push('High work queue detected');
     }
     
-    if (metrics.threadPool.availableThreads < 20) {
+    if (metrics.threadPool.availableThreads < alertThresholds.minAvailableThreads) {
         alerts.push('Low thread availability');
     }
ProjectVG.Api/Services/PerformanceCounterService.cs (1)

132-141: Dispose 패턴 개선 필요

현재 Dispose 메서드가 GC 억제 호출(GC.SuppressFinalize)을 하지 않고 있습니다. 표준 Dispose 패턴을 따르는 것이 좋습니다.

표준 Dispose 패턴을 적용하세요:

 public void Dispose()
 {
     if (_disposed) return;
     
     _metricsTimer?.Dispose();
     _currentProcess?.Dispose();
     _disposed = true;
     
     _logger.LogInformation("PerformanceCounterService disposed");
+    GC.SuppressFinalize(this);
 }
test-loadtest/dummy-llm-server/Dockerfile (2)

18-19: 헬스체크 유지 가능하나, curl 대체/타임아웃 여유 고려

현 방식은 동작하지만, 이미지에 curl 추가 시 간결해집니다. 또한 초기 기동 지연을 고려해 timeout을 5s로 늘리는 것이 안전합니다(선택).

-HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
   CMD node -e "require('http').get('http://localhost:7808/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"

12-12: .dockerignore 추가 권장

컨텍스트 축소로 빌드 속도와 캐시 효율을 개선하세요(예: node_modules, logs 등 제외).

scripts/start-loadtest.ps1 (2)

50-64: 헬스 판정 조건 완화(본문 필드 의존 제거)

본문의 status/Status 필드에 의존하지 말고 HTTP 200 기준으로 통일하는 편이 안전합니다.

-        $response = Invoke-RestMethod -Uri $service.Url -TimeoutSec 3
-        if ($response.status -eq "ok" -or $response.Status -eq "Healthy") {
+        $response = Invoke-WebRequest -Uri $service.Url -TimeoutSec 3 -UseBasicParsing
+        if ($response.StatusCode -eq 200) {
             Write-Host "$($service.Name): OK" -ForegroundColor Green
         } else {

21-35: Compose V2 호환성 — docker compose 선호, 강제 no-cache는 옵션화

빌드 캐시를 매번 버리면 반복 속도가 느려집니다. 기본은 캐시 사용, 필요 시 --fresh 스위치로 no-cache를 선택적으로 적용하세요.

-docker-compose -p projectvg-loadtest --env-file env.loadtest -f docker-compose.loadtest.yml build --no-cache 2>$null
-docker-compose -p projectvg-loadtest --env-file env.loadtest -f docker-compose.loadtest.yml up -d
+param([switch]$fresh)
+if ($fresh) { docker compose -p projectvg-loadtest --env-file env.loadtest -f docker-compose.loadtest.yml build --no-cache 2>$null }
+else        { docker compose -p projectvg-loadtest --env-file env.loadtest -f docker-compose.loadtest.yml build 2>$null }
+docker compose -p projectvg-loadtest --env-file env.loadtest -f docker-compose.loadtest.yml up -d
ProjectVG.Api/Program.cs (1)

44-49: 모니터링 서비스 등록 OK — 단, 설정 토글 반영 고려

appsettings.loadtest.jsonLoadTestOptimizations.EnablePerformanceCounters 값을 읽어 토글 가능하게 하면 운영/부하테스트 내에서도 유연합니다(선택).

ProjectVG.Api/appsettings.loadtest.json (1)

27-34: Kestrel 설정은 코드와 중복 — 단일 소스화 권장

현재 LoadTestConfiguration에서 하드코딩, 이 파일에도 값이 존재해 드리프트 위험이 있습니다. 한 곳(코드 또는 설정)으로 통일하고 다른 한쪽은 제거/위임하세요.

test-loadtest/README.md (3)

21-22: 사용자 경로 하드코딩 제거

로컬 사용자 경로 예시 대신 저장소 루트 기준으로 일반화하세요.

-   cd "C:\Users\imdls\Documents\Project\MainAPI Server"
+   cd <repo-root>

96-102: 환경 변수/포트 정합성 주의

문서(7808/7812/7816)와 appsettings.loadtest.json(5604/5605/7919)이 상이합니다. 하나로 통일해 혼선을 방지하세요.


129-138: 정리 섹션: 이미지 삭제는 선택적, 캐시 재활용 안내 추가 제안

이미지 제거는 재빌드 지연을 유발합니다. 필요 시에만 수행하도록 주석으로 강조하세요.

ProjectVG.Api/Configuration/LoadTestConfiguration.cs (1)

26-35: 하드코딩 대신 구성 바인딩 고려(선택)

appsettings.loadtest.json의 값을 바인딩해 재배포 없이 조정 가능하도록 하는 것을 권장합니다.

ProjectVG.Api/ApiServiceCollectionExtensions.cs (1)

21-31: Swagger 등록 조건부화 검토

LoadTest에서 DisableSwagger=true 설정을 존중하려면 Swagger 등록을 토글 가능하게 바꾸는 것이 좋습니다(다른 파일 변경 필요).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4a46f9 and 6518bbc.

⛔ Files ignored due to path filters (3)
  • test-loadtest/dummy-tts-server/public/Take1-1__voice.wav is excluded by !**/*.wav
  • test-loadtest/dummy-tts-server/public/Take1-2_voice.wav is excluded by !**/*.wav
  • test-loadtest/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (33)
  • .gitignore (0 hunks)
  • ProjectVG.Api/ApiServiceCollectionExtensions.cs (1 hunks)
  • ProjectVG.Api/Configuration/LoadTestConfiguration.cs (1 hunks)
  • ProjectVG.Api/Controllers/MonitoringController.cs (1 hunks)
  • ProjectVG.Api/Program.cs (2 hunks)
  • ProjectVG.Api/Services/PerformanceCounterService.cs (1 hunks)
  • ProjectVG.Api/appsettings.loadtest.json (1 hunks)
  • ProjectVG.Api/wwwroot/.gitkeep (0 hunks)
  • ProjectVG.Infrastructure/InfrastructureServiceCollectionExtensions.cs (1 hunks)
  • ProjectVG.Infrastructure/Integrations/LLMClient/LLMClient.cs (0 hunks)
  • env.loadtest (1 hunks)
  • scripts/docker-monitor.ps1 (1 hunks)
  • scripts/loadtest-with-monitoring.ps1 (1 hunks)
  • scripts/monitor-performance.ps1 (1 hunks)
  • scripts/quick-monitor.ps1 (1 hunks)
  • scripts/start-loadtest.ps1 (1 hunks)
  • scripts/stop-loadtest.ps1 (1 hunks)
  • test-clients/ai-chat-client/script.js (1 hunks)
  • test-loadtest/LoadTest-Report-Phase1.md (1 hunks)
  • test-loadtest/README.md (1 hunks)
  • test-loadtest/chat-loadtest.js (1 hunks)
  • test-loadtest/dummy-llm-server/Dockerfile (1 hunks)
  • test-loadtest/dummy-llm-server/package.json (1 hunks)
  • test-loadtest/dummy-llm-server/server.js (1 hunks)
  • test-loadtest/dummy-memory-server/Dockerfile (1 hunks)
  • test-loadtest/dummy-memory-server/package.json (1 hunks)
  • test-loadtest/dummy-memory-server/server.js (1 hunks)
  • test-loadtest/dummy-tts-server/Dockerfile (1 hunks)
  • test-loadtest/dummy-tts-server/package.json (1 hunks)
  • test-loadtest/dummy-tts-server/server.js (1 hunks)
  • test-loadtest/monitoring-dashboard.html (1 hunks)
  • test-loadtest/package.json (1 hunks)
  • test-loadtest/simple-loadtest.js (1 hunks)
💤 Files with no reviewable changes (3)
  • ProjectVG.Api/wwwroot/.gitkeep
  • ProjectVG.Infrastructure/Integrations/LLMClient/LLMClient.cs
  • .gitignore
🧰 Additional context used
🧬 Code graph analysis (8)
test-loadtest/dummy-llm-server/server.js (2)
test-loadtest/dummy-memory-server/server.js (11)
  • express (1-1)
  • cors (2-2)
  • app (4-4)
  • PORT (5-5)
  • simulateDelay (17-19)
  • req (29-29)
  • req (57-57)
  • req (85-85)
  • req (124-124)
  • req (164-164)
  • req (193-193)
test-loadtest/dummy-tts-server/server.js (9)
  • express (1-1)
  • cors (2-2)
  • app (7-7)
  • PORT (8-8)
  • DELAY_MIN (9-9)
  • DELAY_MAX (10-10)
  • simulateDelay (20-23)
  • delay (21-21)
  • req (68-68)
ProjectVG.Api/Controllers/MonitoringController.cs (1)
ProjectVG.Api/Services/PerformanceCounterService.cs (5)
  • PerformanceCounterService (6-142)
  • PerformanceCounterService (14-24)
  • GetAvailableThreads (113-117)
  • Dispose (132-141)
  • GetDotNetCountersCommand (124-130)
test-loadtest/dummy-tts-server/server.js (1)
test-loadtest/dummy-llm-server/server.js (9)
  • express (1-1)
  • cors (2-2)
  • app (4-4)
  • PORT (5-5)
  • DELAY_MIN (6-6)
  • DELAY_MAX (7-7)
  • simulateDelay (13-16)
  • delay (14-14)
  • req (25-33)
ProjectVG.Api/ApiServiceCollectionExtensions.cs (1)
ProjectVG.Api/Services/PerformanceCounterService.cs (2)
  • PerformanceCounterService (6-142)
  • PerformanceCounterService (14-24)
test-loadtest/dummy-memory-server/server.js (2)
test-loadtest/dummy-llm-server/server.js (2)
  • PORT (5-5)
  • simulateDelay (13-16)
test-loadtest/dummy-tts-server/server.js (3)
  • PORT (8-8)
  • simulateDelay (20-23)
  • i (51-51)
test-loadtest/chat-loadtest.js (1)
test-clients/ai-chat-client/script.js (9)
  • headers (561-561)
  • response (119-119)
  • response (142-148)
  • response (703-707)
  • response (726-726)
  • response (967-974)
  • response (1098-1103)
  • response (1211-1217)
  • response (1336-1341)
test-loadtest/simple-loadtest.js (1)
test-loadtest/chat-loadtest.js (2)
  • http (6-6)
  • CONFIG (12-40)
ProjectVG.Api/Program.cs (1)
ProjectVG.Api/Configuration/LoadTestConfiguration.cs (3)
  • LoadTestConfiguration (8-55)
  • ConfigureKestrelForLoadTest (14-35)
  • ConfigureThreadPoolForLoadTest (40-54)
🔇 Additional comments (13)
test-loadtest/package.json (1)

1-5: ws 의존성 — 확인 완료 (8.18.3, 보안패치 포함)

파일: test-loadtest/package.json (lines 1-5)
^8.18.3은 npm상 최신(8.18.3)이며 CVE-2024-37890 등 주요 취약점은 8.17.1+에서 패치되어 해당 버전에 포함됩니다. 즉시 업그레이드 불필요. 권장: npm audit/Snyk 등으로 SCA 실행 및 lockfile 확인.

test-loadtest/dummy-memory-server/package.json (1)

1-20: 패키지 구성이 잘 되어 있음

로드 테스트용 더미 메모리 서버의 package.json이 적절히 구성되어 있습니다. 필요한 최소한의 의존성만 포함하고 있어 좋습니다.

test-loadtest/dummy-memory-server/server.js (1)

1-282: 전체적으로 잘 구현된 더미 서버입니다!

메모리 서버의 더미 구현이 LoadTest 환경에 적합하게 잘 설계되었습니다. 에피소딕/시맨틱 메모리 분리, 지연 시뮬레이션, 다양한 엔드포인트 지원 등이 모두 적절히 구현되어 있습니다.

test-loadtest/dummy-llm-server/server.js (3)

13-16: 비동기 지연 시뮬레이션 함수가 잘 구현되었습니다

지연 시간을 랜덤하게 생성하는 로직이 적절하며, Promise를 사용한 비동기 처리가 올바르게 구현되었습니다.


39-44: 테스트 응답 생성을 위한 다양한 감정과 액션 정의

감정과 액션 배열이 ProjectVG 형식에 맞게 잘 정의되어 있습니다. 부하 테스트 시 다양한 응답 패턴을 시뮬레이션할 수 있어 효과적입니다.


87-91: 헬스체크 엔드포인트 구현 우수

헬스체크 엔드포인트가 필요한 정보(상태, 서비스명, 포트, 타임스탬프)를 포함하여 적절하게 구현되었습니다. Docker 헬스체크와의 통합에도 유용합니다.

ProjectVG.Api/ApiServiceCollectionExtensions.cs (1)

82-86: DI 등록 방향성 적절

LoadTest 전용 PerformanceCounterService를 Singleton으로 등록한 점 적절합니다(백그라운드 타이머 1개 유지).

scripts/start-loadtest.ps1 (1)

87-87: stop 스크립트 존재 확인 필요

scripts\stop-loadtest.ps1가 PR에 포함됐는지 확인 부탁드립니다. 없으면 안내 문구를 수정하거나 스크립트를 추가하세요.

ProjectVG.Api/Program.cs (3)

15-20: LoadTest 시 Kestrel 튜닝 진입 위치 적절

환경 게이팅 뒤 Kestrel 옵션을 적용하는 구조 적절합니다.


23-28: ThreadPool 최적화 호출 시점 OK

호스트 빌드 전, LoadTest에서만 적용하도록 한 점 적절합니다.


33-38: 불필요 — OAUTH2_ENABLED가 이미 반영됩니다

Program.cs에서 builder.Configuration.AddEnvironmentVariableSubstitution(...)이 호출되며 ProjectVG.Api/appsettings.Production.json의 "OAuth2:Enabled"가 "${OAUTH2_ENABLED}"로 정의되어 있어 현재 GetValue("OAuth2:Enabled", ...)는 OAUTH2_ENABLED 환경변수를 통해 값이 반영됩니다.

Likely an incorrect or invalid review comment.

ProjectVG.Api/appsettings.loadtest.json (1)

17-26: DisableSwagger/로깅 토글이 실제 코드 경로에 반영되는지 확인

AddApiServices()에서 Swagger 등록이 항상 수행된다면 이 토글이 무의미합니다. 토글을 읽어 조건부로 등록하도록 수정 필요(다른 파일 변경).

test-loadtest/README.md (1)

58-64: 헬스 엔드포인트 일관성 확인

문서의 API 헬스(GET /api/v1/health)가 실제 구현/스크립트와 동일한지 확인 바랍니다. 불일치 시 문서/스크립트 중 하나를 정정하세요.

Comment on lines +1 to +34
# ProjectVG API 부하 테스트 환경변수
# 부하 테스트용 설정으로 로그 레벨을 WARN으로 올리고 더미 서버를 사용

# 외부 서비스 연결 (더미 서버 사용)
LLM_BASE_URL=http://localhost:7808
MEMORY_BASE_URL=http://localhost:7812
TTS_BASE_URL=http://localhost:7816
TTS_API_KEY=dummy-tts-api-key-for-loadtest

# 데이터베이스 연결 (부하테스트 전용 - 운영 DB와 완전 분리)
DB_CONNECTION_STRING=Server=localhost,1434;Database=ProjectVG_LoadTest;User Id=sa;Password=LoadTest123!;TrustServerCertificate=true;MultipleActiveResultSets=true

# Redis 연결 (부하테스트 전용 - 운영 Redis와 완전 분리)
REDIS_CONNECTION_STRING=localhost:6381

# JWT 설정
JWT_SECRET_KEY=your-super-secret-jwt-key-here-minimum-32-characters
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=15
JWT_REFRESH_TOKEN_LIFETIME_DAYS=30

# OAuth2 설정 (부하 테스트에서는 비활성화)
OAUTH2_ENABLED=false

# Google OAuth2 설정 (부하 테스트용 더미 설정)
GOOGLE_OAUTH_ENABLED=false
GOOGLE_OAUTH_CLIENT_ID=dummy-client-id
GOOGLE_OAUTH_CLIENT_SECRET=dummy-client-secret
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:7804/auth/oauth2/callback
GOOGLE_OAUTH_AUTO_CREATE_USER=true
GOOGLE_OAUTH_DEFAULT_ROLE=User

# 부하 테스트 전용 설정
ASPNETCORE_ENVIRONMENT=LoadTest
LOGGING_LEVEL=Warning
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

.NET 바인딩 키 불일치로 설정이 적용되지 않을 수 있음(OAuth2/외부서비스 등)

Program.cs는 OAuth2:Enabled를 읽습니다. 현재 OAUTH2_ENABLED=false는 바인딩되지 않아 OAuth2가 계속 활성화될 수 있습니다. 외부 서비스도 마찬가지입니다. 환경변수 키를 “__”로 네임스페이스화하세요.

-LLM_BASE_URL=http://localhost:7808
-MEMORY_BASE_URL=http://localhost:7812
-TTS_BASE_URL=http://localhost:7816
-OAUTH2_ENABLED=false
+ExternalServices__LLM__BaseUrl=http://localhost:7808
+ExternalServices__Memory__BaseUrl=http://localhost:7812
+ExternalServices__TTS__BaseUrl=http://localhost:7816
+OAuth2__Enabled=false

추가로 Google OAuth 관련 키도 실제 바인딩에 맞춰 네임스페이스화하세요(예: GoogleOAuth__Enabled=false, 사용 중인 구성 스키마에 맞춤).


환경변수 키를 .NET 구성 바인딩 규칙(콜론 → '__')에 맞게 네임스페이스화하세요

Program.cs가 'OAuth2:Enabled' 등 콜론 기반 키를 읽습니다 — 환경변수는 ':' 대신 '__'(double underscore)로 네임스페이스화해야 바인딩됩니다.

-LLM_BASE_URL=http://localhost:7808
-MEMORY_BASE_URL=http://localhost:7812
-TTS_BASE_URL=http://localhost:7816
-OAUTH2_ENABLED=false
+ExternalServices__LLM__BaseUrl=http://localhost:7808
+ExternalServices__Memory__BaseUrl=http://localhost:7812
+ExternalServices__TTS__BaseUrl=http://localhost:7816
+OAuth2__Enabled=false

Google OAuth 관련 변수도 구성 스키마에 맞춰 네임스페이스화하세요(예: GoogleOAuth__Enabled, GoogleOAuth__ClientId, GoogleOAuth__ClientSecret, GoogleOAuth__RedirectUri).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# ProjectVG API 부하 테스트 환경변수
# 부하 테스트용 설정으로 로그 레벨을 WARN으로 올리고 더미 서버를 사용
# 외부 서비스 연결 (더미 서버 사용)
LLM_BASE_URL=http://localhost:7808
MEMORY_BASE_URL=http://localhost:7812
TTS_BASE_URL=http://localhost:7816
TTS_API_KEY=dummy-tts-api-key-for-loadtest
# 데이터베이스 연결 (부하테스트 전용 - 운영 DB와 완전 분리)
DB_CONNECTION_STRING=Server=localhost,1434;Database=ProjectVG_LoadTest;User Id=sa;Password=LoadTest123!;TrustServerCertificate=true;MultipleActiveResultSets=true
# Redis 연결 (부하테스트 전용 - 운영 Redis와 완전 분리)
REDIS_CONNECTION_STRING=localhost:6381
# JWT 설정
JWT_SECRET_KEY=your-super-secret-jwt-key-here-minimum-32-characters
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=15
JWT_REFRESH_TOKEN_LIFETIME_DAYS=30
# OAuth2 설정 (부하 테스트에서는 비활성화)
OAUTH2_ENABLED=false
# Google OAuth2 설정 (부하 테스트용 더미 설정)
GOOGLE_OAUTH_ENABLED=false
GOOGLE_OAUTH_CLIENT_ID=dummy-client-id
GOOGLE_OAUTH_CLIENT_SECRET=dummy-client-secret
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:7804/auth/oauth2/callback
GOOGLE_OAUTH_AUTO_CREATE_USER=true
GOOGLE_OAUTH_DEFAULT_ROLE=User
# 부하 테스트 전용 설정
ASPNETCORE_ENVIRONMENT=LoadTest
LOGGING_LEVEL=Warning
# ProjectVG API 부하 테스트 환경변수
# 부하 테스트용 설정으로 로그 레벨을 WARN으로 올리고 더미 서버를 사용
# 외부 서비스 연결 (더미 서버 사용)
ExternalServices__LLM__BaseUrl=http://localhost:7808
ExternalServices__Memory__BaseUrl=http://localhost:7812
ExternalServices__TTS__BaseUrl=http://localhost:7816
TTS_API_KEY=dummy-tts-api-key-for-loadtest
# 데이터베이스 연결 (부하테스트 전용 - 운영 DB와 완전 분리)
DB_CONNECTION_STRING=Server=localhost,1434;Database=ProjectVG_LoadTest;User Id=sa;Password=LoadTest123!;TrustServerCertificate=true;MultipleActiveResultSets=true
# Redis 연결 (부하테스트 전용 - 운영 Redis와 완전 분리)
REDIS_CONNECTION_STRING=localhost:6381
# JWT 설정
JWT_SECRET_KEY=your-super-secret-jwt-key-here-minimum-32-characters
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=15
JWT_REFRESH_TOKEN_LIFETIME_DAYS=30
# OAuth2 설정 (부하 테스트에서는 비활성화)
OAuth2__Enabled=false
# Google OAuth2 설정 (부하 테스트용 더미 설정)
GOOGLE_OAUTH_ENABLED=false
GOOGLE_OAUTH_CLIENT_ID=dummy-client-id
GOOGLE_OAUTH_CLIENT_SECRET=dummy-client-secret
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:7804/auth/oauth2/callback
GOOGLE_OAUTH_AUTO_CREATE_USER=true
GOOGLE_OAUTH_DEFAULT_ROLE=User
# 부하 테스트 전용 설정
ASPNETCORE_ENVIRONMENT=LoadTest
LOGGING_LEVEL=Warning
🤖 Prompt for AI Agents
In env.loadtest lines 1-34: environment variable keys use colon-style config
names but must be namespaced using double-underscores for .NET binding; rename
keys such as OAuth2:Enabled -> OAuth2__Enabled and any other colon-delimited
keys, and update all Google OAuth variables to match the config schema (e.g.
GoogleOAuth__Enabled, GoogleOAuth__ClientId, GoogleOAuth__ClientSecret,
GoogleOAuth__RedirectUri, GoogleOAuth__AutoCreateUser, GoogleOAuth__DefaultRole)
so Program.cs can bind them correctly.

Comment on lines +10 to +20
# 데이터베이스 연결 (부하테스트 전용 - 운영 DB와 완전 분리)
DB_CONNECTION_STRING=Server=localhost,1434;Database=ProjectVG_LoadTest;User Id=sa;Password=LoadTest123!;TrustServerCertificate=true;MultipleActiveResultSets=true

# Redis 연결 (부하테스트 전용 - 운영 Redis와 완전 분리)
REDIS_CONNECTION_STRING=localhost:6381

# JWT 설정
JWT_SECRET_KEY=your-super-secret-jwt-key-here-minimum-32-characters
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=15
JWT_REFRESH_TOKEN_LIFETIME_DAYS=30

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

민감정보 커밋 방지 — 샘플 파일로 대체 권장

DB 패스워드/JWT 시크릿이 저장소에 존재합니다. 유출 위험이 있어 env.loadtest.example로 옮기고 실제 env.loadtest는 .gitignore에 추가하세요. 예:

.gitignore

env.loadtest

env.loadtest.example

-DB_CONNECTION_STRING=...;Password=LoadTest123!;...
-JWT_SECRET_KEY=your-super-secret-jwt-key-here-minimum-32-characters
+DB_CONNECTION_STRING=Server=localhost,1434;Database=ProjectVG_LoadTest;User Id=sa;Password=CHANGEME;TrustServerCertificate=true;MultipleActiveResultSets=true
+JWT_SECRET_KEY=CHANGEME_MIN_32_CHARS
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 데이터베이스 연결 (부하테스트 전용 - 운영 DB와 완전 분리)
DB_CONNECTION_STRING=Server=localhost,1434;Database=ProjectVG_LoadTest;User Id=sa;Password=LoadTest123!;TrustServerCertificate=true;MultipleActiveResultSets=true
# Redis 연결 (부하테스트 전용 - 운영 Redis와 완전 분리)
REDIS_CONNECTION_STRING=localhost:6381
# JWT 설정
JWT_SECRET_KEY=your-super-secret-jwt-key-here-minimum-32-characters
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=15
JWT_REFRESH_TOKEN_LIFETIME_DAYS=30
# 데이터베이스 연결 (부하테스트 전용 - 운영 DB와 완전 분리)
DB_CONNECTION_STRING=Server=localhost,1434;Database=ProjectVG_LoadTest;User Id=sa;Password=CHANGEME;TrustServerCertificate=true;MultipleActiveResultSets=true
# Redis 연결 (부하테스트 전용 - 운영 Redis와 완전 분리)
REDIS_CONNECTION_STRING=localhost:6381
# JWT 설정
JWT_SECRET_KEY=CHANGEME_MIN_32_CHARS
JWT_ACCESS_TOKEN_LIFETIME_MINUTES=15
JWT_REFRESH_TOKEN_LIFETIME_DAYS=30
🤖 Prompt for AI Agents
In env.loadtest around lines 10 to 20, the file contains real secrets (DB
password, JWT secret); replace this committed file with a non-sensitive template
named env.loadtest.example containing placeholder values and documentation for
required keys, add env.loadtest to .gitignore so the real file is never
committed, and remove or rotate any secrets already exposed in the repo (or
coordinate secret rotation) to ensure no sensitive credentials remain in source
control.

Comment on lines +82 to +86
public static IServiceCollection AddLoadTestPerformanceServices(this IServiceCollection services)
{
services.AddSingleton<PerformanceCounterService>();
return services;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

CPU 사용률 계산 로직 오류 가능성(하위 서비스) — 잘못된 퍼센트 산출

현재 PerformanceCounterService.GetCpuUsage()가 누적 CPU 시간(TotalProcessorTime ms)을 “퍼센트”처럼 반환하고 있어 경고/모니터링이 왜곡됩니다. 샘플링 기반으로 벽시계 시간 대비 CPU 시간 Δ를 계산하도록 수정 필요.

예시(다른 파일에 적용 필요):

// PerformanceCounterService.cs 일부
private TimeSpan _prevCpu = TimeSpan.Zero;
private DateTime _prevTs = DateTime.UtcNow;

private double GetCpuUsage()
{
    try
    {
        var nowCpu = _currentProcess.TotalProcessorTime;
        var nowTs  = DateTime.UtcNow;

        var cpuDeltaMs = (nowCpu - _prevCpu).TotalMilliseconds;
        var wallMs     = (nowTs - _prevTs).TotalMilliseconds;

        _prevCpu = nowCpu;
        _prevTs  = nowTs;

        if (wallMs <= 0) return 0;

        // 논리 코어 수 고려
        var pct = (cpuDeltaMs / (wallMs * Environment.ProcessorCount)) * 100.0;
        return Math.Clamp(pct, 0, 100);
    }
    catch { return 0; }
}

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching repository for PerformanceCounterService / GetCpuUsage / TotalProcessorTime..."
rg -n --hidden --no-ignore-vcs -S '\bPerformanceCounterService\b' || true
rg -n --hidden --no-ignore-vcs -S '\bGetCpuUsage\b' || true
rg -n --hidden --no-ignore-vcs -S '\bTotalProcessorTime\b' || true

echo
echo "Showing matches with context (up to 5 files)..."
rg -n -C3 --hidden --no-ignore-vcs -S 'PerformanceCounterService|GetCpuUsage|TotalProcessorTime' | sed -n '1,500p' || true

Length of output: 9537


GetCpuUsage가 누적 CPU 시간(ms)을 반환 — 실제 퍼센트(%) 계산으로 수정 필요

위치: ProjectVG.Api/Services/PerformanceCounterService.cs (GetCpuUsage). 현재 GetCpuUsage()가 _currentProcess.TotalProcessorTime.TotalMilliseconds를 반환해 CpuUsagePercent에 누적 밀리초가 들어갑니다. 타이머(5s) 샘플링 기준으로 이전 TotalProcessorTime/타임스탬프와의 Δ를 이용해 벽시계 시간 대비 CPU 시간 비율을 계산하도록 변경하세요.

권장 수정 예시:

private TimeSpan _prevCpu = TimeSpan.Zero;
private DateTime _prevTs = DateTime.UtcNow;

private double GetCpuUsage()
{
    try
    {
        var nowCpu = _currentProcess.TotalProcessorTime;
        var nowTs  = DateTime.UtcNow;

        var cpuDeltaMs = (nowCpu - _prevCpu).TotalMilliseconds;
        var wallMs     = (nowTs  - _prevTs).TotalMilliseconds;

        _prevCpu = nowCpu;
        _prevTs  = nowTs;

        if (wallMs <= 0) return 0;

        var pct = (cpuDeltaMs / (wallMs * Environment.ProcessorCount)) * 100.0;
        return Math.Clamp(pct, 0, 100);
    }
    catch { return 0; }
}

Comment on lines +21 to +25
options.Limits.MaxRequestBodySize = 10_000_000; // 10MB
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(10);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

RequestHeadersTimeout 이중 설정 제거

동일 옵션을 30s → 10s로 두 번 설정하고 있습니다. 하나로 정리하세요.

-        options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
         options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
         options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(10);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
options.Limits.MaxRequestBodySize = 10_000_000; // 10MB
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(10);
options.Limits.MaxRequestBodySize = 10_000_000; // 10MB
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(10);
🤖 Prompt for AI Agents
In ProjectVG.Api/Configuration/LoadTestConfiguration.cs around lines 21 to 25,
the RequestHeadersTimeout is set twice (first to 30s, then to 10s); remove the
duplicate by keeping a single options.Limits.RequestHeadersTimeout assignment
with the intended value (10 seconds) and delete the earlier 30-second line so
the configuration is unambiguous.

@@ -0,0 +1,22 @@
FROM node:18-alpine
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Node 18(alpine)는 2025-04 EOL — LTS 업그레이드 권장

보안 패치 종료된 베이스 이미지는 테스트 환경이라도 리스크입니다. Node 20 LTS(alpine)로 올리고, 런타임을 production으로 고정하세요.

다음 디프로 교체 제안:

-FROM node:18-alpine
+FROM node:20-alpine
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM node:18-alpine
FROM node:20-alpine
🤖 Prompt for AI Agents
In test-loadtest/dummy-tts-server/Dockerfile around line 1, the base image uses
node:18-alpine which reached EOL; update the Dockerfile to use node:20-alpine
(Node 20 LTS) and add an ENV NODE_ENV=production line to lock the runtime to
production; ensure any package installation commands use NODE_ENV=production or
npm ci --only=production / npm prune --production as appropriate and rebuild the
image to verify no runtime breakages.

Comment on lines +9 to +12
RUN npm install --only=production

# Copy source code
COPY . .
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

의존성 설치 방식 최적화 및 재현성 보강

lockfile 기반 설치와 런타임 최적화를 적용하세요. 또한 비루트 실행으로 컨테이너 기본 보안을 올리세요.

-# Install dependencies
-RUN npm install --only=production
+ENV NODE_ENV=production
+# Reproducible, faster CI-style install without dev deps
+RUN npm ci --omit=dev
+
-# Copy source code
-COPY . .
+# Copy source with correct ownership and drop root
+COPY --chown=node:node . .
+USER node
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN npm install --only=production
# Copy source code
COPY . .
ENV NODE_ENV=production
# Reproducible, faster CI-style install without dev deps
RUN npm ci --omit=dev
# Copy source with correct ownership and drop root
COPY --chown=node:node . .
USER node
🤖 Prompt for AI Agents
In test-loadtest/dummy-tts-server/Dockerfile around lines 9 to 12, the
Dockerfile currently runs npm install and copies the entire context which harms
reproducibility and security; change to copy package-lock.json and package.json
first and run a lockfile-based install (npm ci with --only=production or
--omit=dev depending on npm version) to ensure deterministic installs, use a
build pattern that installs dependencies in a stage and only copies needed
artifacts into a smaller runtime image (or at minimum COPY package*.json before
installing), set a non-root user by creating and chowning the app directory and
switching to USER for runtime, and keep the final image containing only
production deps and app files to optimize size and security.

"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"multer": "^1.4.5-lts.1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Multer 버전 확인 필요

multer 1.4.5-lts.1 버전을 사용하고 있습니다. 최신 보안 패치가 적용된 버전인지 확인이 필요합니다.


🌐 Web query:

multer npm package latest version and security vulnerabilities

💡 Result:

Summary (as of 2025-09-13):

  • Latest stable multer release: 2.0.2. [1][2]
  • Known security vulnerabilities (all addressed in 2.0.x):
    • CVE-2025-47935 — memory leak / resource exhaustion (affects <2.0.0). Fixed in 2.0.0. [3][4]
    • CVE-2025-47944 — malformed multipart can cause crash (affects >=1.4.4-lts.1, <2.0.0). Fixed in 2.0.0. [3][5]
    • CVE-2025-48997 — empty-field-name upload leads to crash (affects >=1.4.4-lts.1, <2.0.1). Fixed in 2.0.1. [6]
    • CVE-2025-7338 — malformed request DoS (affects >=1.4.4-lts.1, <2.0.2). Fixed in 2.0.2. [7][8]

Recommendation: upgrade any multer versions older than 2.0.2 to >=2.0.2 (or later) and run your dependency scanner / test suite. [2][3][7]

Sources:

  1. npm package page(s) for multer (version listings). [1]
  2. Snyk package page (latest version 2.0.2, no direct vulnerabilities listed for 2.0.2). [2]
  3. Express security release blog (May 19, 2025). [3]
  4. GitLab / NVD advisories for CVE-2025-47935. [4]
  5. GitLab advisory / CVE-2025-47944. [5]
  6. GitLab advisory / CVE-2025-48997. [6]
  7. GitLab advisory / CVE-2025-7338 and Snyk summary. [7][8]

If you want, I can (a) show the exact npm install command, (b) produce a CI check snippet to fail builds on older multer, or (c scan a package-lock.json for vulnerable versions. Which would you like?


multer 보안 취약 — 2.0.2 이상으로 즉시 업그레이드 필요

test-loadtest/dummy-tts-server/package.json에 "multer": "^1.4.5-lts.1" 사용 중이며, 해당 버전대는 CVE-2025-47935 / CVE-2025-47944 / CVE-2025-48997 / CVE-2025-7338 등 취약점에 영향. 조치: package.json에서 multer를 "^2.0.2" 이상으로 업데이트하고 npm install/ci, 의존성 스캔 및 전체 테스트 실행.

File: test-loadtest/dummy-tts-server/package.json
Lines: 13-13

    "multer": "^1.4.5-lts.1"
🤖 Prompt for AI Agents
In test-loadtest/dummy-tts-server/package.json around line 13, the project
depends on "multer": "^1.4.5-lts.1" which is vulnerable; update the dependency
to "^2.0.2" or later in package.json, run npm install (or npm ci), re-run your
dependency scanner to confirm the vulnerabilities are resolved, and execute the
full test suite; if any breaking changes surface from Multer v2, update the code
to the new Multer v2 API (request handling/middleware usage) accordingly before
committing.

Comment on lines +37 to +47
### 전체 성능 지표

| 지표 | 값 |
|------|-----|
| 총 테스트 시간 | 90초 |
| 총 Chat 요청 수 | 570개 |
| 평균 Chat RPS | 6.33 |
| 전체 RPS | 7.0 |
| 평균 응답 시간 | 1,430ms |
| 최소 응답 시간 | 1,015ms |
| 최대 응답 시간 | 2,024ms |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

지연시간 통계에 p95/p99 누락

평균/최솟값만으로는 tail latency를 판단하기 어렵습니다. p50/p95/p99 및 표준편차를 추가해주세요.

다음과 같이 항목을 보강 제안:

 | 평균 응답 시간 | 1,430ms |
 | 최소 응답 시간 | 1,015ms |
 | 최대 응답 시간 | 2,024ms |
+| p50 / p95 / p99 | 1,350 / 1,900 / 2,100 ms |

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In test-loadtest/LoadTest-Report-Phase1.md around lines 37 to 47, the latency
statistics table only shows mean/min/max which omits tail latency; add p50, p95,
p99 and standard deviation (stddev) rows (or columns) to the performance metrics
table, compute values from the raw response-time dataset, format them
consistently with existing units (ms), and update the table header/rows so it
reads: p50, p95, p99, stddev (alongside mean/min/max and RPS) to reflect tail
latency and variance.

Comment on lines +243 to +264
aggregateStats() {
return this.clients.reduce((total, client) => {
const stats = client.getStats();
return {
requests: total.requests + stats.requests,
success: total.success + stats.success,
errors: total.errors + stats.errors,
totalResponseTime: total.totalResponseTime + stats.totalResponseTime,
minResponseTime: Math.min(total.minResponseTime, stats.minResponseTime === Infinity ? 0 : stats.minResponseTime),
maxResponseTime: Math.max(total.maxResponseTime, stats.maxResponseTime),
avgResponseTime: 0, // 계산 후 설정
successRate: 0 // 계산 후 설정
};
}, {
requests: 0,
success: 0,
errors: 0,
totalResponseTime: 0,
minResponseTime: Infinity,
maxResponseTime: 0
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

집계 통계의 avgResponseTime/성공률이 항상 0으로 출력되는 버그

reduce 결과에 파생 통계를 채우지 않아 최종 리포트가 왜곡됩니다. 아래처럼 계산 로직을 추가하세요.

-    aggregateStats() {
-        return this.clients.reduce((total, client) => {
+    aggregateStats() {
+        const aggregated = this.clients.reduce((total, client) => {
             const stats = client.getStats();
             return {
                 requests: total.requests + stats.requests,
                 success: total.success + stats.success,
                 errors: total.errors + stats.errors,
                 totalResponseTime: total.totalResponseTime + stats.totalResponseTime,
                 minResponseTime: Math.min(total.minResponseTime, stats.minResponseTime === Infinity ? 0 : stats.minResponseTime),
                 maxResponseTime: Math.max(total.maxResponseTime, stats.maxResponseTime),
-                avgResponseTime: 0, // 계산 후 설정
-                successRate: 0 // 계산 후 설정
+                avgResponseTime: 0,
+                successRate: 0
             };
         }, {
             requests: 0,
             success: 0, 
             errors: 0,
             totalResponseTime: 0,
             minResponseTime: Infinity,
             maxResponseTime: 0
-        });
+        });
+        aggregated.avgResponseTime = aggregated.requests > 0
+          ? Math.round(aggregated.totalResponseTime / aggregated.requests) : 0;
+        aggregated.successRate = aggregated.requests > 0
+          ? Math.round((aggregated.success / aggregated.requests) * 100) : 0;
+        return aggregated;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
aggregateStats() {
return this.clients.reduce((total, client) => {
const stats = client.getStats();
return {
requests: total.requests + stats.requests,
success: total.success + stats.success,
errors: total.errors + stats.errors,
totalResponseTime: total.totalResponseTime + stats.totalResponseTime,
minResponseTime: Math.min(total.minResponseTime, stats.minResponseTime === Infinity ? 0 : stats.minResponseTime),
maxResponseTime: Math.max(total.maxResponseTime, stats.maxResponseTime),
avgResponseTime: 0, // 계산 후 설정
successRate: 0 // 계산 후 설정
};
}, {
requests: 0,
success: 0,
errors: 0,
totalResponseTime: 0,
minResponseTime: Infinity,
maxResponseTime: 0
});
}
aggregateStats() {
const aggregated = this.clients.reduce((total, client) => {
const stats = client.getStats();
return {
requests: total.requests + stats.requests,
success: total.success + stats.success,
errors: total.errors + stats.errors,
totalResponseTime: total.totalResponseTime + stats.totalResponseTime,
minResponseTime: Math.min(total.minResponseTime, stats.minResponseTime === Infinity ? 0 : stats.minResponseTime),
maxResponseTime: Math.max(total.maxResponseTime, stats.maxResponseTime),
avgResponseTime: 0,
successRate: 0
};
}, {
requests: 0,
success: 0,
errors: 0,
totalResponseTime: 0,
minResponseTime: Infinity,
maxResponseTime: 0
});
aggregated.avgResponseTime = aggregated.requests > 0
? Math.round(aggregated.totalResponseTime / aggregated.requests) : 0;
aggregated.successRate = aggregated.requests > 0
? Math.round((aggregated.success / aggregated.requests) * 100) : 0;
return aggregated;
}
🤖 Prompt for AI Agents
In test-loadtest/simple-loadtest.js around lines 243 to 264, the reduce
accumulator leaves avgResponseTime and successRate as 0 so aggregate output is
wrong; after finishing the reduction compute derived metrics: set
minResponseTime to 0 if it remained Infinity, compute avgResponseTime as
totalResponseTime / requests only when requests > 0 (else 0), and compute
successRate as (success / requests) * 100 (or 0 when requests === 0); return the
accumulator with these computed fields instead of the placeholder zeros and
ensure division-by-zero is guarded.

@ImGdevel ImGdevel merged commit 81aabed into develop Sep 13, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant