ERP-node/frontend/lib/caching/codeCache.ts

199 lines
4.8 KiB
TypeScript

/**
* 공통 코드 캐시 시스템
* 자주 사용되는 공통 코드들을 메모리에 캐싱하여 성능을 향상시킵니다.
*/
import { commonCodeApi } from "@/lib/api/commonCode";
interface CacheEntry {
data: any;
timestamp: number;
expiry: number;
}
class CodeCache {
private cache = new Map<string, CacheEntry>();
private defaultTTL = 5 * 60 * 1000; // 5분
/**
* 캐시에 데이터 저장
*/
set(key: string, data: any, ttl?: number): void {
const expiry = ttl || this.defaultTTL;
const entry: CacheEntry = {
data,
timestamp: Date.now(),
expiry,
};
this.cache.set(key, entry);
}
/**
* 캐시에서 데이터 조회
*/
get(key: string): any | null {
const entry = this.cache.get(key);
if (!entry) {
return null;
}
// TTL 체크
if (Date.now() - entry.timestamp > entry.expiry) {
this.cache.delete(key);
return null;
}
return entry.data;
}
/**
* 캐시에서 데이터 삭제
*/
delete(key: string): boolean {
return this.cache.delete(key);
}
/**
* 모든 캐시 삭제
*/
clear(): void {
this.cache.clear();
}
/**
* 만료된 캐시 정리
*/
cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > entry.expiry) {
this.cache.delete(key);
}
}
}
/**
* 캐시 상태 조회
*/
getStats(): { size: number; keys: string[] } {
return {
size: this.cache.size,
keys: Array.from(this.cache.keys()),
};
}
/**
* 공통 코드 캐시 키 생성
*/
createCodeKey(category: string, companyCode?: string): string {
return `code:${category}:${companyCode || "*"}`;
}
/**
* 여러 코드 카테고리를 배치로 미리 로딩
*/
async preloadCodes(categories: string[]): Promise<void> {
console.log(`🔄 코드 배치 로딩 시작: ${categories.join(", ")}`);
const promises = categories.map(async (category) => {
try {
const response = await commonCodeApi.codes.getList(category, { isActive: true });
if (response.success && response.data) {
const cacheKey = this.createCodeKey(category);
this.set(cacheKey, response.data, this.defaultTTL);
console.log(`✅ 코드 로딩 완료: ${category} (${response.data.length}개)`);
}
} catch (error) {
console.error(`❌ 코드 로딩 실패: ${category}`, error);
}
});
await Promise.all(promises);
console.log(`✅ 코드 배치 로딩 완료: ${categories.length}개 카테고리`);
}
/**
* 코드를 동기적으로 조회 (캐시에서만)
*/
getCodeSync(category: string, companyCode?: string): any[] | null {
const cacheKey = this.createCodeKey(category, companyCode);
return this.get(cacheKey);
}
/**
* 코드를 비동기적으로 조회 (캐시 미스 시 API 호출)
*/
async getCodeAsync(category: string, companyCode?: string): Promise<any[]> {
const cached = this.getCodeSync(category, companyCode);
if (cached) {
return cached;
}
try {
const response = await commonCodeApi.codes.getList(category, { isActive: true });
if (response.success && response.data) {
const cacheKey = this.createCodeKey(category, companyCode);
this.set(cacheKey, response.data, this.defaultTTL);
return response.data;
}
} catch (error) {
console.error(`❌ 코드 조회 실패: ${category}`, error);
}
return [];
}
/**
* 캐시 정보 조회 (성능 메트릭용)
*/
getCacheInfo(): {
size: number;
keys: string[];
totalMemoryUsage: number;
hitRate?: number;
} {
const stats = this.getStats();
// 메모리 사용량 추정 (대략적)
let totalMemoryUsage = 0;
for (const [key, entry] of this.cache.entries()) {
// 키 크기 + 데이터 크기 추정
totalMemoryUsage += key.length * 2; // 문자열은 UTF-16이므로 2바이트
if (Array.isArray(entry.data)) {
totalMemoryUsage += entry.data.length * 100; // 각 항목당 대략 100바이트로 추정
} else {
totalMemoryUsage += JSON.stringify(entry.data).length * 2;
}
}
return {
...stats,
totalMemoryUsage,
};
}
/**
* 특정 카테고리의 캐시 무효화
*/
invalidate(category: string, companyCode?: string): boolean {
const cacheKey = this.createCodeKey(category, companyCode);
return this.delete(cacheKey);
}
}
// 싱글톤 인스턴스 생성
const codeCache = new CodeCache();
// 주기적으로 만료된 캐시 정리 (10분마다)
if (typeof window !== "undefined") {
setInterval(
() => {
codeCache.cleanup();
},
10 * 60 * 1000,
);
}
export default codeCache;
export { CodeCache, codeCache };