feat(cache): add SimpleCacheManager and cache formatted rates/amounts in exchange and currency services
This commit is contained in:
97
lib/services/cache_manager.dart
Normal file
97
lib/services/cache_manager.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user