mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-06-05 23:03:48 +00:00
287 lines
6.5 KiB
Go
287 lines
6.5 KiB
Go
package libpack_cache_memory
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"container/list"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// LRUMemoryCache is an efficient LRU-based memory cache implementation
|
|
type LRUMemoryCache struct {
|
|
entries map[string]*lruEntry
|
|
evictList *list.List
|
|
gzipWriterPool *sync.Pool
|
|
gzipReaderPool *sync.Pool
|
|
maxMemorySize int64
|
|
maxEntries int64
|
|
currentMemory int64
|
|
currentCount int64
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
type lruEntry struct {
|
|
expiresAt time.Time
|
|
element *list.Element
|
|
key string
|
|
value []byte
|
|
size int64
|
|
compressed bool
|
|
}
|
|
|
|
// NewLRUMemoryCache creates a new LRU memory cache
|
|
func NewLRUMemoryCache(maxMemorySize, maxEntries int64) *LRUMemoryCache {
|
|
return &LRUMemoryCache{
|
|
maxMemorySize: maxMemorySize,
|
|
maxEntries: maxEntries,
|
|
entries: make(map[string]*lruEntry),
|
|
evictList: list.New(),
|
|
gzipWriterPool: &sync.Pool{
|
|
New: func() interface{} {
|
|
return gzip.NewWriter(nil)
|
|
},
|
|
},
|
|
gzipReaderPool: &sync.Pool{
|
|
New: func() interface{} {
|
|
return &gzip.Reader{}
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Set adds or updates an entry in the cache
|
|
func (c *LRUMemoryCache) Set(key string, value []byte, ttl time.Duration) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
// Calculate expiry time
|
|
expiresAt := time.Now().Add(ttl)
|
|
|
|
// Check if we should compress
|
|
compressed := false
|
|
finalValue := value
|
|
if len(value) > 1024 { // Compress if larger than 1KB
|
|
if compressedData, err := c.compress(value); err == nil && len(compressedData) < len(value) {
|
|
compressed = true
|
|
finalValue = compressedData
|
|
}
|
|
}
|
|
|
|
entrySize := int64(len(key) + len(finalValue) + 64) // 64 bytes overhead estimate
|
|
|
|
// Check if key exists
|
|
if existing, exists := c.entries[key]; exists {
|
|
// Update existing entry
|
|
c.evictList.MoveToFront(existing.element)
|
|
atomic.AddInt64(&c.currentMemory, -existing.size)
|
|
atomic.AddInt64(&c.currentMemory, entrySize)
|
|
|
|
existing.value = finalValue
|
|
existing.compressed = compressed
|
|
existing.size = entrySize
|
|
existing.expiresAt = expiresAt
|
|
|
|
c.evictIfNeeded()
|
|
return
|
|
}
|
|
|
|
// Create new entry
|
|
entry := &lruEntry{
|
|
key: key,
|
|
value: finalValue,
|
|
compressed: compressed,
|
|
size: entrySize,
|
|
expiresAt: expiresAt,
|
|
}
|
|
|
|
element := c.evictList.PushFront(entry)
|
|
entry.element = element
|
|
c.entries[key] = entry
|
|
|
|
atomic.AddInt64(&c.currentMemory, entrySize)
|
|
atomic.AddInt64(&c.currentCount, 1)
|
|
|
|
c.evictIfNeeded()
|
|
}
|
|
|
|
// Get retrieves a value from the cache
|
|
func (c *LRUMemoryCache) Get(key string) ([]byte, bool) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
entry, exists := c.entries[key]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
|
|
// Check if expired
|
|
if time.Now().After(entry.expiresAt) {
|
|
c.removeEntry(entry)
|
|
return nil, false
|
|
}
|
|
|
|
// Move to front (most recently used)
|
|
c.evictList.MoveToFront(entry.element)
|
|
|
|
// Decompress if needed
|
|
if entry.compressed {
|
|
if decompressed, err := c.decompress(entry.value); err == nil {
|
|
return decompressed, true
|
|
}
|
|
// If decompression fails, remove the entry
|
|
c.removeEntry(entry)
|
|
return nil, false
|
|
}
|
|
|
|
return entry.value, true
|
|
}
|
|
|
|
// Delete removes an entry from the cache
|
|
func (c *LRUMemoryCache) Delete(key string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
if entry, exists := c.entries[key]; exists {
|
|
c.removeEntry(entry)
|
|
}
|
|
}
|
|
|
|
// Clear removes all entries
|
|
func (c *LRUMemoryCache) Clear() {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
c.entries = make(map[string]*lruEntry)
|
|
c.evictList = list.New()
|
|
atomic.StoreInt64(&c.currentMemory, 0)
|
|
atomic.StoreInt64(&c.currentCount, 0)
|
|
}
|
|
|
|
// evictIfNeeded removes entries when limits are exceeded
|
|
func (c *LRUMemoryCache) evictIfNeeded() {
|
|
// Evict based on entry count
|
|
for atomic.LoadInt64(&c.currentCount) > c.maxEntries && c.evictList.Len() > 0 {
|
|
c.evictOldest()
|
|
}
|
|
|
|
// Evict based on memory
|
|
for atomic.LoadInt64(&c.currentMemory) > c.maxMemorySize && c.evictList.Len() > 0 {
|
|
c.evictOldest()
|
|
}
|
|
}
|
|
|
|
// evictOldest removes the least recently used entry
|
|
func (c *LRUMemoryCache) evictOldest() {
|
|
element := c.evictList.Back()
|
|
if element == nil {
|
|
return
|
|
}
|
|
|
|
entry := element.Value.(*lruEntry)
|
|
c.removeEntry(entry)
|
|
}
|
|
|
|
// removeEntry removes an entry from all data structures
|
|
func (c *LRUMemoryCache) removeEntry(entry *lruEntry) {
|
|
c.evictList.Remove(entry.element)
|
|
delete(c.entries, entry.key)
|
|
atomic.AddInt64(&c.currentMemory, -entry.size)
|
|
atomic.AddInt64(&c.currentCount, -1)
|
|
}
|
|
|
|
// CleanExpiredEntries removes all expired entries
|
|
func (c *LRUMemoryCache) CleanExpiredEntries() {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
for element := c.evictList.Back(); element != nil; {
|
|
entry := element.Value.(*lruEntry)
|
|
|
|
if now.After(entry.expiresAt) {
|
|
next := element.Prev()
|
|
c.removeEntry(entry)
|
|
element = next
|
|
} else {
|
|
element = element.Prev()
|
|
}
|
|
}
|
|
}
|
|
|
|
// compress compresses data using gzip
|
|
func (c *LRUMemoryCache) compress(data []byte) ([]byte, error) {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
gz := c.gzipWriterPool.Get().(*gzip.Writer)
|
|
gz.Reset(buf)
|
|
defer c.gzipWriterPool.Put(gz)
|
|
|
|
if _, err := gz.Write(data); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := gz.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
compressed := make([]byte, buf.Len())
|
|
copy(compressed, buf.Bytes())
|
|
return compressed, nil
|
|
}
|
|
|
|
// decompress decompresses gzip data
|
|
func (c *LRUMemoryCache) decompress(data []byte) ([]byte, error) {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
buf.Write(data)
|
|
|
|
gr := c.gzipReaderPool.Get().(*gzip.Reader)
|
|
defer c.gzipReaderPool.Put(gr)
|
|
|
|
if err := gr.Reset(buf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := GetBuffer()
|
|
defer PutBuffer(result)
|
|
|
|
if _, err := result.ReadFrom(gr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
decompressed := make([]byte, result.Len())
|
|
copy(decompressed, result.Bytes())
|
|
return decompressed, nil
|
|
}
|
|
|
|
// GetStats returns cache statistics
|
|
func (c *LRUMemoryCache) GetStats() map[string]interface{} {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
return map[string]interface{}{
|
|
"entries": atomic.LoadInt64(&c.currentCount),
|
|
"memory_bytes": atomic.LoadInt64(&c.currentMemory),
|
|
"max_entries": c.maxEntries,
|
|
"max_memory": c.maxMemorySize,
|
|
"fill_percent": float64(atomic.LoadInt64(&c.currentMemory)) / float64(c.maxMemorySize) * 100,
|
|
}
|
|
}
|
|
|
|
// GetMemoryUsage returns current memory usage in bytes
|
|
func (c *LRUMemoryCache) GetMemoryUsage() int64 {
|
|
return atomic.LoadInt64(&c.currentMemory)
|
|
}
|
|
|
|
// GetMaxMemorySize returns the maximum memory size
|
|
func (c *LRUMemoryCache) GetMaxMemorySize() int64 {
|
|
return c.maxMemorySize
|
|
}
|
|
|
|
// CountQueries returns the number of entries in the cache
|
|
func (c *LRUMemoryCache) CountQueries() int64 {
|
|
return atomic.LoadInt64(&c.currentCount)
|
|
}
|