generic_cache.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package util
  2. import (
  3. "context"
  4. "time"
  5. "github.com/karlseguin/ccache/v2"
  6. "github.com/seaweedfs/seaweedfs/weed/glog"
  7. )
  8. // CacheableStore defines the interface for stores that can be cached
  9. type CacheableStore[T any] interface {
  10. Get(ctx context.Context, filerAddress string, key string) (T, error)
  11. Store(ctx context.Context, filerAddress string, key string, value T) error
  12. Delete(ctx context.Context, filerAddress string, key string) error
  13. List(ctx context.Context, filerAddress string) ([]string, error)
  14. }
  15. // CopyFunction defines how to deep copy cached values
  16. type CopyFunction[T any] func(T) T
  17. // CachedStore provides generic TTL caching for any store type
  18. type CachedStore[T any] struct {
  19. baseStore CacheableStore[T]
  20. cache *ccache.Cache
  21. listCache *ccache.Cache
  22. copyFunc CopyFunction[T]
  23. ttl time.Duration
  24. listTTL time.Duration
  25. }
  26. // CachedStoreConfig holds configuration for the generic cached store
  27. type CachedStoreConfig struct {
  28. TTL time.Duration
  29. ListTTL time.Duration
  30. MaxCacheSize int64
  31. }
  32. // NewCachedStore creates a new generic cached store
  33. func NewCachedStore[T any](
  34. baseStore CacheableStore[T],
  35. copyFunc CopyFunction[T],
  36. config CachedStoreConfig,
  37. ) *CachedStore[T] {
  38. // Apply defaults
  39. if config.TTL == 0 {
  40. config.TTL = 5 * time.Minute
  41. }
  42. if config.ListTTL == 0 {
  43. config.ListTTL = 1 * time.Minute
  44. }
  45. if config.MaxCacheSize == 0 {
  46. config.MaxCacheSize = 1000
  47. }
  48. // Create ccache instances
  49. pruneCount := config.MaxCacheSize >> 3
  50. if pruneCount <= 0 {
  51. pruneCount = 100
  52. }
  53. return &CachedStore[T]{
  54. baseStore: baseStore,
  55. cache: ccache.New(ccache.Configure().MaxSize(config.MaxCacheSize).ItemsToPrune(uint32(pruneCount))),
  56. listCache: ccache.New(ccache.Configure().MaxSize(100).ItemsToPrune(10)),
  57. copyFunc: copyFunc,
  58. ttl: config.TTL,
  59. listTTL: config.ListTTL,
  60. }
  61. }
  62. // Get retrieves an item with caching
  63. func (c *CachedStore[T]) Get(ctx context.Context, filerAddress string, key string) (T, error) {
  64. // Try cache first
  65. item := c.cache.Get(key)
  66. if item != nil {
  67. // Cache hit - return cached item (DO NOT extend TTL)
  68. value := item.Value().(T)
  69. glog.V(4).Infof("Cache hit for key %s", key)
  70. return c.copyFunc(value), nil
  71. }
  72. // Cache miss - fetch from base store
  73. glog.V(4).Infof("Cache miss for key %s, fetching from store", key)
  74. value, err := c.baseStore.Get(ctx, filerAddress, key)
  75. if err != nil {
  76. var zero T
  77. return zero, err
  78. }
  79. // Cache the result with TTL
  80. c.cache.Set(key, c.copyFunc(value), c.ttl)
  81. glog.V(3).Infof("Cached key %s with TTL %v", key, c.ttl)
  82. return value, nil
  83. }
  84. // Store stores an item and invalidates cache
  85. func (c *CachedStore[T]) Store(ctx context.Context, filerAddress string, key string, value T) error {
  86. // Store in base store
  87. err := c.baseStore.Store(ctx, filerAddress, key, value)
  88. if err != nil {
  89. return err
  90. }
  91. // Invalidate cache entries
  92. c.cache.Delete(key)
  93. c.listCache.Clear() // Invalidate list cache
  94. glog.V(3).Infof("Stored and invalidated cache for key %s", key)
  95. return nil
  96. }
  97. // Delete deletes an item and invalidates cache
  98. func (c *CachedStore[T]) Delete(ctx context.Context, filerAddress string, key string) error {
  99. // Delete from base store
  100. err := c.baseStore.Delete(ctx, filerAddress, key)
  101. if err != nil {
  102. return err
  103. }
  104. // Invalidate cache entries
  105. c.cache.Delete(key)
  106. c.listCache.Clear() // Invalidate list cache
  107. glog.V(3).Infof("Deleted and invalidated cache for key %s", key)
  108. return nil
  109. }
  110. // List lists all items with caching
  111. func (c *CachedStore[T]) List(ctx context.Context, filerAddress string) ([]string, error) {
  112. const listCacheKey = "item_list"
  113. // Try list cache first
  114. item := c.listCache.Get(listCacheKey)
  115. if item != nil {
  116. // Cache hit - return cached list (DO NOT extend TTL)
  117. items := item.Value().([]string)
  118. glog.V(4).Infof("List cache hit, returning %d items", len(items))
  119. return append([]string(nil), items...), nil // Return a copy
  120. }
  121. // Cache miss - fetch from base store
  122. glog.V(4).Infof("List cache miss, fetching from store")
  123. items, err := c.baseStore.List(ctx, filerAddress)
  124. if err != nil {
  125. return nil, err
  126. }
  127. // Cache the result with TTL (store a copy)
  128. itemsCopy := append([]string(nil), items...)
  129. c.listCache.Set(listCacheKey, itemsCopy, c.listTTL)
  130. glog.V(3).Infof("Cached list with %d entries, TTL %v", len(items), c.listTTL)
  131. return items, nil
  132. }
  133. // ClearCache clears all cached entries
  134. func (c *CachedStore[T]) ClearCache() {
  135. c.cache.Clear()
  136. c.listCache.Clear()
  137. glog.V(2).Infof("Cleared all cache entries")
  138. }
  139. // GetCacheStats returns cache statistics
  140. func (c *CachedStore[T]) GetCacheStats() map[string]interface{} {
  141. return map[string]interface{}{
  142. "itemCache": map[string]interface{}{
  143. "size": c.cache.ItemCount(),
  144. "ttl": c.ttl.String(),
  145. },
  146. "listCache": map[string]interface{}{
  147. "size": c.listCache.ItemCount(),
  148. "ttl": c.listTTL.String(),
  149. },
  150. }
  151. }