internal/lru/lru.go (89 lines of code) (raw):

package lru import ( "time" "github.com/karlseguin/ccache/v3" "github.com/prometheus/client_golang/prometheus" ) // lruCacheGetPerPromote is a value that makes the item to be promoted // it is taken arbitrally as a sane value indicating that the item // was frequently picked // promotion moves the item to the front of the LRU list const getsPerPromote = 64 // itemsToPruneDiv is a value that indicates how much items // needs to be pruned on OOM, this prunes 1/16 of items const itemsToPruneDiv = 16 const defaultCacheMaxSize = 1000 const defaultCacheExpirationInterval = time.Minute // Option function to configure a Cache type Option func(*Cache) // Cache wraps a ccache and allows setting custom metrics for hits/misses. // duration and maxSize are initialized to their default values but should // be configured using WithExpirationInterval and WithMaxSize options. type Cache struct { op string duration time.Duration maxSize int64 cache *ccache.Cache[any] metricCachedEntries *prometheus.GaugeVec metricCacheRequests *prometheus.CounterVec } // New creates an LRU cache func New(op string, opts ...Option) *Cache { c := &Cache{ op: op, duration: defaultCacheExpirationInterval, maxSize: defaultCacheMaxSize, } for _, opt := range opts { opt(c) } configuration := ccache.Configure[any]() configuration.MaxSize(c.maxSize) configuration.ItemsToPrune(uint32(c.maxSize) / itemsToPruneDiv) // nolint:gosec configuration.GetsPerPromote(getsPerPromote) // if item gets requested frequently promote it configuration.OnDelete(func(*ccache.Item[any]) { if c.metricCachedEntries != nil { c.metricCachedEntries.WithLabelValues(op).Dec() } }) c.cache = ccache.New(configuration) return c } // FindOrFetch will try to get the item from the cache if exists and is not expired. // If it can't find it, it will call fetchFn to retrieve the item and cache it. func (c *Cache) FindOrFetch(cacheNamespace, key string, fetchFn func() (any, error)) (any, error) { item := c.cache.Get(cacheNamespace + key) if item != nil && !item.Expired() { if c.metricCacheRequests != nil { c.metricCacheRequests.WithLabelValues(c.op, "hit").Inc() } return item.Value(), nil } value, err := fetchFn() if err != nil { if c.metricCacheRequests != nil { c.metricCacheRequests.WithLabelValues(c.op, "error").Inc() } return nil, err } if c.metricCacheRequests != nil { c.metricCacheRequests.WithLabelValues(c.op, "miss").Inc() } if c.metricCachedEntries != nil { c.metricCachedEntries.WithLabelValues(c.op).Inc() } c.cache.Set(cacheNamespace+key, value, c.duration) return value, nil } // Stop stops the cache and releases its resources. func (c *Cache) Stop() { if c.cache != nil { c.cache.Stop() } } func WithCachedEntriesMetric(m *prometheus.GaugeVec) Option { return func(c *Cache) { c.metricCachedEntries = m } } func WithCachedRequestsMetric(m *prometheus.CounterVec) Option { return func(c *Cache) { c.metricCacheRequests = m } } func WithExpirationInterval(t time.Duration) Option { return func(c *Cache) { c.duration = t } } func WithMaxSize(i int64) Option { return func(c *Cache) { c.maxSize = i } }