@ -0,0 +1,637 @@
import { useState } from 'react' ;
import { Card , CardContent } from '@shared/components/ui/card' ;
import { Badge } from '@shared/components/ui/badge' ;
import { PageContainer , PageHeader } from '@shared/components/layout' ;
import {
Activity , Gauge , Users , Database , Brain , Server ,
CheckCircle , AlertTriangle , TrendingUp , Clock ,
Zap , Shield , BarChart3 , Cpu , HardDrive , Wifi ,
} from 'lucide-react' ;
/ *
* 성 능 모 니 터 링 ( PER - 01 ~ PER - 06 )
*
* 총 사 용 자 3 , 000 명 ( 본 청 200 명 · 상 황 실 100 명 24 / 7 + 지 방 청 · 관 할 서 · 함 정 )
* 통 합 게 이 트 웨 이 ( V - PASS · VTS · E - nav ) + S & P Global AIS A / B 클 래 스 전 제
*
* ① 성 능 현 황 ② 응 답 성 ( PER - 01 ) ③ 처 리 용 량 ( PER - 02 · 03 )
* ④ AI 모 델 성 능 ( PER - 04 ) ⑤ 가 용 성 · 확 장 성 ( PER - 05 · 06 )
* /
type Tab = 'overview' | 'response' | 'capacity' | 'aiModel' | 'availability' ;
// ─── 성능 KPI ──────────────────
const PERF_KPI = [
{ label : '현재 동시접속' , value : '342' , unit : '명' , icon : Users , color : '#3b82f6' , status : 'normal' } ,
{ label : '대시보드 p95' , value : '1.8' , unit : '초' , icon : Gauge , color : '#10b981' , status : 'good' } ,
{ label : '시스템 가동률' , value : '99.87' , unit : '%' , icon : Shield , color : '#06b6d4' , status : 'good' } ,
{ label : 'AI 추론 p95' , value : '1.4' , unit : '초' , icon : Brain , color : '#8b5cf6' , status : 'good' } ,
{ label : '배치 SLA 준수' , value : '100' , unit : '%' , icon : CheckCircle , color : '#10b981' , status : 'good' } ,
{ label : '이벤트 경보' , value : '0' , unit : '건' , icon : AlertTriangle , color : '#f59e0b' , status : 'normal' } ,
] ;
// ─── SLO 적용 그룹 ──────────────────
const USER_GROUPS = [
{ group : '본청 상황실' , users : 100 , concurrent : '100 (100%)' , sla : '≤ 2초 대시보드 / ≤ 1초 지도' , priority : 'critical' as const , note : '24/7 상시 접속 · 최상위 SLO' } ,
{ group : '본청 기타' , users : 100 , concurrent : '50 (50%)' , sla : '≤ 3초 대시보드' , priority : 'high' as const , note : '주간 업무 시간' } ,
{ group : '지방청(5개)' , users : 400 , concurrent : '120 (30%)' , sla : '≤ 3초 대시보드' , priority : 'high' as const , note : '관할해역 상황실' } ,
{ group : '관할서' , users : 1500 , concurrent : '300 (20%)' , sla : '≤ 1.5초 조회' , priority : 'info' as const , note : '주간 피크' } ,
{ group : '함정·파출소' , users : 800 , concurrent : '120 (15%)' , sla : '≤ 1초 API' , priority : 'info' as const , note : '모바일 Agent · 저대역폭' } ,
] ;
// ─── PER-01 응답성 SLO vs 실측 ──────────────────
const RESPONSE_SLO = [
{ target : '메인 대시보드 초기 로드' , slo : '2.0초' , p50 : '0.9초' , p95 : '1.8초' , p99 : '2.4초' , status : 'good' as const } ,
{ target : '위험도 지도 격자 조회' , slo : '2.0초' , p50 : '0.7초' , p95 : '1.6초' , p99 : '2.1초' , status : 'good' as const } ,
{ target : '의심 선박·어구 단순 조회' , slo : '1.5초' , p50 : '0.4초' , p95 : '1.1초' , p99 : '1.7초' , status : 'good' as const } ,
{ target : '복합 분석·시각화' , slo : '5.0초' , p50 : '2.1초' , p95 : '4.2초' , p99 : '5.8초' , status : 'warn' as const } ,
{ target : 'AI 추론 API (단건)' , slo : '2.0초' , p50 : '0.6초' , p95 : '1.4초' , p99 : '1.9초' , status : 'good' as const } ,
{ target : '연계 API (read)' , slo : '500ms' , p50 : '120ms' , p95 : '380ms' , p99 : '510ms' , status : 'warn' as const } ,
{ target : '함정 모바일 Agent API' , slo : '1.0초' , p50 : '0.3초' , p95 : '0.8초' , p99 : '1.2초' , status : 'good' as const } ,
{ target : 'AI 탐지 알림 End-to-End' , slo : '3.0초' , p50 : '1.1초' , p95 : '2.3초' , p99 : '2.8초' , status : 'good' as const } ,
] ;
// ─── PER-02 동시접속·TPS ──────────────────
const CAPACITY_METRICS = [
{ metric : '현재 동시접속' , current : 342 , target : '600 (정상 피크)' , max : '900 (작전 피크)' , utilization : 57 , intent : 'info' as const } ,
{ metric : '현재 TPS' , current : 185 , target : '400 TPS' , max : '600 TPS' , utilization : 46 , intent : 'info' as const } ,
{ metric : '활성 세션' , current : 287 , target : '500' , max : '750' , utilization : 57 , intent : 'info' as const } ,
{ metric : 'WebSocket 연결' , current : 142 , target : '300' , max : '500' , utilization : 47 , intent : 'info' as const } ,
] ;
// ─── PER-03 배치 처리 현황 ──────────────────
const BATCH_JOBS = [
{ name : 'AIS 국내 정제·적재' , schedule : '매 5분' , volume : '~5 GB/일' , sla : '5분' , avg : '2분 18초' , lastRun : '성공' , status : 'success' as const } ,
{ name : 'S&P 글로벌 AIS 집계·격자' , schedule : '00:00 야간' , volume : '~500 GB/일 (압축 후)' , sla : '3시간' , avg : '2시간 12분' , lastRun : '성공' , status : 'success' as const } ,
{ name : '위성영상 타일링·인덱싱' , schedule : '수신 직후' , volume : '건당 2~10 GB' , sla : '2시간/건' , avg : '1시간 24분' , lastRun : '성공' , status : 'success' as const } ,
{ name : '피처 스토어 갱신' , schedule : '매시 정각' , volume : '~200 MB' , sla : '1시간' , avg : '8분 42초' , lastRun : '성공' , status : 'success' as const } ,
{ name : 'AI 모델 재학습 (주간)' , schedule : '일요일 02:00' , volume : '학습셋 전체' , sla : '8시간' , avg : '6시간 18분' , lastRun : '성공' , status : 'success' as const } ,
{ name : '통계·리포트 집계' , schedule : '매시 정각' , volume : '~50 MB' , sla : '30분' , avg : '6분 12초' , lastRun : '성공' , status : 'success' as const } ,
{ name : '해양기상·환경 수집' , schedule : '매시 정각' , volume : '~500 MB' , sla : '10분' , avg : '3분 48초' , lastRun : '지연' , status : 'warn' as const } ,
] ;
// ─── PER-04 AI 모델 성능 ──────────────────
const AI_MODELS = [
{ model : '불법조업 위험도 예측' , accuracy : 92.4 , precision : 89.1 , recall : 87.6 , f1 : 88.3 , rocAuc : 0.948 , target : '≥ 85% 정확도' , status : 'good' as const } ,
{ model : '순찰 경로 추천 (단일)' , accuracy : 94.1 , precision : 91.2 , recall : 90.5 , f1 : 90.8 , rocAuc : 0.961 , target : '≥ 90% 정확도' , status : 'good' as const } ,
{ model : '다함정 협력 경로 최적화' , accuracy : 91.8 , precision : 88.4 , recall : 87.9 , f1 : 88.1 , rocAuc : 0.936 , target : '≥ 85% 정확도' , status : 'good' as const } ,
{ model : '불법 어선 (Dark Vessel) 탐지' , accuracy : 96.2 , precision : 94.8 , recall : 92.3 , f1 : 93.5 , rocAuc : 0.978 , target : '≥ 92% 정확도' , status : 'good' as const } ,
{ model : '불법 어망·어구 탐지' , accuracy : 88.7 , precision : 85.2 , recall : 83.6 , f1 : 84.4 , rocAuc : 0.912 , target : '≥ 85% 정확도' , status : 'warn' as const } ,
{ model : 'AIS 조작 패턴 감지' , accuracy : 93.5 , precision : 91.7 , recall : 89.4 , f1 : 90.5 , rocAuc : 0.954 , target : '≥ 90% 정확도' , status : 'good' as const } ,
] ;
// ─── PER-05 가용성·장애복구 ──────────────────
const AVAILABILITY_METRICS = [
{ component : '애플리케이션 서버 (K8s)' , uptime : '99.98%' , rto : '≤ 30초' , rpo : '0 (stateless)' , lastIncident : '없음' , status : 'good' as const } ,
{ component : 'DB (PostgreSQL HA)' , uptime : '99.95%' , rto : '≤ 60초' , rpo : '≤ 5초' , lastIncident : '2026-03-28' , status : 'good' as const } ,
{ component : 'TimescaleDB (Hot)' , uptime : '99.92%' , rto : '≤ 120초' , rpo : '≤ 15초' , lastIncident : '2026-04-02' , status : 'good' as const } ,
{ component : '벡터 DB (RAG)' , uptime : '99.87%' , rto : '≤ 180초' , rpo : '≤ 30초' , lastIncident : '2026-04-08' , status : 'warn' as const } ,
{ component : 'NAS 스토리지' , uptime : '99.99%' , rto : '≤ 60초' , rpo : '0 (이중화)' , lastIncident : '없음' , status : 'good' as const } ,
{ component : '통합게이트웨이' , uptime : '99.89%' , rto : '≤ 60초' , rpo : '≤ 10초' , lastIncident : '2026-04-05' , status : 'good' as const } ,
{ component : 'S&P Global AIS API' , uptime : '99.41%' , rto : 'Fallback 즉시' , rpo : '국내 신호 대체' , lastIncident : '2026-04-12' , status : 'warn' as const } ,
{ component : 'LLM Q&A 서버 (H200)' , uptime : '99.76%' , rto : '≤ 120초' , rpo : '0 (stateless)' , lastIncident : '2026-04-09' , status : 'good' as const } ,
] ;
// ─── PER-06 확장성·자원 사용률 ──────────────────
const RESOURCE_USAGE = [
{ resource : '워커 노드 CPU' , current : 48 , threshold : 70 , max : 80 , scalePolicy : 'HPA 자동 확장' , unit : '%' } ,
{ resource : '워커 노드 메모리' , current : 52 , threshold : 75 , max : 85 , scalePolicy : 'HPA 자동 확장' , unit : '%' } ,
{ resource : 'AI 서버 GPU (RTX pro 6000)' , current : 61 , threshold : 80 , max : 90 , scalePolicy : '추론 큐잉' , unit : '%' } ,
{ resource : 'LLM 서버 GPU (H200)' , current : 44 , threshold : 75 , max : 85 , scalePolicy : '요청 병합·배치' , unit : '%' } ,
{ resource : 'DB 연결 풀' , current : 128 , threshold : 300 , max : 400 , scalePolicy : 'PgBouncer 풀 확대' , unit : '개' } ,
{ resource : 'NAS 사용량' , current : 28 , threshold : 75 , max : 90 , scalePolicy : '콜드 티어 이관' , unit : '% (100TB)' } ,
{ resource : 'Kafka 컨슈머 Lag' , current : 142 , threshold : 5000 , max : 10000 , scalePolicy : '파티션 증설' , unit : 'msg' } ,
{ resource : 'Redis 캐시 메모리' , current : 38 , threshold : 70 , max : 85 , scalePolicy : 'Eviction + 클러스터 확장' , unit : '%' } ,
] ;
// ─── 성능 영향 최소화 전략 (S&P 글로벌 대응) ──────────────────
const IMPACT_REDUCTION = [
{ strategy : '이중 수집 파이프라인 물리 분리' , target : '국내 vs 글로벌 격리' , effect : '글로벌 장애 → 국내 무영향' , per : 'PER-01·05' } ,
{ strategy : '경계 조기 필터링' , target : '지리·선박 클래스 필터' , effect : '원본 50~80% 감축' , per : 'PER-03' } ,
{ strategy : '스트림·백프레셔 (Kafka)' , target : 'Lag 임계 초과 시 다운샘플링' , effect : '온라인 무영향' , per : 'PER-01·03' } ,
{ strategy : '티어드 스토리지 (Hot/Warm/Cold)' , target : '1~7일 / 30일 / 이후' , effect : '쿼리 비용 최소화' , per : 'PER-03·06' } ,
{ strategy : '공간 사전 집계 (H3 격자)' , target : 'Materialized View' , effect : '대시보드 Redis만 조회' , per : 'PER-01' } ,
{ strategy : 'Circuit Breaker (S&P)' , target : '실패율 50% 차단' , effect : '국내 신호 Fallback' , per : 'PER-05' } ,
{ strategy : 'K8s PriorityClass 격리' , target : '온라인 vs 배치' , effect : '상황실 SLO 절대 보장' , per : 'PER-01·03' } ,
{ strategy : 'HPA 자동 확장' , target : 'CPU/메모리 70% 임계' , effect : '피크 자동 대응' , per : 'PER-02·06' } ,
] ;
const statusIntent = ( s : 'good' | 'warn' | 'critical' | 'success' ) : 'success' | 'warning' | 'critical' = > {
if ( s === 'good' || s === 'success' ) return 'success' ;
if ( s === 'warn' ) return 'warning' ;
return 'critical' ;
} ;
const barColor = ( ratio : number ) : string = > {
if ( ratio < 0.6 ) return '#10b981' ;
if ( ratio < 0.8 ) return '#f59e0b' ;
return '#ef4444' ;
} ;
export function PerformanceMonitoring() {
const [ tab , setTab ] = useState < Tab > ( 'overview' ) ;
return (
< PageContainer >
< PageHeader
icon = { Activity }
iconColor = "text-cyan-400"
title = "성능 모니터링"
description = "PER-01~06 | 응답성·처리용량·AI 모델·가용성·확장성 실시간 현황"
demo
/ >
{ /* 탭 */ }
< div className = "flex gap-0 border-b border-border" >
{ ( [
{ key : 'overview' as Tab , icon : BarChart3 , label : '성능 현황' } ,
{ key : 'response' as Tab , icon : Gauge , label : '응답성 (PER-01)' } ,
{ key : 'capacity' as Tab , icon : Users , label : '처리용량 (PER-02·03)' } ,
{ key : 'aiModel' as Tab , icon : Brain , label : 'AI 모델 (PER-04)' } ,
{ key : 'availability' as Tab , icon : Shield , label : '가용성·확장성 (PER-05·06)' } ,
] ) . map ( t = > (
< button type = "button" key = { t . key } onClick = { ( ) = > setTab ( t . key ) }
className = { ` flex items-center gap-1.5 px-4 py-2.5 text-[11px] font-medium border-b-2 transition-colors ${ tab === t . key ? 'text-cyan-400 border-cyan-400' : 'text-hint border-transparent hover:text-label' } ` } >
< t.icon className = "w-3.5 h-3.5" / > { t . label }
< / button >
) ) }
< / div >
{ /* ── ① 성능 현황 ── */ }
{ tab === 'overview' && (
< div className = "space-y-3" >
{ /* KPI */ }
< div className = "grid grid-cols-6 gap-2" >
{ PERF_KPI . map ( k = > (
< div key = { k . label } className = "flex items-center gap-3 px-4 py-3 rounded-xl border border-border bg-card" style = { { borderLeftColor : k.color , borderLeftWidth : 3 } } >
< k.icon className = "w-5 h-5" style = { { color : k.color } } / >
< div >
< div className = "text-lg font-bold" style = { { color : k.color } } >
{ k . value } < span className = "text-[10px] ml-1 text-hint" > { k . unit } < / span >
< / div >
< div className = "text-[9px] text-hint" > { k . label } < / div >
< / div >
< / div >
) ) }
< / div >
{ /* 사용자 그룹별 SLO */ }
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Users className = "w-4 h-4 text-cyan-400" / >
< span className = "text-[12px] font-bold text-heading" > 사 용 자 그 룹 별 SLO ( 총 2 , 900 명 + 추 정 ) < / span >
< Badge intent = "info" size = "xs" > 본 청 200 · 상 황 실 100 확 정 < / Badge >
< / div >
< table className = "w-full text-[11px]" >
< thead className = "text-hint text-[10px] border-b border-border" >
< tr >
< th className = "text-left py-2 px-2 font-medium" > 그 룹 < / th >
< th className = "text-right py-2 px-2 font-medium" > 총 인 원 < / th >
< th className = "text-right py-2 px-2 font-medium" > 동 시 접 속 추 정 < / th >
< th className = "text-left py-2 px-2 font-medium" > SLA < / th >
< th className = "text-center py-2 px-2 font-medium" > 우 선 순 위 < / th >
< th className = "text-left py-2 px-2 font-medium" > 특 성 < / th >
< / tr >
< / thead >
< tbody >
{ USER_GROUPS . map ( g = > (
< tr key = { g . group } className = "border-b border-border/40 hover:bg-surface-overlay/40" >
< td className = "py-2 px-2 text-label font-medium" > { g . group } < / td >
< td className = "py-2 px-2 text-right text-label" > { g . users . toLocaleString ( ) } 명 < / td >
< td className = "py-2 px-2 text-right text-heading font-medium" > { g . concurrent } < / td >
< td className = "py-2 px-2 text-label" > { g . sla } < / td >
< td className = "py-2 px-2 text-center" > < Badge intent = { g . priority } size = "xs" > { g . priority === 'critical' ? '최상' : g . priority === 'high' ? '높음' : '일반' } < / Badge > < / td >
< td className = "py-2 px-2 text-hint" > { g . note } < / td >
< / tr >
) ) }
< / tbody >
< / table >
< / CardContent > < / Card >
{ /* 성능 영향 최소화 전략 */ }
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Zap className = "w-4 h-4 text-amber-400" / >
< span className = "text-[12px] font-bold text-heading" > 성 능 영 향 최 소 화 전 략 ( 글 로 벌 AIS 대 응 ) < / span >
< / div >
< div className = "grid grid-cols-2 gap-2" >
{ IMPACT_REDUCTION . map ( ( s , i ) = > (
< div key = { s . strategy } className = "flex items-start gap-2 px-3 py-2 bg-surface-overlay rounded-lg" >
< div className = "w-5 h-5 rounded-full bg-amber-500/20 flex items-center justify-center shrink-0 text-[9px] font-bold text-amber-400" > { i + 1 } < / div >
< div className = "flex-1" >
< div className = "flex items-center gap-2 mb-0.5" >
< span className = "text-[11px] text-heading font-medium" > { s . strategy } < / span >
< Badge intent = "info" size = "xs" > { s . per } < / Badge >
< / div >
< div className = "text-[9px] text-hint mb-0.5" > 대 상 : { s . target } < / div >
< div className = "text-[9px] text-green-400" > 효 과 : { s . effect } < / div >
< / div >
< / div >
) ) }
< / div >
< / CardContent > < / Card >
< / div >
) }
{ /* ── ② 응답성 (PER-01) ── */ }
{ tab === 'response' && (
< div className = "space-y-3" >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center justify-between mb-3" >
< div className = "flex items-center gap-2" >
< Gauge className = "w-4 h-4 text-cyan-400" / >
< span className = "text-[12px] font-bold text-heading" > PER - 01 서 비 스 응 답 성 — SLO vs 실 측 ( p50 / p95 / p99 ) < / span >
< / div >
< Badge intent = "success" size = "sm" > TER - 03 검 증 통 과 < / Badge >
< / div >
< table className = "w-full text-[11px]" >
< thead className = "text-hint text-[10px] border-b border-border" >
< tr >
< th className = "text-left py-2 px-2 font-medium" > 대 상 < / th >
< th className = "text-right py-2 px-2 font-medium" > SLO 목 표 < / th >
< th className = "text-right py-2 px-2 font-medium" > p50 < / th >
< th className = "text-right py-2 px-2 font-medium" > p95 < / th >
< th className = "text-right py-2 px-2 font-medium" > p99 < / th >
< th className = "text-center py-2 px-2 font-medium" > 상 태 < / th >
< / tr >
< / thead >
< tbody >
{ RESPONSE_SLO . map ( r = > (
< tr key = { r . target } className = "border-b border-border/40 hover:bg-surface-overlay/40" >
< td className = "py-2 px-2 text-label font-medium" > { r . target } < / td >
< td className = "py-2 px-2 text-right text-cyan-400 font-medium" > { r . slo } < / td >
< td className = "py-2 px-2 text-right text-hint" > { r . p50 } < / td >
< td className = "py-2 px-2 text-right text-heading font-medium" > { r . p95 } < / td >
< td className = "py-2 px-2 text-right text-label" > { r . p99 } < / td >
< td className = "py-2 px-2 text-center" > < Badge intent = { statusIntent ( r . status ) } size = "xs" > { r . status === 'good' ? '정상' : '주의' } < / Badge > < / td >
< / tr >
) ) }
< / tbody >
< / table >
< / CardContent > < / Card >
< div className = "grid grid-cols-2 gap-3" >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Shield className = "w-4 h-4 text-purple-400" / >
< span className = "text-[12px] font-bold text-heading" > 상 황 실 전 용 SLO ( 24 / 7 100 명 ) < / span >
< / div >
< div className = "space-y-2" >
{ [
{ item : '메인 대시보드 초기 로드' , target : '≤ 2초' , current : '1.8초' , met : true } ,
{ item : '위험도 지도 실시간 갱신' , target : '≤ 1초' , current : '0.7초' , met : true } ,
{ item : 'AI 탐지 알림 수신 → 표출' , target : '≤ 3초 E2E' , current : '2.3초' , met : true } ,
{ item : '단속 계획·경로 조회' , target : '≤ 1.5초' , current : '1.1초' , met : true } ,
{ item : '장애 시 세션 유지' , target : '< 30초 복구' , current : '18초' , met : true } ,
] . map ( s = > (
< div key = { s . item } className = "flex items-center justify-between px-3 py-2 bg-surface-overlay rounded-lg" >
< div >
< div className = "text-[11px] text-heading font-medium" > { s . item } < / div >
< div className = "text-[9px] text-hint" > 목 표 : { s . target } < / div >
< / div >
< div className = "flex items-center gap-2" >
< span className = "text-[11px] text-green-400 font-bold" > { s . current } < / span >
{ s . met ? < CheckCircle className = "w-4 h-4 text-green-500" / > : < AlertTriangle className = "w-4 h-4 text-amber-500" / > }
< / div >
< / div >
) ) }
< / div >
< / CardContent > < / Card >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Clock className = "w-4 h-4 text-blue-400" / >
< span className = "text-[12px] font-bold text-heading" > 측 정 방 법 론 < / span >
< / div >
< ul className = "space-y-2 text-[11px]" >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > 샘 플 링 : < / strong > < span className = "text-label" > 1 초 간 격 p50 / p95 / p99 집 계 < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > 도 구 : < / strong > < span className = "text-label" > OpenTelemetry + Prometheus + Grafana < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > APM : < / strong > < span className = "text-label" > 분 산 추 적 + Trace ID 요 청 단 위 관 통 < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > API 재 시 도 : < / strong > < span className = "text-label" > 3 회 · Exponential Backoff · 타 임 아 웃 3 초 < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > 경 보 : < / strong > < span className = "text-label" > SLO 위 반 지 속 5 분 → PagerDuty < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > 원 인 분 석 : < / strong > < span className = "text-label" > RED / USE 방 법 론 + 로 그 · 메 트 릭 · 추 적 상 관 분 석 < / span > < / div >
< / li >
< / ul >
< / CardContent > < / Card >
< / div >
< / div >
) }
{ /* ── ③ 처리용량 (PER-02·03) ── */ }
{ tab === 'capacity' && (
< div className = "space-y-3" >
{ /* 동시접속·TPS */ }
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Users className = "w-4 h-4 text-blue-400" / >
< span className = "text-[12px] font-bold text-heading" > PER - 02 동 시 접 속 · 처 리 용 량 ( 정 상 피 크 600 / 작 전 피 크 900 ) < / span >
< / div >
< div className = "grid grid-cols-4 gap-3" >
{ CAPACITY_METRICS . map ( c = > (
< div key = { c . metric } className = "px-3 py-3 bg-surface-overlay rounded-lg" >
< div className = "text-[10px] text-hint mb-1" > { c . metric } < / div >
< div className = "text-xl font-bold text-heading mb-1" > { c . current . toLocaleString ( ) } < / div >
< div className = "text-[9px] text-label mb-2" > 목 표 { c . target } / 최 대 { c . max } < / div >
< div className = "h-1.5 bg-surface-raised rounded-full overflow-hidden" >
< div className = "h-full rounded-full transition-all" style = { { width : ` ${ c . utilization } % ` , backgroundColor : barColor ( c . utilization / 100 ) } } / >
< / div >
< div className = "text-[9px] text-hint mt-1" > { c . utilization } % 사 용 중 < / div >
< / div >
) ) }
< / div >
< / CardContent > < / Card >
{ /* 배치 작업 현황 */ }
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Database className = "w-4 h-4 text-purple-400" / >
< span className = "text-[12px] font-bold text-heading" > PER - 03 배 치 · 대 용 량 처 리 현 황 < / span >
< Badge intent = "success" size = "xs" > SLA 준 수 6 / 7 < / Badge >
< / div >
< table className = "w-full text-[11px]" >
< thead className = "text-hint text-[10px] border-b border-border" >
< tr >
< th className = "text-left py-2 px-2 font-medium" > 배 치 작 업 < / th >
< th className = "text-left py-2 px-2 font-medium" > 스 케 줄 < / th >
< th className = "text-right py-2 px-2 font-medium" > 처 리 볼 륨 < / th >
< th className = "text-right py-2 px-2 font-medium" > SLA < / th >
< th className = "text-right py-2 px-2 font-medium" > 평 균 소 요 < / th >
< th className = "text-center py-2 px-2 font-medium" > 최 근 실 행 < / th >
< / tr >
< / thead >
< tbody >
{ BATCH_JOBS . map ( j = > (
< tr key = { j . name } className = "border-b border-border/40 hover:bg-surface-overlay/40" >
< td className = "py-2 px-2 text-label font-medium" > { j . name } < / td >
< td className = "py-2 px-2 text-hint" > { j . schedule } < / td >
< td className = "py-2 px-2 text-right text-label" > { j . volume } < / td >
< td className = "py-2 px-2 text-right text-cyan-400" > { j . sla } < / td >
< td className = "py-2 px-2 text-right text-heading font-medium" > { j . avg } < / td >
< td className = "py-2 px-2 text-center" > < Badge intent = { statusIntent ( j . status ) } size = "xs" > { j . lastRun } < / Badge > < / td >
< / tr >
) ) }
< / tbody >
< / table >
< / CardContent > < / Card >
{ /* 처리 볼륨 산정 */ }
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< HardDrive className = "w-4 h-4 text-cyan-400" / >
< span className = "text-[12px] font-bold text-heading" > 데 이 터 처 리 볼 륨 ( 국 내 + S & P 글 로 벌 ) < / span >
< / div >
< div className = "grid grid-cols-3 gap-3 text-[11px]" >
< div className = "px-3 py-3 bg-surface-overlay rounded-lg" >
< div className = "text-[10px] text-hint mb-1" > 일 수 집 ( 원 본 ) < / div >
< div className = "text-xl font-bold text-heading" > 1.6 ~ 3.2 TB < / div >
< div className = "text-[9px] text-label mt-1" > AIS ( 국 내 ) + S & P A / B + 위 성 + 환 경 < / div >
< / div >
< div className = "px-3 py-3 bg-surface-overlay rounded-lg" >
< div className = "text-[10px] text-hint mb-1" > 일 적 재 ( 필 터 · 압 축 후 ) < / div >
< div className = "text-xl font-bold text-heading" > 330 ~ 900 GB < / div >
< div className = "text-[9px] text-green-400 mt-1" > 경 계 필 터 링 50 ~ 80 % 감 축 < / div >
< / div >
< div className = "px-3 py-3 bg-surface-overlay rounded-lg" >
< div className = "text-[10px] text-hint mb-1" > 3 년 누 적 ( 티 어 드 ) < / div >
< div className = "text-xl font-bold text-heading" > ~ 360 TB ~ 1 PB < / div >
< div className = "text-[9px] text-amber-400 mt-1" > NAS 100 TB → 객 체 스 토 리 지 이 관 < / div >
< / div >
< / div >
< / CardContent > < / Card >
< / div >
) }
{ /* ── ④ AI 모델 성능 (PER-04) ── */ }
{ tab === 'aiModel' && (
< div className = "space-y-3" >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center justify-between mb-3" >
< div className = "flex items-center gap-2" >
< Brain className = "w-4 h-4 text-purple-400" / >
< span className = "text-[12px] font-bold text-heading" > PER - 04 AI 모 델 성 능 지 표 < / span >
< / div >
< div className = "flex items-center gap-2" >
< Badge intent = "success" size = "xs" > 6 개 모 델 운 영 중 < / Badge >
< Badge intent = "warning" size = "xs" > 어 망 · 어 구 모 델 개 선 필 요 < / Badge >
< / div >
< / div >
< table className = "w-full text-[11px]" >
< thead className = "text-hint text-[10px] border-b border-border" >
< tr >
< th className = "text-left py-2 px-2 font-medium" > 모 델 < / th >
< th className = "text-right py-2 px-2 font-medium" > 정 확 도 < / th >
< th className = "text-right py-2 px-2 font-medium" > 정 밀 도 < / th >
< th className = "text-right py-2 px-2 font-medium" > 재 현 율 < / th >
< th className = "text-right py-2 px-2 font-medium" > F1 < / th >
< th className = "text-right py-2 px-2 font-medium" > ROC - AUC < / th >
< th className = "text-left py-2 px-2 font-medium" > 목 표 < / th >
< th className = "text-center py-2 px-2 font-medium" > 상 태 < / th >
< / tr >
< / thead >
< tbody >
{ AI_MODELS . map ( m = > (
< tr key = { m . model } className = "border-b border-border/40 hover:bg-surface-overlay/40" >
< td className = "py-2 px-2 text-label font-medium" > { m . model } < / td >
< td className = "py-2 px-2 text-right text-heading font-medium" > { m . accuracy } % < / td >
< td className = "py-2 px-2 text-right text-label" > { m . precision } % < / td >
< td className = "py-2 px-2 text-right text-label" > { m . recall } % < / td >
< td className = "py-2 px-2 text-right text-label" > { m . f1 } < / td >
< td className = "py-2 px-2 text-right text-cyan-400" > { m . rocAuc } < / td >
< td className = "py-2 px-2 text-hint text-[10px]" > { m . target } < / td >
< td className = "py-2 px-2 text-center" > < Badge intent = { statusIntent ( m . status ) } size = "xs" > { m . status === 'good' ? '통과' : '주의' } < / Badge > < / td >
< / tr >
) ) }
< / tbody >
< / table >
< / CardContent > < / Card >
< div className = "grid grid-cols-2 gap-3" >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< TrendingUp className = "w-4 h-4 text-green-400" / >
< span className = "text-[12px] font-bold text-heading" > 모 델 성 능 저 하 대 응 < / span >
< / div >
< ul className = "space-y-2 text-[11px]" >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > 학 습 / 검 증 / 테 스 트 분 할 : < / strong > < span className = "text-label" > 70 / 15 / 15 비 율 , K - Fold 5 < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > 드 리 프 트 탐 지 : < / strong > < span className = "text-label" > 입 력 분 포 KL divergence 주 간 모 니 터 링 < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > 성 능 저 하 임 계 : < / strong > < span className = "text-label" > F1 3 % p 하 락 시 자 동 재 학 습 트 리 거 < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > 설 명 가 능 성 : < / strong > < span className = "text-label" > Feature Importance + SHAP 값 제 공 < / span > < / div >
< / li >
< li className = "flex items-start gap-2" >
< CheckCircle className = "w-3.5 h-3.5 text-green-500 shrink-0 mt-0.5" / >
< div > < strong className = "text-heading" > A / B 테 스 트 : < / strong > < span className = "text-label" > Shadow → Canary 5 % → 50 % → 100 % 단 계 배 포 < / span > < / div >
< / li >
< / ul >
< / CardContent > < / Card >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Cpu className = "w-4 h-4 text-amber-400" / >
< span className = "text-[12px] font-bold text-heading" > 추 론 성 능 ( GPU 활 용 ) < / span >
< / div >
< div className = "space-y-3" >
< div >
< div className = "flex items-center justify-between mb-1" >
< span className = "text-[11px] text-label" > AI 서 버 ( RTX pro 6000 Blackwell × 2 ) < / span >
< span className = "text-[11px] text-heading font-bold" > 61 % < / span >
< / div >
< div className = "h-2 bg-surface-raised rounded-full overflow-hidden" >
< div className = "h-full bg-amber-500 rounded-full" style = { { width : '61%' } } / >
< / div >
< div className = "text-[9px] text-hint mt-1" > 추 론 단 건 평 균 1.4 초 · 큐 대 기 & lt ; 200 ms < / div >
< / div >
< div >
< div className = "flex items-center justify-between mb-1" >
< span className = "text-[11px] text-label" > LLM 서 버 ( H200 × 2 ) < / span >
< span className = "text-[11px] text-heading font-bold" > 44 % < / span >
< / div >
< div className = "h-2 bg-surface-raised rounded-full overflow-hidden" >
< div className = "h-full bg-green-500 rounded-full" style = { { width : '44%' } } / >
< / div >
< div className = "text-[9px] text-hint mt-1" > Q & A 스 트 리 밍 첫 토 큰 평 균 380 ms < / div >
< / div >
< div className = "pt-2 border-t border-border/40" >
< div className = "text-[10px] text-hint mb-1" > 동 시 추 론 요 청 처 리 < / div >
< div className = "flex items-center gap-2" >
< Badge intent = "info" size = "xs" > 최 대 100 요 청 / 초 < / Badge >
< Badge intent = "success" size = "xs" > 큐 잉 지 연 & lt ; 1 % < / Badge >
< / div >
< / div >
< / div >
< / CardContent > < / Card >
< / div >
< / div >
) }
{ /* ── ⑤ 가용성·확장성 (PER-05·06) ── */ }
{ tab === 'availability' && (
< div className = "space-y-3" >
{ /* 가용성 */ }
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Shield className = "w-4 h-4 text-cyan-400" / >
< span className = "text-[12px] font-bold text-heading" > PER - 05 가 용 성 및 장 애 복 구 ( 목 표 ≥ 99.9 % ) < / span >
< / div >
< table className = "w-full text-[11px]" >
< thead className = "text-hint text-[10px] border-b border-border" >
< tr >
< th className = "text-left py-2 px-2 font-medium" > 구 성 요 소 < / th >
< th className = "text-right py-2 px-2 font-medium" > 가 동 률 < / th >
< th className = "text-right py-2 px-2 font-medium" > RTO < / th >
< th className = "text-right py-2 px-2 font-medium" > RPO < / th >
< th className = "text-left py-2 px-2 font-medium" > 최 근 장 애 < / th >
< th className = "text-center py-2 px-2 font-medium" > 상 태 < / th >
< / tr >
< / thead >
< tbody >
{ AVAILABILITY_METRICS . map ( a = > (
< tr key = { a . component } className = "border-b border-border/40 hover:bg-surface-overlay/40" >
< td className = "py-2 px-2 text-label font-medium" > { a . component } < / td >
< td className = "py-2 px-2 text-right text-heading font-medium" > { a . uptime } < / td >
< td className = "py-2 px-2 text-right text-cyan-400" > { a . rto } < / td >
< td className = "py-2 px-2 text-right text-label" > { a . rpo } < / td >
< td className = "py-2 px-2 text-hint" > { a . lastIncident } < / td >
< td className = "py-2 px-2 text-center" > < Badge intent = { statusIntent ( a . status ) } size = "xs" > { a . status === 'good' ? '정상' : '주의' } < / Badge > < / td >
< / tr >
) ) }
< / tbody >
< / table >
< / CardContent > < / Card >
{ /* 확장성 */ }
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-3" >
< Server className = "w-4 h-4 text-purple-400" / >
< span className = "text-[12px] font-bold text-heading" > PER - 06 확 장 성 및 자 원 사 용 률 < / span >
< Badge intent = "info" size = "xs" > 2 배 ( 6 , 000 명 ) 확 장 목 표 < / Badge >
< / div >
< div className = "grid grid-cols-2 gap-3" >
{ RESOURCE_USAGE . map ( r = > {
const ratio = r . current / r . max ;
return (
< div key = { r . resource } className = "px-3 py-3 bg-surface-overlay rounded-lg" >
< div className = "flex items-center justify-between mb-2" >
< span className = "text-[11px] text-label font-medium" > { r . resource } < / span >
< span className = "text-[11px] text-heading font-bold" > { r . current } { r . unit === '%' || r . unit . startsWith ( '%' ) ? '%' : ' ' + r . unit } < / span >
< / div >
< div className = "h-2 bg-surface-raised rounded-full overflow-hidden mb-2" >
< div className = "h-full rounded-full transition-all" style = { { width : ` ${ ( ratio ) * 100 } % ` , backgroundColor : barColor ( ratio ) } } / >
< / div >
< div className = "flex items-center justify-between text-[9px]" >
< span className = "text-hint" > 경 보 { r . threshold } { r . unit === '%' || r . unit . startsWith ( '%' ) ? '%' : '' } · 한 계 { r . max } { r . unit === '%' || r . unit . startsWith ( '%' ) ? '%' : '' } < / span >
< Badge intent = "muted" size = "xs" > { r . scalePolicy } < / Badge >
< / div >
< / div >
) ;
} ) }
< / div >
< / CardContent > < / Card >
{ /* 요약 지표 */ }
< div className = "grid grid-cols-4 gap-3" >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-2" >
< Wifi className = "w-4 h-4 text-cyan-400" / >
< span className = "text-[11px] font-bold text-heading" > 연 간 가 동 률 목 표 < / span >
< / div >
< div className = "text-2xl font-bold text-cyan-400" > 99.9 % < / div >
< div className = "text-[9px] text-hint mt-1" > 월 간 다 운 타 임 ≤ 43 분 < / div >
< / CardContent > < / Card >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-2" >
< Clock className = "w-4 h-4 text-purple-400" / >
< span className = "text-[11px] font-bold text-heading" > RTO 평 균 < / span >
< / div >
< div className = "text-2xl font-bold text-purple-400" > ≤ 60 초 < / div >
< div className = "text-[9px] text-hint mt-1" > 자 동 페 일 오 버 · Self - healing < / div >
< / CardContent > < / Card >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-2" >
< Database className = "w-4 h-4 text-green-400" / >
< span className = "text-[11px] font-bold text-heading" > RPO 평 균 < / span >
< / div >
< div className = "text-2xl font-bold text-green-400" > ≤ 10 초 < / div >
< div className = "text-[9px] text-hint mt-1" > 실 시 간 복 제 + 백 업 이 중 화 < / div >
< / CardContent > < / Card >
< Card > < CardContent className = "p-4" >
< div className = "flex items-center gap-2 mb-2" >
< TrendingUp className = "w-4 h-4 text-amber-400" / >
< span className = "text-[11px] font-bold text-heading" > Scale - out 여 유 < / span >
< / div >
< div className = "text-2xl font-bold text-amber-400" > × 2 < / div >
< div className = "text-[9px] text-hint mt-1" > 6 , 000 명 까 지 선 형 확 장 < / div >
< / CardContent > < / Card >
< / div >
< / div >
) }
< / PageContainer >
) ;
}