Skip to main content
Glama
memory-manager.ts7.57 kB
/** * Memory Management Utility for Naver Search MCP Server * 메모리 누수 방지 및 리소스 관리를 위한 유틸리티 */ import { NaverSearchClient } from "../clients/naver-search.client.js"; import { clearCategoriesCache } from "../handlers/category.handlers.js"; export interface MemoryUsage { rss: number; // Resident Set Size heapTotal: number; // Total heap size heapUsed: number; // Used heap size external: number; // External memory usage arrayBuffers: number; // ArrayBuffer memory usage } export interface MemoryStats { usage: MemoryUsage; timestamp: number; formattedUsage: { rss: string; heapTotal: string; heapUsed: string; external: string; arrayBuffers: string; }; } /** * 메모리 사용량 조회 */ export function getMemoryUsage(): MemoryStats { const usage = (globalThis as any).process.memoryUsage(); const timestamp = Date.now(); const formatBytes = (bytes: number): string => { const mb = bytes / 1024 / 1024; return `${mb.toFixed(2)} MB`; }; return { usage, timestamp, formattedUsage: { rss: formatBytes(usage.rss), heapTotal: formatBytes(usage.heapTotal), heapUsed: formatBytes(usage.heapUsed), external: formatBytes(usage.external), arrayBuffers: formatBytes(usage.arrayBuffers), }, }; } /** * 메모리 누수 감지 임계값 (MB) */ const MEMORY_THRESHOLDS = { WARNING: 200, // 200MB CRITICAL: 500, // 500MB EMERGENCY: 1024, // 1GB }; /** * 메모리 상태 확인 */ export function checkMemoryHealth(): { status: 'healthy' | 'warning' | 'critical' | 'emergency'; recommendation: string; stats: MemoryStats; } { const stats = getMemoryUsage(); const heapUsedMB = stats.usage.heapUsed / 1024 / 1024; let status: 'healthy' | 'warning' | 'critical' | 'emergency' = 'healthy'; let recommendation = '메모리 사용량이 정상 범위입니다.'; if (heapUsedMB > MEMORY_THRESHOLDS.EMERGENCY) { status = 'emergency'; recommendation = '즉시 메모리 정리가 필요합니다. 서버 재시작을 고려하세요.'; } else if (heapUsedMB > MEMORY_THRESHOLDS.CRITICAL) { status = 'critical'; recommendation = '메모리 사용량이 위험 수준입니다. 리소스 정리를 수행하세요.'; } else if (heapUsedMB > MEMORY_THRESHOLDS.WARNING) { status = 'warning'; recommendation = '메모리 사용량이 높습니다. 모니터링을 계속하세요.'; } return { status, recommendation, stats }; } /** * 포괄적인 메모리 정리 수행 */ export async function performMemoryCleanup(): Promise<{ before: MemoryStats; after: MemoryStats; cleanupActions: string[]; }> { const before = getMemoryUsage(); const cleanupActions: string[] = []; try { // 1. 클라이언트 인스턴스 정리 NaverSearchClient.destroyInstance(); cleanupActions.push('NaverSearchClient 인스턴스 정리 완료'); // 2. 카테고리 캐시 정리 clearCategoriesCache(); cleanupActions.push('카테고리 캐시 정리 완료'); // 3. 가비지 컬렉션 강제 실행 (가능한 경우) if ((globalThis as any).gc) { (globalThis as any).gc(); cleanupActions.push('가비지 컬렉션 강제 실행 완료'); } else { cleanupActions.push('가비지 컬렉션 사용 불가 (--expose-gc 플래그 필요)'); } // 4. 잠시 대기하여 정리 작업 완료 await new Promise(resolve => setTimeout(resolve, 100)); } catch (error) { cleanupActions.push(`정리 중 오류 발생: ${error}`); } const after = getMemoryUsage(); return { before, after, cleanupActions }; } /** * 메모리 사용량 모니터링 클래스 */ export class MemoryMonitor { private intervalId: any = null; private memoryHistory: MemoryStats[] = []; private maxHistorySize = 10; /** * 메모리 모니터링 시작 */ start(intervalMs: number = 60000): void { // 기본 1분 간격 if (this.intervalId) { this.stop(); } this.intervalId = setInterval(() => { const stats = getMemoryUsage(); this.memoryHistory.push(stats); // 히스토리 크기 제한 if (this.memoryHistory.length > this.maxHistorySize) { this.memoryHistory.shift(); } // 메모리 상태 확인 및 로깅 const health = checkMemoryHealth(); if (health.status !== 'healthy') { console.error(`[Memory Monitor] Status: ${health.status}, Usage: ${health.stats.formattedUsage.heapUsed}, Recommendation: ${health.recommendation}`); // 위험 수준에서 자동 정리 수행 if (health.status === 'critical' || health.status === 'emergency') { this.performAutoCleanup(); } } }, intervalMs); console.error(`[Memory Monitor] Started monitoring with ${intervalMs}ms interval`); } /** * 메모리 모니터링 중지 */ stop(): void { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; console.error('[Memory Monitor] Monitoring stopped'); } } /** * 메모리 히스토리 조회 */ getHistory(): MemoryStats[] { return [...this.memoryHistory]; } /** * 메모리 사용량 트렌드 분석 */ analyzeTrend(): { trend: 'increasing' | 'decreasing' | 'stable'; averageUsage: number; maxUsage: number; minUsage: number; } { if (this.memoryHistory.length < 3) { return { trend: 'stable', averageUsage: 0, maxUsage: 0, minUsage: 0 }; } const usages = this.memoryHistory.map(stat => stat.usage.heapUsed); const recent = usages.slice(-3); let trend: 'increasing' | 'decreasing' | 'stable' = 'stable'; if (recent[2] > recent[1] && recent[1] > recent[0]) { trend = 'increasing'; } else if (recent[2] < recent[1] && recent[1] < recent[0]) { trend = 'decreasing'; } return { trend, averageUsage: usages.reduce((a, b) => a + b, 0) / usages.length, maxUsage: Math.max(...usages), minUsage: Math.min(...usages) }; } /** * 자동 메모리 정리 수행 */ private async performAutoCleanup(): Promise<void> { try { console.error('[Memory Monitor] Performing automatic memory cleanup...'); const result = await performMemoryCleanup(); const beforeMB = result.before.usage.heapUsed / 1024 / 1024; const afterMB = result.after.usage.heapUsed / 1024 / 1024; const savedMB = beforeMB - afterMB; console.error(`[Memory Monitor] Cleanup completed. Saved ${savedMB.toFixed(2)} MB`); console.error(`[Memory Monitor] Actions: ${result.cleanupActions.join(', ')}`); } catch (error) { console.error(`[Memory Monitor] Auto cleanup failed: ${error}`); } } } // 전역 메모리 모니터 인스턴스 let globalMemoryMonitor: MemoryMonitor | null = null; /** * 글로벌 메모리 모니터 시작 */ export function startGlobalMemoryMonitoring(intervalMs: number = 300000): void { // 기본 5분 간격 if (!globalMemoryMonitor) { globalMemoryMonitor = new MemoryMonitor(); } globalMemoryMonitor.start(intervalMs); } /** * 글로벌 메모리 모니터 중지 */ export function stopGlobalMemoryMonitoring(): void { if (globalMemoryMonitor) { globalMemoryMonitor.stop(); globalMemoryMonitor = null; } } /** * 현재 메모리 모니터 인스턴스 조회 */ export function getGlobalMemoryMonitor(): MemoryMonitor | null { return globalMemoryMonitor; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/isnow890/naver-search-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server