diff options
Diffstat (limited to 'weed/iam/util/generic_cache.go')
| -rw-r--r-- | weed/iam/util/generic_cache.go | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/weed/iam/util/generic_cache.go b/weed/iam/util/generic_cache.go new file mode 100644 index 000000000..19bc3d67b --- /dev/null +++ b/weed/iam/util/generic_cache.go @@ -0,0 +1,175 @@ +package util + +import ( + "context" + "time" + + "github.com/karlseguin/ccache/v2" + "github.com/seaweedfs/seaweedfs/weed/glog" +) + +// CacheableStore defines the interface for stores that can be cached +type CacheableStore[T any] interface { + Get(ctx context.Context, filerAddress string, key string) (T, error) + Store(ctx context.Context, filerAddress string, key string, value T) error + Delete(ctx context.Context, filerAddress string, key string) error + List(ctx context.Context, filerAddress string) ([]string, error) +} + +// CopyFunction defines how to deep copy cached values +type CopyFunction[T any] func(T) T + +// CachedStore provides generic TTL caching for any store type +type CachedStore[T any] struct { + baseStore CacheableStore[T] + cache *ccache.Cache + listCache *ccache.Cache + copyFunc CopyFunction[T] + ttl time.Duration + listTTL time.Duration +} + +// CachedStoreConfig holds configuration for the generic cached store +type CachedStoreConfig struct { + TTL time.Duration + ListTTL time.Duration + MaxCacheSize int64 +} + +// NewCachedStore creates a new generic cached store +func NewCachedStore[T any]( + baseStore CacheableStore[T], + copyFunc CopyFunction[T], + config CachedStoreConfig, +) *CachedStore[T] { + // Apply defaults + if config.TTL == 0 { + config.TTL = 5 * time.Minute + } + if config.ListTTL == 0 { + config.ListTTL = 1 * time.Minute + } + if config.MaxCacheSize == 0 { + config.MaxCacheSize = 1000 + } + + // Create ccache instances + pruneCount := config.MaxCacheSize >> 3 + if pruneCount <= 0 { + pruneCount = 100 + } + + return &CachedStore[T]{ + baseStore: baseStore, + cache: ccache.New(ccache.Configure().MaxSize(config.MaxCacheSize).ItemsToPrune(uint32(pruneCount))), + listCache: ccache.New(ccache.Configure().MaxSize(100).ItemsToPrune(10)), + copyFunc: copyFunc, + ttl: config.TTL, + listTTL: config.ListTTL, + } +} + +// Get retrieves an item with caching +func (c *CachedStore[T]) Get(ctx context.Context, filerAddress string, key string) (T, error) { + // Try cache first + item := c.cache.Get(key) + if item != nil { + // Cache hit - return cached item (DO NOT extend TTL) + value := item.Value().(T) + glog.V(4).Infof("Cache hit for key %s", key) + return c.copyFunc(value), nil + } + + // Cache miss - fetch from base store + glog.V(4).Infof("Cache miss for key %s, fetching from store", key) + value, err := c.baseStore.Get(ctx, filerAddress, key) + if err != nil { + var zero T + return zero, err + } + + // Cache the result with TTL + c.cache.Set(key, c.copyFunc(value), c.ttl) + glog.V(3).Infof("Cached key %s with TTL %v", key, c.ttl) + return value, nil +} + +// Store stores an item and invalidates cache +func (c *CachedStore[T]) Store(ctx context.Context, filerAddress string, key string, value T) error { + // Store in base store + err := c.baseStore.Store(ctx, filerAddress, key, value) + if err != nil { + return err + } + + // Invalidate cache entries + c.cache.Delete(key) + c.listCache.Clear() // Invalidate list cache + + glog.V(3).Infof("Stored and invalidated cache for key %s", key) + return nil +} + +// Delete deletes an item and invalidates cache +func (c *CachedStore[T]) Delete(ctx context.Context, filerAddress string, key string) error { + // Delete from base store + err := c.baseStore.Delete(ctx, filerAddress, key) + if err != nil { + return err + } + + // Invalidate cache entries + c.cache.Delete(key) + c.listCache.Clear() // Invalidate list cache + + glog.V(3).Infof("Deleted and invalidated cache for key %s", key) + return nil +} + +// List lists all items with caching +func (c *CachedStore[T]) List(ctx context.Context, filerAddress string) ([]string, error) { + const listCacheKey = "item_list" + + // Try list cache first + item := c.listCache.Get(listCacheKey) + if item != nil { + // Cache hit - return cached list (DO NOT extend TTL) + items := item.Value().([]string) + glog.V(4).Infof("List cache hit, returning %d items", len(items)) + return append([]string(nil), items...), nil // Return a copy + } + + // Cache miss - fetch from base store + glog.V(4).Infof("List cache miss, fetching from store") + items, err := c.baseStore.List(ctx, filerAddress) + if err != nil { + return nil, err + } + + // Cache the result with TTL (store a copy) + itemsCopy := append([]string(nil), items...) + c.listCache.Set(listCacheKey, itemsCopy, c.listTTL) + glog.V(3).Infof("Cached list with %d entries, TTL %v", len(items), c.listTTL) + return items, nil +} + +// ClearCache clears all cached entries +func (c *CachedStore[T]) ClearCache() { + c.cache.Clear() + c.listCache.Clear() + glog.V(2).Infof("Cleared all cache entries") +} + +// GetCacheStats returns cache statistics +func (c *CachedStore[T]) GetCacheStats() map[string]interface{} { + return map[string]interface{}{ + "itemCache": map[string]interface{}{ + "size": c.cache.ItemCount(), + "ttl": c.ttl.String(), + }, + "listCache": map[string]interface{}{ + "size": c.listCache.ItemCount(), + "ttl": c.listTTL.String(), + }, + } +} |
