201 lines
4.4 KiB
Go
201 lines
4.4 KiB
Go
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"
|
|
}
|
|
}
|