Files
Document-AI-server/src/background_ai_service.py
hyungi 18e53355a0 feat: 백그라운드 AI 서비스 및 실시간 대시보드 추가
- 백그라운드에서 AI 모델을 항상 로딩된 상태로 유지
- 실시간 모델 상태 모니터링 대시보드 웹페이지 구현
- 시스템 성능 지표 수집 및 시각화
- AI 모델 재시작, 캐시 정리 등 관리 기능
- 작업 큐 시스템으로 처리 효율성 향상
- psutil 의존성 추가로 시스템 모니터링 강화
2025-07-25 06:40:52 +09:00

283 lines
11 KiB
Python

#!/usr/bin/env python3
"""
백그라운드 AI 모델 서비스 및 모니터링 시스템
실시간 모델 상태 추적 및 성능 지표 수집
"""
import asyncio
import time
import psutil
import threading
from datetime import datetime, timedelta
from dataclasses import dataclass, asdict
from typing import Dict, List, Optional
from pathlib import Path
import json
import logging
from integrated_translation_system import IntegratedTranslationSystem
@dataclass
class ModelStatus:
"""개별 모델 상태 정보"""
name: str
status: str # "loading", "ready", "error", "unloaded"
load_time: Optional[float] = None
memory_usage_mb: float = 0.0
last_used: Optional[datetime] = None
error_message: Optional[str] = None
total_processed: int = 0
@dataclass
class SystemMetrics:
"""전체 시스템 성능 지표"""
total_memory_usage_mb: float
cpu_usage_percent: float
active_jobs: int
queued_jobs: int
completed_jobs_today: int
average_processing_time: float
uptime_seconds: float
class BackgroundAIService:
"""백그라운드 AI 모델 서비스 관리자"""
def __init__(self):
self.models_status: Dict[str, ModelStatus] = {}
self.translation_system: Optional[IntegratedTranslationSystem] = None
self.start_time = datetime.now()
self.job_queue = asyncio.Queue()
self.active_jobs = {}
self.completed_jobs = []
self.metrics_history = []
# 로깅 설정
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.INFO)
# 모델 상태 초기화
self._initialize_model_status()
# 백그라운드 워커 및 모니터링 시작
self.worker_task = None
self.monitoring_task = None
def _initialize_model_status(self):
"""모델 상태 초기화"""
model_names = ["NLLB 번역", "KoBART 요약"]
for name in model_names:
self.models_status[name] = ModelStatus(
name=name,
status="unloaded"
)
async def start_service(self):
"""백그라운드 서비스 시작"""
self.logger.info("🚀 백그라운드 AI 서비스 시작")
# 모델 로딩 시작
asyncio.create_task(self._load_models())
# 백그라운드 워커 시작
self.worker_task = asyncio.create_task(self._process_job_queue())
# 모니터링 시작
self.monitoring_task = asyncio.create_task(self._collect_metrics())
self.logger.info("✅ 백그라운드 서비스 준비 완료")
async def _load_models(self):
"""AI 모델들을 순차적으로 로딩"""
try:
# NLLB 모델 로딩 시작
self.models_status["NLLB 번역"].status = "loading"
load_start = time.time()
self.translation_system = IntegratedTranslationSystem()
# 실제 모델 로딩
success = await asyncio.to_thread(self.translation_system.load_models)
if success:
load_time = time.time() - load_start
# 각 모델 상태 업데이트
self.models_status["NLLB 번역"].status = "ready"
self.models_status["NLLB 번역"].load_time = load_time
self.models_status["KoBART 요약"].status = "ready"
self.models_status["KoBART 요약"].load_time = load_time
self.logger.info(f"✅ 모든 AI 모델 로딩 완료 ({load_time:.1f}초)")
else:
# 로딩 실패
for model_name in self.models_status.keys():
self.models_status[model_name].status = "error"
self.models_status[model_name].error_message = "모델 로딩 실패"
self.logger.error("❌ AI 모델 로딩 실패")
except Exception as e:
self.logger.error(f"❌ 모델 로딩 중 오류: {e}")
for model_name in self.models_status.keys():
self.models_status[model_name].status = "error"
self.models_status[model_name].error_message = str(e)
def _get_memory_usage(self) -> float:
"""현재 프로세스의 메모리 사용량 반환 (MB)"""
try:
process = psutil.Process()
return process.memory_info().rss / 1024 / 1024 # bytes to MB
except:
return 0.0
async def _collect_metrics(self):
"""주기적으로 시스템 메트릭 수집"""
while True:
try:
# 메모리 사용량 업데이트
total_memory = self._get_memory_usage()
# 각 모델별 메모리 사용량 추정 (실제로는 더 정교한 측정 필요)
if self.models_status["NLLB 번역"].status == "ready":
self.models_status["NLLB 번역"].memory_usage_mb = total_memory * 0.6
if self.models_status["KoBART 요약"].status == "ready":
self.models_status["KoBART 요약"].memory_usage_mb = total_memory * 0.4
# 전체 시스템 메트릭 수집
metrics = SystemMetrics(
total_memory_usage_mb=total_memory,
cpu_usage_percent=psutil.cpu_percent(),
active_jobs=len(self.active_jobs),
queued_jobs=self.job_queue.qsize(),
completed_jobs_today=len([
job for job in self.completed_jobs
if job.get('completed_at', datetime.min).date() == datetime.now().date()
]),
average_processing_time=self._calculate_average_processing_time(),
uptime_seconds=(datetime.now() - self.start_time).total_seconds()
)
# 메트릭 히스토리에 추가 (최근 100개만 유지)
self.metrics_history.append({
'timestamp': datetime.now(),
'metrics': metrics
})
if len(self.metrics_history) > 100:
self.metrics_history.pop(0)
await asyncio.sleep(5) # 5초마다 수집
except Exception as e:
self.logger.error(f"메트릭 수집 오류: {e}")
await asyncio.sleep(10)
def _calculate_average_processing_time(self) -> float:
"""최근 처리된 작업들의 평균 처리 시간 계산"""
recent_jobs = [
job for job in self.completed_jobs[-20:] # 최근 20개
if 'processing_time' in job
]
if not recent_jobs:
return 0.0
return sum(job['processing_time'] for job in recent_jobs) / len(recent_jobs)
async def _process_job_queue(self):
"""작업 큐 처리 워커"""
while True:
try:
# 큐에서 작업 가져오기
job = await self.job_queue.get()
job_id = job['job_id']
# 활성 작업에 추가
self.active_jobs[job_id] = {
'start_time': datetime.now(),
'job_data': job
}
# 실제 AI 처리
await self._process_single_job(job)
# 완료된 작업으로 이동
processing_time = (datetime.now() - self.active_jobs[job_id]['start_time']).total_seconds()
self.completed_jobs.append({
'job_id': job_id,
'completed_at': datetime.now(),
'processing_time': processing_time
})
# 활성 작업에서 제거
del self.active_jobs[job_id]
# 모델 사용 시간 업데이트
for model_status in self.models_status.values():
if model_status.status == "ready":
model_status.last_used = datetime.now()
model_status.total_processed += 1
except Exception as e:
self.logger.error(f"작업 처리 오류: {e}")
if job_id in self.active_jobs:
del self.active_jobs[job_id]
async def _process_single_job(self, job):
"""개별 작업 처리"""
# 기존 번역 시스템 로직 사용
if self.translation_system and self.models_status["NLLB 번역"].status == "ready":
result = await asyncio.to_thread(
self.translation_system.process_document,
job['file_path']
)
return result
else:
raise Exception("AI 모델이 준비되지 않음")
async def add_job(self, job_data: Dict):
"""새 작업을 큐에 추가"""
await self.job_queue.put(job_data)
def get_dashboard_data(self) -> Dict:
"""대시보드용 데이터 반환"""
current_metrics = self.metrics_history[-1]['metrics'] if self.metrics_history else None
return {
'models_status': {name: asdict(status) for name, status in self.models_status.items()},
'current_metrics': asdict(current_metrics) if current_metrics else None,
'recent_metrics': [
{
'timestamp': entry['timestamp'].isoformat(),
'metrics': asdict(entry['metrics'])
}
for entry in self.metrics_history[-20:] # 최근 20개
],
'active_jobs': len(self.active_jobs),
'completed_today': len([
job for job in self.completed_jobs
if job.get('completed_at', datetime.min).date() == datetime.now().date()
])
}
async def restart_models(self):
"""모델 재시작"""
self.logger.info("🔄 AI 모델 재시작 중...")
# 모든 모델 상태를 재로딩으로 설정
for model_status in self.models_status.values():
model_status.status = "loading"
model_status.error_message = None
# 기존 시스템 정리
if self.translation_system:
del self.translation_system
# 모델 재로딩
await self._load_models()
# 전역 서비스 인스턴스
ai_service = BackgroundAIService()