UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

513 lines (456 loc) 12.5 kB
import { CacheConfig, CacheLevel, CacheProvider, CacheEntry, CacheMetrics, CacheStats, InvalidationRule, CacheStatus, ContentType, CacheStrategy, } from "./types"; export interface CacheManagerOptions { levels: CacheLevel[]; defaultTTL: number; maxMemoryUsage: number; // MB compressionEnabled: boolean; metricsEnabled: boolean; debugMode: boolean; } export class CacheManager { private configs: Map<CacheLevel, CacheConfig> = new Map(); private providers: Map<CacheLevel, CacheProvider> = new Map(); private metrics: CacheMetrics; private invalidationRules: InvalidationRule[] = []; private options: CacheManagerOptions; constructor(options: CacheManagerOptions) { this.options = options; this.metrics = this.initializeMetrics(); this.setupDefaultConfigs(); } /** * 캐시 설정 초기화 */ private setupDefaultConfigs(): void { // 브라우저 캐시 설정 this.configs.set("browser", { level: "browser", provider: "browser", strategy: "ttl", ttl: 3600, // 1시간 maxSize: 50, // MB compression: true, encryption: false, tags: ["static-assets", "components"], headers: { "Cache-Control": "public, max-age=3600", ETag: "auto-generated", }, invalidationRules: [ { id: "version-change", strategy: "version-based", trigger: "deployment", scope: "global", priority: "high", conditions: ["version_update"], }, ], }); // CDN 캐시 설정 this.configs.set("cdn", { level: "cdn", provider: "cloudflare", strategy: "ttl", ttl: 86400, // 24시간 maxSize: 1000, // MB compression: true, encryption: false, tags: ["static-assets", "images", "css", "js"], headers: { "Cache-Control": "public, max-age=86400", Vary: "Accept-Encoding", }, invalidationRules: [ { id: "asset-update", strategy: "tag-based", trigger: "content_update", scope: "tagged", priority: "medium", conditions: ["asset_change", "content_publish"], }, ], }); // 서버 캐시 설정 this.configs.set("server", { level: "server", provider: "redis", strategy: "lru", ttl: 1800, // 30분 maxSize: 500, // MB compression: true, encryption: true, tags: ["api-responses", "component-data", "templates"], headers: {}, invalidationRules: [ { id: "content-update", strategy: "dependency-based", trigger: "data_change", scope: "related", priority: "high", conditions: ["content_edit", "component_update"], }, ], }); } /** * 메트릭 초기화 */ private initializeMetrics(): CacheMetrics { return { hitRate: 0, missRate: 0, totalRequests: 0, totalHits: 0, totalMisses: 0, averageResponseTime: 0, memoryUsage: 0, diskUsage: 0, networkTraffic: 0, errors: 0, evictions: 0, compressionRatio: 0, lastUpdated: new Date(), }; } /** * 캐시에서 데이터 조회 */ async get<T>( key: string, contentType: ContentType, options?: { levels?: CacheLevel[]; fallback?: () => Promise<T>; } ): Promise<{ data: T | null; status: CacheStatus; level?: CacheLevel }> { const startTime = Date.now(); const levels = options?.levels || this.options.levels; // 캐시 레벨별로 순차 조회 for (const level of levels) { try { const config = this.configs.get(level); if (!config) continue; const entry = await this.getFromLevel<T>(key, level); if (entry && this.isValidEntry(entry)) { this.updateMetrics("hit", Date.now() - startTime, level); return { data: entry.data, status: "hit", level, }; } } catch (error) { console.error(`캐시 조회 오류 (${level}):`, error); this.metrics.errors++; } } // 캐시 미스 처리 this.updateMetrics("miss", Date.now() - startTime); if (options?.fallback) { try { const data = await options.fallback(); // 폴백 데이터를 캐시에 저장 await this.set(key, data, contentType); return { data, status: "miss" }; } catch (error) { console.error("폴백 함수 실행 오류:", error); return { data: null, status: "error" }; } } return { data: null, status: "miss" }; } /** * 캐시에 데이터 저장 */ async set<T>( key: string, data: T, contentType: ContentType, options?: { ttl?: number; levels?: CacheLevel[]; tags?: string[]; } ): Promise<boolean> { const levels = options?.levels || this.options.levels; let success = true; for (const level of levels) { try { const config = this.configs.get(level); if (!config) continue; const entry: CacheEntry<T> = { key, data, contentType, level, ttl: options?.ttl || config.ttl, tags: options?.tags || config.tags, createdAt: new Date(), expiresAt: new Date(Date.now() + (options?.ttl || config.ttl) * 1000), hits: 0, lastAccessed: new Date(), size: this.calculateSize(data), compressed: config.compression, metadata: { version: "1.0", checksum: this.calculateChecksum(data), }, }; await this.setToLevel(entry, level); } catch (error) { console.error(`캐시 저장 오류 (${level}):`, error); success = false; this.metrics.errors++; } } return success; } /** * 캐시 무효화 */ async invalidate( pattern: string | string[], options?: { levels?: CacheLevel[]; strategy?: "key" | "tag" | "pattern"; } ): Promise<number> { const levels = options?.levels || this.options.levels; const strategy = options?.strategy || "key"; let invalidatedCount = 0; for (const level of levels) { try { const count = await this.invalidateFromLevel(pattern, level, strategy); invalidatedCount += count; } catch (error) { console.error(`캐시 무효화 오류 (${level}):`, error); this.metrics.errors++; } } return invalidatedCount; } /** * 캐시 통계 조회 */ getStats(): CacheStats { const levelStats = new Map<CacheLevel, any>(); for (const level of this.options.levels) { levelStats.set(level, { hitRate: this.calculateHitRate(level), memoryUsage: this.getMemoryUsage(level), entryCount: this.getEntryCount(level), averageSize: this.getAverageSize(level), }); } return { overall: this.metrics, byLevel: levelStats, topKeys: this.getTopKeys(), recentActivity: this.getRecentActivity(), errors: this.getRecentErrors(), }; } /** * 캐시 워밍 (사전 로딩) */ async warmUp( keys: Array<{ key: string; contentType: ContentType; loader: () => Promise<any>; }> ): Promise<number> { let warmedCount = 0; await Promise.allSettled( keys.map(async ({ key, contentType, loader }) => { try { const data = await loader(); await this.set(key, data, contentType); warmedCount++; } catch (error) { console.error(`캐시 워밍 오류 (${key}):`, error); } }) ); return warmedCount; } /** * 캐시 클리어 */ async clear(levels?: CacheLevel[]): Promise<boolean> { const targetLevels = levels || this.options.levels; let success = true; for (const level of targetLevels) { try { await this.clearLevel(level); } catch (error) { console.error(`캐시 클리어 오류 (${level}):`, error); success = false; } } // 메트릭 리셋 if (!levels || levels.length === this.options.levels.length) { this.metrics = this.initializeMetrics(); } return success; } /** * 헬스 체크 */ async healthCheck(): Promise<{ healthy: boolean; levels: Map<CacheLevel, boolean>; issues: string[]; }> { const levelHealth = new Map<CacheLevel, boolean>(); const issues: string[] = []; let overallHealthy = true; for (const level of this.options.levels) { try { const isHealthy = await this.checkLevelHealth(level); levelHealth.set(level, isHealthy); if (!isHealthy) { overallHealthy = false; issues.push(`${level} 캐시 레벨에 문제가 있습니다`); } } catch (error) { levelHealth.set(level, false); overallHealthy = false; issues.push(`${level} 헬스 체크 실패: ${error}`); } } // 메모리 사용량 체크 if (this.metrics.memoryUsage > this.options.maxMemoryUsage * 0.9) { overallHealthy = false; issues.push("메모리 사용량이 임계값에 근접했습니다"); } // 에러율 체크 const errorRate = this.metrics.errors / Math.max(this.metrics.totalRequests, 1); if (errorRate > 0.05) { // 5% 이상 overallHealthy = false; issues.push("에러율이 높습니다"); } return { healthy: overallHealthy, levels: levelHealth, issues, }; } // Private helper methods private async getFromLevel<T>( key: string, level: CacheLevel ): Promise<CacheEntry<T> | null> { // 실제 구현은 provider별로 다름 return null; } private async setToLevel<T>( entry: CacheEntry<T>, level: CacheLevel ): Promise<void> { // 실제 구현은 provider별로 다름 } private async invalidateFromLevel( pattern: string | string[], level: CacheLevel, strategy: string ): Promise<number> { // 실제 구현은 provider별로 다름 return 0; } private async clearLevel(level: CacheLevel): Promise<void> { // 실제 구현은 provider별로 다름 } private async checkLevelHealth(level: CacheLevel): Promise<boolean> { // 실제 구현은 provider별로 다름 return true; } private isValidEntry<T>(entry: CacheEntry<T>): boolean { return entry.expiresAt > new Date(); } private updateMetrics( type: "hit" | "miss", responseTime: number, level?: CacheLevel ): void { this.metrics.totalRequests++; if (type === "hit") { this.metrics.totalHits++; } else { this.metrics.totalMisses++; } this.metrics.hitRate = this.metrics.totalHits / this.metrics.totalRequests; this.metrics.missRate = this.metrics.totalMisses / this.metrics.totalRequests; this.metrics.averageResponseTime = (this.metrics.averageResponseTime + responseTime) / 2; this.metrics.lastUpdated = new Date(); } private calculateSize(data: any): number { return JSON.stringify(data).length; } private calculateChecksum(data: any): string { // 간단한 체크섬 계산 const str = JSON.stringify(data); let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; // 32bit 정수로 변환 } return hash.toString(16); } private calculateHitRate(level: CacheLevel): number { // 레벨별 히트율 계산 return 0; } private getMemoryUsage(level: CacheLevel): number { // 레벨별 메모리 사용량 return 0; } private getEntryCount(level: CacheLevel): number { // 레벨별 엔트리 수 return 0; } private getAverageSize(level: CacheLevel): number { // 레벨별 평균 크기 return 0; } private getTopKeys(): Array<{ key: string; hits: number }> { // 인기 키 목록 return []; } private getRecentActivity(): Array<{ timestamp: Date; action: string; key: string; }> { // 최근 활동 로그 return []; } private getRecentErrors(): Array<{ timestamp: Date; error: string; level: CacheLevel; }> { // 최근 에러 로그 return []; } } export default CacheManager;