package linedb import ( "sync" "time" ) // CacheEntry представляет запись в кэше type CacheEntry struct { Data any Timestamp time.Time TTL time.Duration } // RecordCache представляет кэш записей type RecordCache struct { cache map[string]*CacheEntry mutex sync.RWMutex maxSize int ttl time.Duration stopChan chan struct{} } // NewRecordCache создает новый кэш func NewRecordCache(maxSize int, ttl time.Duration) *RecordCache { cache := &RecordCache{ cache: make(map[string]*CacheEntry), maxSize: maxSize, ttl: ttl, stopChan: make(chan struct{}), } // Запускаем очистку устаревших записей только если TTL > 0 if ttl > 0 { go cache.cleanupLoop() } return cache } // Set устанавливает значение в кэш func (c *RecordCache) Set(key string, value any) { c.mutex.Lock() defer c.mutex.Unlock() // Проверяем размер кэша if len(c.cache) >= c.maxSize { c.evictOldest() } c.cache[key] = &CacheEntry{ Data: value, Timestamp: time.Now(), TTL: c.ttl, } } // Get получает значение из кэша func (c *RecordCache) Get(key string) (any, bool) { c.mutex.RLock() defer c.mutex.RUnlock() entry, exists := c.cache[key] if !exists { return nil, false } // Проверяем TTL if c.ttl > 0 && time.Since(entry.Timestamp) > c.ttl { delete(c.cache, key) return nil, false } return entry.Data, true } // Has проверяет наличие ключа в кэше func (c *RecordCache) Has(key string) bool { _, exists := c.Get(key) return exists } // Delete удаляет ключ из кэша func (c *RecordCache) Delete(key string) { c.mutex.Lock() defer c.mutex.Unlock() delete(c.cache, key) } // Clear очищает кэш func (c *RecordCache) Clear() { c.mutex.Lock() defer c.mutex.Unlock() c.cache = make(map[string]*CacheEntry) } // Stop останавливает кэш func (c *RecordCache) Stop() { close(c.stopChan) } // Size возвращает размер кэша func (c *RecordCache) Size() int { c.mutex.RLock() defer c.mutex.RUnlock() return len(c.cache) } // GetFlatCacheMap возвращает плоскую карту кэша func (c *RecordCache) GetFlatCacheMap() map[string]*CacheEntry { c.mutex.RLock() defer c.mutex.RUnlock() result := make(map[string]*CacheEntry) for key, entry := range c.cache { result[key] = entry } return result } // evictOldest удаляет самую старую запись из кэша func (c *RecordCache) evictOldest() { var oldestKey string var oldestTime time.Time for key, entry := range c.cache { if oldestKey == "" || entry.Timestamp.Before(oldestTime) { oldestKey = key oldestTime = entry.Timestamp } } if oldestKey != "" { delete(c.cache, oldestKey) } } // cleanupLoop запускает цикл очистки устаревших записей func (c *RecordCache) cleanupLoop() { // Используем более безопасный интервал interval := c.ttl / 4 if interval < time.Second { interval = time.Second } ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ticker.C: c.cleanup() case <-c.stopChan: return } } } // cleanup очищает устаревшие записи func (c *RecordCache) cleanup() { c.mutex.Lock() defer c.mutex.Unlock() now := time.Now() for key, entry := range c.cache { if c.ttl > 0 && now.Sub(entry.Timestamp) > c.ttl { delete(c.cache, key) } } } // SetByRecord устанавливает запись в кэш по записи func (c *RecordCache) SetByRecord(record any, collectionName string) { key := toString(record) + ":" + collectionName c.Set(key, record) } // UpdateCacheAfterInsert обновляет кэш после вставки func (c *RecordCache) UpdateCacheAfterInsert(record any, collectionName string) { c.SetByRecord(record, collectionName) } // toString преобразует значение в строку func toString(value any) string { switch v := value.(type) { case string: return v case int: return string(rune(v)) case float64: return string(rune(int(v))) case map[string]any: if id, exists := v["id"]; exists { return toString(id) } return "unknown" default: return "unknown" } }