mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-28 23:52:15 +00:00
- 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
216 lines
4.4 KiB
Go
216 lines
4.4 KiB
Go
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"`
|
|
}
|