Files
telecom-platform/apps/api-server/internal/cache/cache.go
T
nutcas3 4a74483044 feat: Add cache package with in-memory implementation, LRU eviction, and multi-cache management
- Add CacheItem struct with key/value/expiration/createdAt/accessCount/lastAccess fields and IsExpired method
- Add Cache interface with Get/Set/Delete/Clear/Keys/Size/Stats methods
- Add CacheStats struct with hits/misses/hitRate/size/evictions/totalItems metrics
- Add MemoryCache with sync.RWMutex, maxSize limit, automatic cleanup goroutine, LRU eviction, and access tracking
- Add RedisCache placeholder
2026-04-20 18:09:26 +03:00

413 lines
8.9 KiB
Go

package cache
import (
"encoding/json"
"fmt"
"sync"
"time"
)
// CacheItem represents an item in the cache
type CacheItem struct {
Key string `json:"key"`
Value any `json:"value"`
Expiration time.Time `json:"expiration"`
CreatedAt time.Time `json:"created_at"`
AccessCount int64 `json:"access_count"`
LastAccess time.Time `json:"last_access"`
}
// IsExpired checks if the cache item has expired
func (ci *CacheItem) IsExpired() bool {
return time.Now().After(ci.Expiration)
}
// Cache interface defines the cache operations
type Cache interface {
Get(key string) (any, bool)
Set(key string, value any, ttl time.Duration)
Delete(key string)
Clear()
Keys() []string
Size() int
Stats() CacheStats
}
// CacheStats provides cache statistics
type CacheStats struct {
Hits int64 `json:"hits"`
Misses int64 `json:"misses"`
HitRate float64 `json:"hit_rate"`
Size int `json:"size"`
Evictions int64 `json:"evictions"`
TotalItems int64 `json:"total_items"`
}
// MemoryCache is an in-memory cache implementation
type MemoryCache struct {
items map[string]*CacheItem
mu sync.RWMutex
stats CacheStats
maxSize int
cleanupTick *time.Ticker
stopCleanup chan bool
}
// NewMemoryCache creates a new memory cache
func NewMemoryCache(maxSize int, cleanupInterval time.Duration) *MemoryCache {
cache := &MemoryCache{
items: make(map[string]*CacheItem),
maxSize: maxSize,
cleanupTick: time.NewTicker(cleanupInterval),
stopCleanup: make(chan bool),
}
// Start cleanup goroutine
go cache.cleanup()
return cache
}
// cleanup removes expired items from cache
func (mc *MemoryCache) cleanup() {
for {
select {
case <-mc.cleanupTick.C:
mc.removeExpired()
case <-mc.stopCleanup:
return
}
}
}
// removeExpired removes expired items from cache
func (mc *MemoryCache) removeExpired() {
mc.mu.Lock()
defer mc.mu.Unlock()
now := time.Now()
for key, item := range mc.items {
if now.After(item.Expiration) {
delete(mc.items, key)
mc.stats.Evictions++
}
}
}
// Get retrieves an item from cache
func (mc *MemoryCache) Get(key string) (any, bool) {
mc.mu.RLock()
defer mc.mu.RUnlock()
item, exists := mc.items[key]
if !exists {
mc.stats.Misses++
return nil, false
}
if item.IsExpired() {
delete(mc.items, key)
mc.stats.Misses++
mc.stats.Evictions++
return nil, false
}
// Update access statistics
item.AccessCount++
item.LastAccess = time.Now()
mc.stats.Hits++
return item.Value, true
}
// Set stores an item in cache
func (mc *MemoryCache) Set(key string, value any, ttl time.Duration) {
mc.mu.Lock()
defer mc.mu.Unlock()
// Check if we need to evict items to make space
if len(mc.items) >= mc.maxSize {
mc.evictLRU()
}
expiration := time.Now().Add(ttl)
item := &CacheItem{
Key: key,
Value: value,
Expiration: expiration,
CreatedAt: time.Now(),
AccessCount: 1,
LastAccess: time.Now(),
}
mc.items[key] = item
mc.stats.TotalItems++
}
// evictLRU removes the least recently used item
func (mc *MemoryCache) evictLRU() {
var lruKey string
var lruTime time.Time
for key, item := range mc.items {
if lruKey == "" || item.LastAccess.Before(lruTime) {
lruKey = key
lruTime = item.LastAccess
}
}
if lruKey != "" {
delete(mc.items, lruKey)
mc.stats.Evictions++
}
}
// Delete removes an item from cache
func (mc *MemoryCache) Delete(key string) {
mc.mu.Lock()
defer mc.mu.Unlock()
delete(mc.items, key)
}
// Clear removes all items from cache
func (mc *MemoryCache) Clear() {
mc.mu.Lock()
defer mc.mu.Unlock()
mc.items = make(map[string]*CacheItem)
mc.stats.Evictions += int64(len(mc.items))
}
// Keys returns all keys in cache
func (mc *MemoryCache) Keys() []string {
mc.mu.RLock()
defer mc.mu.RUnlock()
keys := make([]string, 0, len(mc.items))
for key := range mc.items {
keys = append(keys, key)
}
return keys
}
// Size returns the current cache size
func (mc *MemoryCache) Size() int {
mc.mu.RLock()
defer mc.mu.RUnlock()
return len(mc.items)
}
// Stats returns cache statistics
func (mc *MemoryCache) Stats() CacheStats {
mc.mu.RLock()
defer mc.mu.RUnlock()
total := mc.stats.Hits + mc.stats.Misses
hitRate := 0.0
if total > 0 {
hitRate = float64(mc.stats.Hits) / float64(total) * 100
}
return CacheStats{
Hits: mc.stats.Hits,
Misses: mc.stats.Misses,
HitRate: hitRate,
Size: len(mc.items),
Evictions: mc.stats.Evictions,
TotalItems: mc.stats.TotalItems,
}
}
// Close stops the cache cleanup goroutine
func (mc *MemoryCache) Close() {
mc.cleanupTick.Stop()
close(mc.stopCleanup)
}
// RedisCache is a Redis-based cache implementation (placeholder)
type RedisCache struct {
// Redis client would be here
}
// NewRedisCache creates a new Redis cache
func NewRedisCache(addr string, password string, db int) *RedisCache {
// Implementation would connect to Redis
return &RedisCache{}
}
// Get retrieves an item from Redis cache
func (rc *RedisCache) Get(key string) (any, bool) {
// Redis GET implementation
return nil, false
}
// Set stores an item in Redis cache
func (rc *RedisCache) Set(key string, value any, ttl time.Duration) {
// Redis SET implementation
}
// Delete removes an item from Redis cache
func (rc *RedisCache) Delete(key string) {
// Redis DEL implementation
}
// Clear removes all items from Redis cache
func (rc *RedisCache) Clear() {
// Redis FLUSH implementation
}
// Keys returns all keys in Redis cache
func (rc *RedisCache) Keys() []string {
// Redis KEYS implementation
return nil
}
// Size returns the current cache size
func (rc *RedisCache) Size() int {
// Redis DBSIZE implementation
return 0
}
// Stats returns cache statistics
func (rc *RedisCache) Stats() CacheStats {
// Redis INFO implementation
return CacheStats{}
}
// CacheManager manages multiple cache instances
type CacheManager struct {
caches map[string]Cache
mu sync.RWMutex
}
// NewCacheManager creates a new cache manager
func NewCacheManager() *CacheManager {
return &CacheManager{
caches: make(map[string]Cache),
}
}
// AddCache adds a cache instance
func (cm *CacheManager) AddCache(name string, cache Cache) {
cm.mu.Lock()
defer cm.mu.Unlock()
cm.caches[name] = cache
}
// GetCache retrieves a cache instance
func (cm *CacheManager) GetCache(name string) (Cache, bool) {
cm.mu.RLock()
defer cm.mu.RUnlock()
cache, exists := cm.caches[name]
return cache, exists
}
// RemoveCache removes a cache instance
func (cm *CacheManager) RemoveCache(name string) {
cm.mu.Lock()
defer cm.mu.Unlock()
delete(cm.caches, name)
}
// GetAllStats returns statistics for all caches
func (cm *CacheManager) GetAllStats() map[string]CacheStats {
cm.mu.RLock()
defer cm.mu.RUnlock()
stats := make(map[string]CacheStats)
for name, cache := range cm.caches {
stats[name] = cache.Stats()
}
return stats
}
// ClearAll clears all caches
func (cm *CacheManager) ClearAll() {
cm.mu.RLock()
defer cm.mu.RUnlock()
for _, cache := range cm.caches {
cache.Clear()
}
}
// Global cache manager instance
var globalCacheManager *CacheManager
// InitializeCacheManager initializes the global cache manager
func InitializeCacheManager() {
globalCacheManager = NewCacheManager()
// Add default memory cache
memoryCache := NewMemoryCache(1000, 5*time.Minute)
globalCacheManager.AddCache("default", memoryCache)
// Add specialized caches
userCache := NewMemoryCache(500, 10*time.Minute)
globalCacheManager.AddCache("users", userCache)
serviceCache := NewMemoryCache(200, 2*time.Minute)
globalCacheManager.AddCache("services", serviceCache)
metricsCache := NewMemoryCache(1000, 1*time.Minute)
globalCacheManager.AddCache("metrics", metricsCache)
}
// GetCacheManager returns the global cache manager
func GetCacheManager() *CacheManager {
return globalCacheManager
}
// GetDefaultCache returns the default cache
func GetDefaultCache() Cache {
if globalCacheManager == nil {
InitializeCacheManager()
}
cache, _ := globalCacheManager.GetCache("default")
return cache
}
// GetUserCache returns the user cache
func GetUserCache() Cache {
if globalCacheManager == nil {
InitializeCacheManager()
}
cache, _ := globalCacheManager.GetCache("users")
return cache
}
// GetServiceCache returns the service cache
func GetServiceCache() Cache {
if globalCacheManager == nil {
InitializeCacheManager()
}
cache, _ := globalCacheManager.GetCache("services")
return cache
}
// GetMetricsCache returns the metrics cache
func GetMetricsCache() Cache {
if globalCacheManager == nil {
InitializeCacheManager()
}
cache, _ := globalCacheManager.GetCache("metrics")
return cache
}
// CacheKey generates a cache key with namespace
func CacheKey(namespace, key string) string {
return fmt.Sprintf("%s:%s", namespace, key)
}
// SerializeValue serializes a value for caching
func SerializeValue(value any) ([]byte, error) {
return json.Marshal(value)
}
// DeserializeValue deserializes a value from cache
func DeserializeValue(data []byte, target any) error {
return json.Unmarshal(data, target)
}