class _CacheEntry { final T value; final DateTime expiresAt; final int size; _CacheEntry( {required this.value, required this.expiresAt, required this.size}); bool get isExpired => DateTime.now().isAfter(expiresAt); } /// 간단한 메모리 기반 캐시 매니저 (TTL/최대 개수/용량 제한) class SimpleCacheManager { final int maxEntries; final int maxBytes; final Duration ttl; final Map> _store = >{}; int _currentBytes = 0; // 간단한 메트릭 int _hits = 0; int _misses = 0; int _puts = 0; int _evictions = 0; SimpleCacheManager({ this.maxEntries = 128, this.maxBytes = 1024 * 1024, // 1MB this.ttl = const Duration(minutes: 30), }); T? get(String key) { final entry = _store.remove(key); if (entry == null) return null; if (entry.isExpired) { _currentBytes -= entry.size; _misses++; return null; } // LRU 갱신: 재삽입으로 가장 최근으로 이동 _store[key] = entry; _hits++; return entry.value; } void set(String key, T value, {int size = 1, Duration? customTtl}) { final expiresAt = DateTime.now().add(customTtl ?? ttl); final existing = _store.remove(key); if (existing != null) { _currentBytes -= existing.size; } _store[key] = _CacheEntry(value: value, expiresAt: expiresAt, size: size); _currentBytes += size; _puts++; _evictIfNeeded(); } void invalidate(String key) { final removed = _store.remove(key); if (removed != null) { _currentBytes -= removed.size; } } void clear() { _store.clear(); _currentBytes = 0; } void _evictIfNeeded() { // 개수/용량 제한을 넘으면 오래된 것부터 제거 while (_store.length > maxEntries || _currentBytes > maxBytes) { if (_store.isEmpty) break; final firstKey = _store.keys.first; final removed = _store.remove(firstKey); if (removed != null) { _currentBytes -= removed.size; _evictions++; } } } Map dumpMetrics() { final totalGets = _hits + _misses; final hitRate = totalGets == 0 ? 0 : _hits / totalGets; return { 'entries': _store.length, 'bytes': _currentBytes, 'hits': _hits, 'misses': _misses, 'hitRate': hitRate, 'puts': _puts, 'evictions': _evictions, }; } }