new flat KV structure
This commit is contained in:
Vendored
+242
@@ -0,0 +1,242 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LRUIndex maintains an in-memory LRU index of cache entries
|
||||
type LRUIndex struct {
|
||||
mu sync.RWMutex
|
||||
entries map[string]*list.Element // path -> list element
|
||||
lruList *list.List // LRU order (front = most recent)
|
||||
maxSize int64 // Maximum total size in bytes
|
||||
currSize int64 // Current total size in bytes
|
||||
|
||||
// Stats
|
||||
hitCount int64
|
||||
missCount int64
|
||||
evictionCount int64
|
||||
}
|
||||
|
||||
// lruEntry wraps CacheEntry for the linked list
|
||||
type lruEntry struct {
|
||||
entry *CacheEntry
|
||||
}
|
||||
|
||||
// NewLRUIndex creates a new LRU index
|
||||
func NewLRUIndex(maxSizeGB int) *LRUIndex {
|
||||
return &LRUIndex{
|
||||
entries: make(map[string]*list.Element),
|
||||
lruList: list.New(),
|
||||
maxSize: int64(maxSizeGB) * 1024 * 1024 * 1024, // GB to bytes
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves a cache entry and updates its LRU position
|
||||
// Returns nil if not found
|
||||
func (idx *LRUIndex) Get(path string) *CacheEntry {
|
||||
idx.mu.Lock()
|
||||
defer idx.mu.Unlock()
|
||||
|
||||
elem, exists := idx.entries[path]
|
||||
if !exists {
|
||||
idx.missCount++
|
||||
return nil
|
||||
}
|
||||
|
||||
// Move to front (most recently used)
|
||||
idx.lruList.MoveToFront(elem)
|
||||
|
||||
// Update last accessed time
|
||||
entry := elem.Value.(*lruEntry).entry
|
||||
entry.LastAccessed = time.Now()
|
||||
|
||||
idx.hitCount++
|
||||
return entry
|
||||
}
|
||||
|
||||
// Put adds or updates a cache entry
|
||||
func (idx *LRUIndex) Put(entry *CacheEntry) {
|
||||
idx.mu.Lock()
|
||||
defer idx.mu.Unlock()
|
||||
|
||||
if elem, exists := idx.entries[entry.OriginalPath]; exists {
|
||||
// Update existing entry
|
||||
oldEntry := elem.Value.(*lruEntry).entry
|
||||
idx.currSize -= oldEntry.Size
|
||||
idx.currSize += entry.Size
|
||||
|
||||
elem.Value.(*lruEntry).entry = entry
|
||||
idx.lruList.MoveToFront(elem)
|
||||
} else {
|
||||
// Add new entry
|
||||
le := &lruEntry{entry: entry}
|
||||
elem := idx.lruList.PushFront(le)
|
||||
idx.entries[entry.OriginalPath] = elem
|
||||
idx.currSize += entry.Size
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes a cache entry by path
|
||||
// Returns the removed entry or nil if not found
|
||||
func (idx *LRUIndex) Remove(path string) *CacheEntry {
|
||||
idx.mu.Lock()
|
||||
defer idx.mu.Unlock()
|
||||
|
||||
elem, exists := idx.entries[path]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := elem.Value.(*lruEntry).entry
|
||||
idx.lruList.Remove(elem)
|
||||
delete(idx.entries, path)
|
||||
idx.currSize -= entry.Size
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// GetLRUEntry returns the least recently used entry without removing it
|
||||
// Returns nil if the index is empty
|
||||
func (idx *LRUIndex) GetLRUEntry() *CacheEntry {
|
||||
idx.mu.RLock()
|
||||
defer idx.mu.RUnlock()
|
||||
|
||||
back := idx.lruList.Back()
|
||||
if back == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return back.Value.(*lruEntry).entry
|
||||
}
|
||||
|
||||
// PopLRUEntry removes and returns the least recently used entry
|
||||
// Returns nil if the index is empty
|
||||
func (idx *LRUIndex) PopLRUEntry() *CacheEntry {
|
||||
idx.mu.Lock()
|
||||
defer idx.mu.Unlock()
|
||||
|
||||
back := idx.lruList.Back()
|
||||
if back == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
entry := back.Value.(*lruEntry).entry
|
||||
idx.lruList.Remove(back)
|
||||
delete(idx.entries, entry.OriginalPath)
|
||||
idx.currSize -= entry.Size
|
||||
idx.evictionCount++
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
// GetExpiredEntries returns all entries that have exceeded the TTL
|
||||
func (idx *LRUIndex) GetExpiredEntries(ttl time.Duration) []*CacheEntry {
|
||||
idx.mu.RLock()
|
||||
defer idx.mu.RUnlock()
|
||||
|
||||
var expired []*CacheEntry
|
||||
cutoff := time.Now().Add(-ttl)
|
||||
|
||||
for elem := idx.lruList.Back(); elem != nil; elem = elem.Prev() {
|
||||
entry := elem.Value.(*lruEntry).entry
|
||||
if entry.CachedAt.Before(cutoff) {
|
||||
expired = append(expired, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return expired
|
||||
}
|
||||
|
||||
// Size returns the current total size of cached files
|
||||
func (idx *LRUIndex) Size() int64 {
|
||||
idx.mu.RLock()
|
||||
defer idx.mu.RUnlock()
|
||||
return idx.currSize
|
||||
}
|
||||
|
||||
// Count returns the number of entries in the index
|
||||
func (idx *LRUIndex) Count() int {
|
||||
idx.mu.RLock()
|
||||
defer idx.mu.RUnlock()
|
||||
return len(idx.entries)
|
||||
}
|
||||
|
||||
// MaxSize returns the maximum cache size
|
||||
func (idx *LRUIndex) MaxSize() int64 {
|
||||
return idx.maxSize
|
||||
}
|
||||
|
||||
// NeedsEviction returns true if the cache size exceeds the maximum
|
||||
func (idx *LRUIndex) NeedsEviction() bool {
|
||||
idx.mu.RLock()
|
||||
defer idx.mu.RUnlock()
|
||||
return idx.currSize > idx.maxSize
|
||||
}
|
||||
|
||||
// Stats returns cache statistics
|
||||
func (idx *LRUIndex) Stats() *CacheStats {
|
||||
idx.mu.RLock()
|
||||
defer idx.mu.RUnlock()
|
||||
|
||||
total := idx.hitCount + idx.missCount
|
||||
var hitRate float64
|
||||
if total > 0 {
|
||||
hitRate = float64(idx.hitCount) / float64(total) * 100
|
||||
}
|
||||
|
||||
return &CacheStats{
|
||||
Enabled: true,
|
||||
TotalSize: idx.currSize,
|
||||
MaxSize: idx.maxSize,
|
||||
EntryCount: len(idx.entries),
|
||||
HitCount: idx.hitCount,
|
||||
MissCount: idx.missCount,
|
||||
EvictionCount: idx.evictionCount,
|
||||
HitRate: hitRate,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllEntries returns all cache entries (for persistence)
|
||||
func (idx *LRUIndex) GetAllEntries() map[string]*CacheEntry {
|
||||
idx.mu.RLock()
|
||||
defer idx.mu.RUnlock()
|
||||
|
||||
result := make(map[string]*CacheEntry, len(idx.entries))
|
||||
for path, elem := range idx.entries {
|
||||
result[path] = elem.Value.(*lruEntry).entry
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// LoadFromMap loads entries from a map (for loading persisted index)
|
||||
func (idx *LRUIndex) LoadFromMap(entries map[string]*CacheEntry) {
|
||||
idx.mu.Lock()
|
||||
defer idx.mu.Unlock()
|
||||
|
||||
// Clear existing entries
|
||||
idx.lruList.Init()
|
||||
idx.entries = make(map[string]*list.Element)
|
||||
idx.currSize = 0
|
||||
|
||||
// Load entries sorted by LastAccessed (oldest first for proper LRU order)
|
||||
// For simplicity, we just add them in arbitrary order - the access pattern will sort them
|
||||
for _, entry := range entries {
|
||||
le := &lruEntry{entry: entry}
|
||||
elem := idx.lruList.PushBack(le) // Add to back (oldest)
|
||||
idx.entries[entry.OriginalPath] = elem
|
||||
idx.currSize += entry.Size
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all entries from the index
|
||||
func (idx *LRUIndex) Clear() {
|
||||
idx.mu.Lock()
|
||||
defer idx.mu.Unlock()
|
||||
|
||||
idx.lruList.Init()
|
||||
idx.entries = make(map[string]*list.Element)
|
||||
idx.currSize = 0
|
||||
}
|
||||
Reference in New Issue
Block a user