mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-20 11:54:25 +00:00
feat: Add in-memory cache with TTL, LRU eviction, JSON serialization, and statistics tracking
- Add Cache struct with entries map, mutex, maxSize, defaultTTL, strategy, and stats fields - Add CacheStrategy type with LRU, TTL, write-through, write-back, and read-through constants - Add CacheEntry struct with Key, Value, ExpiresAt, CreatedAt, and HitCount fields - Add NewCache constructor with CacheConfig parameter and automatic cleanup goroutine - Add Get, Set, Delete, and Clear methods with hit
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CacheStrategy defines caching strategies
|
||||
type CacheStrategy string
|
||||
|
||||
const (
|
||||
CacheStrategyLRU CacheStrategy = "lru"
|
||||
CacheStrategyTTL CacheStrategy = "ttl"
|
||||
CacheStrategyWriteThru CacheStrategy = "write_through"
|
||||
CacheStrategyWriteBack CacheStrategy = "write_back"
|
||||
CacheStrategyReadThru CacheStrategy = "read_through"
|
||||
)
|
||||
|
||||
// CacheEntry represents a cached item
|
||||
type CacheEntry struct {
|
||||
Key string
|
||||
Value []byte
|
||||
ExpiresAt time.Time
|
||||
CreatedAt time.Time
|
||||
HitCount int64
|
||||
}
|
||||
|
||||
// Cache provides in-memory caching with multiple strategies
|
||||
type Cache struct {
|
||||
entries map[string]*CacheEntry
|
||||
mu sync.RWMutex
|
||||
maxSize int
|
||||
defaultTTL time.Duration
|
||||
strategy CacheStrategy
|
||||
stats CacheStats
|
||||
}
|
||||
|
||||
// CacheConfig configures the cache
|
||||
type CacheConfig struct {
|
||||
MaxSize int
|
||||
DefaultTTL time.Duration
|
||||
Strategy CacheStrategy
|
||||
}
|
||||
|
||||
// DefaultCacheConfig returns default cache configuration
|
||||
func DefaultCacheConfig() CacheConfig {
|
||||
return CacheConfig{
|
||||
MaxSize: 10000,
|
||||
DefaultTTL: 5 * time.Minute,
|
||||
Strategy: CacheStrategyTTL,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCache creates a new cache instance
|
||||
func NewCache(config CacheConfig) *Cache {
|
||||
c := &Cache{
|
||||
entries: make(map[string]*CacheEntry),
|
||||
maxSize: config.MaxSize,
|
||||
defaultTTL: config.DefaultTTL,
|
||||
strategy: config.Strategy,
|
||||
}
|
||||
go c.cleanupLoop()
|
||||
return c
|
||||
}
|
||||
|
||||
// Get retrieves a value from cache
|
||||
func (c *Cache) Get(ctx context.Context, key string) ([]byte, bool) {
|
||||
c.mu.RLock()
|
||||
entry, exists := c.entries[key]
|
||||
c.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
c.stats.Misses++
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if time.Now().After(entry.ExpiresAt) {
|
||||
c.Delete(ctx, key)
|
||||
c.stats.Misses++
|
||||
return nil, false
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
entry.HitCount++
|
||||
c.stats.Hits++
|
||||
c.mu.Unlock()
|
||||
|
||||
return entry.Value, true
|
||||
}
|
||||
|
||||
// Set stores a value in cache
|
||||
func (c *Cache) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
|
||||
if ttl == 0 {
|
||||
ttl = c.defaultTTL
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if len(c.entries) >= c.maxSize {
|
||||
c.evict()
|
||||
}
|
||||
|
||||
c.entries[key] = &CacheEntry{
|
||||
Key: key,
|
||||
Value: value,
|
||||
ExpiresAt: time.Now().Add(ttl),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
c.stats.Sets++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetJSON stores a JSON-serializable value
|
||||
func (c *Cache) SetJSON(ctx context.Context, key string, value any, ttl time.Duration) error {
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal value: %w", err)
|
||||
}
|
||||
return c.Set(ctx, key, data, ttl)
|
||||
}
|
||||
|
||||
// GetJSON retrieves and unmarshals a JSON value
|
||||
func (c *Cache) GetJSON(ctx context.Context, key string, dest any) (bool, error) {
|
||||
data, exists := c.Get(ctx, key)
|
||||
if !exists {
|
||||
return false, nil
|
||||
}
|
||||
if err := json.Unmarshal(data, dest); err != nil {
|
||||
return false, fmt.Errorf("failed to unmarshal value: %w", err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Delete removes a value from cache
|
||||
func (c *Cache) Delete(_ context.Context, key string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
delete(c.entries, key)
|
||||
c.stats.Deletes++
|
||||
}
|
||||
|
||||
// Clear removes all entries
|
||||
func (c *Cache) Clear(_ context.Context) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.entries = make(map[string]*CacheEntry)
|
||||
}
|
||||
|
||||
// evict removes the least recently used or oldest entry
|
||||
func (c *Cache) evict() {
|
||||
var oldest *CacheEntry
|
||||
var oldestKey string
|
||||
|
||||
for key, entry := range c.entries {
|
||||
if oldest == nil || entry.CreatedAt.Before(oldest.CreatedAt) {
|
||||
oldest = entry
|
||||
oldestKey = key
|
||||
}
|
||||
}
|
||||
|
||||
if oldestKey != "" {
|
||||
delete(c.entries, oldestKey)
|
||||
c.stats.Evictions++
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) cleanupLoop() {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
c.cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) cleanup() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for key, entry := range c.entries {
|
||||
if now.After(entry.ExpiresAt) {
|
||||
delete(c.entries, key)
|
||||
c.stats.Evictions++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats returns cache statistics
|
||||
func (c *Cache) Stats() CacheStats {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
stats := c.stats
|
||||
stats.Size = len(c.entries)
|
||||
if stats.Hits+stats.Misses > 0 {
|
||||
stats.HitRate = float64(stats.Hits) / float64(stats.Hits+stats.Misses) * 100
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
// CacheStats contains cache statistics
|
||||
type CacheStats struct {
|
||||
Size int `json:"size"`
|
||||
Hits int64 `json:"hits"`
|
||||
Misses int64 `json:"misses"`
|
||||
Sets int64 `json:"sets"`
|
||||
Deletes int64 `json:"deletes"`
|
||||
Evictions int64 `json:"evictions"`
|
||||
HitRate float64 `json:"hit_rate_pct"`
|
||||
}
|
||||
Reference in New Issue
Block a user