aboutsummaryrefslogtreecommitdiff
path: root/weed/iam/util/generic_cache.go
blob: 19bc3d67bcf521e85ffd1bcef4f4fe347dc2e989 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
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(),
		},
	}
}