98 lines
2.4 KiB
Dart
98 lines
2.4 KiB
Dart
class _CacheEntry<T> {
|
|
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<T> {
|
|
final int maxEntries;
|
|
final int maxBytes;
|
|
final Duration ttl;
|
|
|
|
final Map<String, _CacheEntry<T>> _store = <String, _CacheEntry<T>>{};
|
|
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<String, num> 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,
|
|
};
|
|
}
|
|
}
|