diff --git a/docs/implementation-progress.md b/docs/implementation-progress.md index b1a0eed..894ae78 100644 --- a/docs/implementation-progress.md +++ b/docs/implementation-progress.md @@ -88,6 +88,87 @@ --- +## Phase 5 — 대기열 기반 쿼리 관리 + 타임아웃 최적화 + +- [x] **5.1** QueryStatusUpdate에 `queuePosition`, `totalInQueue` 필드 추가 + - QUEUED 상태에서 대기열 순번 정보 전달 + - 상태: **완료** (2026-02-06) + +- [x] **5.2** ActiveQueryManager 대기열 추적 구현 + - ConcurrentLinkedQueue 기반 대기열 추적 + - `tryAcquireQuerySlotImmediate()`: 즉시 슬롯 획득 + - `waitForSlotWithQueue()`: 2초 간격 슬롯 대기 + - `getQueuePosition()`, `getQueueSize()`: 순번 조회 + - 상태: **완료** (2026-02-06) + +- [x] **5.3** ChunkedTrackStreamingService 거부 → 대기열 전환 + - 즉시 슬롯 획득 실패 → QUEUED 상태 2초 간격 전송하며 최대 2분 대기 + - 대기 중에도 순번/전체 큐 크기 안내 + - 상태: **완료** (2026-02-06) + +- [x] **5.4** StompTrackStreamingService 동일 패턴 적용 + - 상태: **완료** (2026-02-06) + +- [x] **5.5** WebSocketProperties에 SessionProperties 추가 + - idleTimeoutMs, serverHeartbeatMs, clientHeartbeatMs, sockjsDisconnectDelayMs, sendTimeLimitSeconds + - 기본값을 하드코딩에서 설정 바인딩으로 전환 + - 상태: **완료** (2026-02-06) + +- [x] **5.6** WebSocketStompConfig 하드코딩 제거 + - 모든 타임아웃/하트비트/풀 설정을 WebSocketProperties에서 주입 + - 상태: **완료** (2026-02-06) + +- [x] **5.7** application-prod.yml 설정 변경 + - Query 풀: 120 → 180, global: 30 → 60, max-per-session: 3 → 20 + - Session idle: 60s → 15s, Heartbeat: 10s → 5s, SockJS disconnect: 30s → 5s + - Send time limit: 120s → 30s + - 상태: **완료** (2026-02-06) + +- [x] **5.8** AsyncConfig 스레드 풀 확장 + - core: 15 → 40, max: 30 → 120, queue: 500 → 100, keepAlive: 40s → 30s + - 상태: **완료** (2026-02-06) + +--- + +## Phase 6 — 일일 데이터 인메모리 캐시 + +- [x] **6.1** DailyTrackCacheManager 신규 구현 + - @Service + @Async 비동기 캐시 매니저 + - ApplicationReadyEvent에서 비동기 워밍업 (D-1 → D-7 순차 로드) + - 날짜별 CompactVesselTrack 캐시 (ConcurrentHashMap) + - 뷰포트 필터링 지원, 다중 날짜 병합 조회 + - splitQueryRange(): 요청 범위 → (캐시구간, DB구간, 오늘구간) 분리 + - refreshAfterDailyJob(): 배치 완료 후 캐시 갱신 + - 상태: **완료** (2026-02-06) + +- [x] **6.2** DailyTrackCacheProperties 신규 구현 + - @ConfigurationProperties(prefix = "cache.daily-track") + - enabled, retentionDays, maxMemoryGb, warmupAsync + - 상태: **완료** (2026-02-06) + +- [x] **6.3** DailyAggregationJobConfig에 캐시 갱신 리스너 추가 + - JobExecutionListener로 배치 완료 후 refreshAfterDailyJob() 호출 + - 상태: **완료** (2026-02-06) + +- [x] **6.4** ChunkedTrackStreamingService 캐시 통합 + - processDailyStrategy: 날짜별 캐시 히트 체크 → 메모리 조회 / DB 폴백 + - processQueryInChunks: 동일 캐시 우선 패턴 + - 상태: **완료** (2026-02-06) + +- [x] **6.5** StompTrackStreamingService 캐시 통합 + - Daily 전략 처리 시 날짜별 캐시 히트 → 메모리 조회, 미스만 DB 처리 + - 상태: **완료** (2026-02-06) + +- [x] **6.6** application-prod.yml 캐시 설정 추가 + - cache.daily-track: enabled=true, retention-days=7, max-memory-gb=5, warmup-async=true + - 상태: **완료** (2026-02-06) + +- [x] **6.7** WebSocketMonitoringController 캐시 모니터링 엔드포인트 + - GET /api/websocket/daily-cache — 캐시 상태, 날짜별 선박수/메모리, 로드 시각 + - 상태: **완료** (2026-02-06) + +--- + ## 커밋 이력 | 날짜 | Phase | 커밋 메시지 | 해시 | diff --git a/docs/websocket-performance-improvement-report.md b/docs/websocket-performance-improvement-report.md index d9cbea4..89b5b31 100644 --- a/docs/websocket-performance-improvement-report.md +++ b/docs/websocket-performance-improvement-report.md @@ -849,17 +849,130 @@ maxConcurrentGlobal = min(DB커넥션풀 / 2, Async스레드풀max) | `domain/vessel/service/optimization/TrackStreamingOptimizer.java` | 적응형 최적화 (미사용) | | `monitoring/service/TrackStreamingMetrics.java` | 스트리밍 메트릭 | -## 부록 B. 운영 환경 리소스 현황 +## Phase 5. 대기열 기반 쿼리 관리 + 타임아웃 최적화 -| 리소스 | 설정값 | 비고 | -|--------|--------|------| -| JVM Heap | 8~16GB (서버 메모리의 1/3) | G1GC | -| Tomcat Threads | max 200 | | -| Tomcat Connections | max 10,000 | | -| DB Pool (Query) | max 60 | 주 사용 | -| DB Pool (Collect) | max 20 | 읽기 전용 | -| DB Pool (Batch) | max 20 | 메타데이터 | -| trackStreamingExecutor | core 15, max 30, queue 500 | CallerRunsPolicy | -| STOMP Inbound | core 10, max 20, queue 100 | | -| STOMP Outbound | core 20, max 40, queue 5000 | | -| WebSocket Buffer | send 256MB, message 50MB | | +### 5.1 대기열 구조 + +기존: 슬롯 부족 시 즉시 거부 (`ERROR: Server is busy`) +개선: 대기열 순번 안내 후 최대 2분 대기 (`QUEUED: position 3/5`) + +``` +요청 유입 + │ + ▼ +tryAcquireSlotImmediate() + │ + ├── 성공 → 즉시 처리 + │ + └── 실패 → 대기열 진입 + │ ┌──────────────────────────────┐ + │ │ 2초 간격 루프: │ + │ │ 1. tryAcquire (2초 타임아웃) │ + │ │ 2. QUEUED 상태 전송 │ + │ │ (순번/전체 큐 크기) │ + │ │ 3. 최대 2분까지 반복 │ + │ └──────────────────────────────┘ + ├── 슬롯 획득 → 처리 시작 + └── 타임아웃 → ERROR 응답 +``` + +### 5.2 타임아웃 최적화 + +| 설정 | AS-IS | TO-BE | 근거 | +|------|-------|-------|------| +| Query DB 풀 | 120 | **180** | 동시 60쿼리 × 3커넥션 | +| max-concurrent-global | 30 | **60** | 180 / ~3 | +| max-per-session | 3 | **20** | 대기열 방식이므로 넉넉하게 | +| Executor max | 30 | **120** | 60실행 + 60대기 | +| Session idle timeout | 60s | **15s** | 빠른 정리 | +| Heartbeat | 10s/10s | **5s/5s** | 죽은 연결 빠른 감지 | +| SockJS disconnect delay | 30s | **5s** | 빠른 해제 | +| Send time limit | 120s | **30s** | 응답 완료 후 빠른 정리 | + +--- + +## Phase 6. 일일 데이터 인메모리 캐시 + +### 6.1 캐시 아키텍처 + +일일(Daily) 집계 테이블의 7일분 데이터를 메모리에 캐시하여 DB 조회를 생략. + +``` +DailyTrackCacheManager (@Service) +├── cache: ConcurrentHashMap +│ ├── D-7 → {tracks: Map, vesselCount, memorySizeBytes} +│ ├── D-6 → ... +│ ├── ... +│ └── D-1 → ... +│ +├── 비동기 워밍업 (ApplicationReadyEvent → @Async) +│ → 스케줄러/API/WebSocket 차단 없이 백그라운드 로드 +│ → D-1 → D-2 → ... → D-7 (최근 우선) +│ → 각 날짜 로드 완료 즉시 캐시 활성화 +│ +├── 하이브리드 쿼리 분리 +│ → 요청 범위의 모든 날짜를 캐시 확인 +│ → 히트: 메모리 조회, 미스: DB 조회, 오늘: hourly/5min 테이블 +│ → 결과 vessel 기준 병합 +│ +└── 일일 갱신 (DailyAggregationJob 완료 시) + → D-1 (재)로드 + D-8 제거 +``` + +### 6.2 캐시 용량 추정 + +| 항목 | 수치 | +|------|------| +| Daily 테이블 1일분 | ~350MB (DB) | +| 7일분 인메모리 추정 | ~4GB (Java 객체 오버헤드 포함) | +| 최대 메모리 한도 | 5GB (설정 가능) | +| JVM 힙 (권장) | 12GB 이상 | + +### 6.3 쿼리 라우팅 + +``` +요청: 2026-01-30 ~ 2026-02-06 15:00 + +1. 날짜별 분류: + ├─ 02-06 (오늘) → DB 필수 (hourly/5min 테이블) + ├─ 02-05: cache HIT → 메모리 + ├─ 02-04: cache HIT → 메모리 + ├─ ... + ├─ 01-31: cache HIT → 메모리 + └─ 01-30: cache MISS → DB (daily 테이블) + +2. 결과: vessel 기준 병합 → 기존 응답 구조 그대로 +``` + +### 6.4 모니터링 + +``` +GET /api/websocket/daily-cache + +{ + "status": "READY", + "cachedDays": 7, + "totalVessels": 280000, + "totalMemoryMb": 3500, + "days": [ + {"date": "2026-02-05", "vesselCount": 42000, "memorySizeMb": 520, "loadedAt": "..."}, + ... + ] +} +``` + +--- + +## 부록 B. 운영 환경 리소스 현황 (Phase 5~6 반영) + +| 리소스 | AS-IS | TO-BE | 비고 | +|--------|-------|-------|------| +| JVM Heap | 8~16GB | **12~16GB 권장** | 캐시 ~4GB + 운영 | +| DB Pool (Query) | max 120 | **max 180** | WebSocket + REST | +| DB Pool (Collect) | max 80 | max 80 | 배치 Reader | +| DB Pool (Batch) | max 30 | max 30 | 메타데이터 | +| max-concurrent-global | 30 | **60** | Query풀 180 / 3 | +| trackStreamingExecutor | core 15, max 30 | **core 40, max 120** | 대기열 + 실행 | +| Session idle timeout | 60s | **15s** | 빠른 정리 | +| Heartbeat | 10s/10s | **5s/5s** | 빠른 감지 | +| Daily cache | - | **7일분 ~4GB** | 비동기 워밍업 |