diff options
author | Vladimir Shushlin <vshushlin@gitlab.com> | 2021-09-15 17:40:44 +0300 |
---|---|---|
committer | Vladimir Shushlin <vshushlin@gitlab.com> | 2021-09-15 17:40:44 +0300 |
commit | e4c92b429ec27d8ce711224ae834da638c7de674 (patch) | |
tree | e3a0af7a6a546ae42f9af2539d5b6c2c6ae58b2a | |
parent | 32f5682975cabde8923456e7948e17d0992c8fb7 (diff) | |
parent | 610f9ce661b6b8e9ca84da89c908e48c2b4be0a7 (diff) |
Merge branch '636-lru-as-package' into 'master'
refactor: move lru cache to its own package
See merge request gitlab-org/gitlab-pages!578
-rw-r--r-- | internal/lru/lru.go | 70 | ||||
-rw-r--r-- | internal/vfs/zip/archive.go | 4 | ||||
-rw-r--r-- | internal/vfs/zip/lru_cache.go | 62 | ||||
-rw-r--r-- | internal/vfs/zip/vfs.go | 30 |
4 files changed, 95 insertions, 71 deletions
diff --git a/internal/lru/lru.go b/internal/lru/lru.go new file mode 100644 index 00000000..f36d2a10 --- /dev/null +++ b/internal/lru/lru.go @@ -0,0 +1,70 @@ +package lru + +import ( + "time" + + "github.com/karlseguin/ccache/v2" + "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 + +// Cache wraps a ccache and allows setting custom metrics for hits/misses. +type Cache struct { + op string + duration time.Duration + cache *ccache.Cache + metricCachedEntries *prometheus.GaugeVec + metricCacheRequests *prometheus.CounterVec +} + +// New creates an LRU cache +func New(op string, maxEntries int64, duration time.Duration, cachedEntriesMetric *prometheus.GaugeVec, cacheRequestsMetric *prometheus.CounterVec) *Cache { + configuration := ccache.Configure() + configuration.MaxSize(maxEntries) + configuration.ItemsToPrune(uint32(maxEntries) / itemsToPruneDiv) + configuration.GetsPerPromote(getsPerPromote) // if item gets requested frequently promote it + configuration.OnDelete(func(*ccache.Item) { + cachedEntriesMetric.WithLabelValues(op).Dec() + }) + + return &Cache{ + op: op, + cache: ccache.New(configuration), + duration: duration, + metricCachedEntries: cachedEntriesMetric, + metricCacheRequests: cacheRequestsMetric, + } +} + +// 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() (interface{}, error)) (interface{}, error) { + item := c.cache.Get(cacheNamespace + key) + + if item != nil && !item.Expired() { + c.metricCacheRequests.WithLabelValues(c.op, "hit").Inc() + return item.Value(), nil + } + + value, err := fetchFn() + if err != nil { + c.metricCacheRequests.WithLabelValues(c.op, "error").Inc() + return nil, err + } + + c.metricCacheRequests.WithLabelValues(c.op, "miss").Inc() + c.metricCachedEntries.WithLabelValues(c.op).Inc() + + c.cache.Set(cacheNamespace+key, value, c.duration) + + return value, nil +} diff --git a/internal/vfs/zip/archive.go b/internal/vfs/zip/archive.go index 3d6a9ff1..588fb76d 100644 --- a/internal/vfs/zip/archive.go +++ b/internal/vfs/zip/archive.go @@ -203,7 +203,7 @@ func (a *zipArchive) Open(ctx context.Context, name string) (vfs.File, error) { return nil, errNotFile } - dataOffset, err := a.fs.dataOffsetCache.findOrFetch(a.cacheNamespace, name, func() (interface{}, error) { + dataOffset, err := a.fs.dataOffsetCache.FindOrFetch(a.cacheNamespace, name, func() (interface{}, error) { return file.DataOffset() }) if err != nil { @@ -252,7 +252,7 @@ func (a *zipArchive) Readlink(ctx context.Context, name string) (string, error) return "", errNotSymlink } - symlinkValue, err := a.fs.readlinkCache.findOrFetch(a.cacheNamespace, name, func() (interface{}, error) { + symlinkValue, err := a.fs.readlinkCache.FindOrFetch(a.cacheNamespace, name, func() (interface{}, error) { rc, err := file.Open() if err != nil { return nil, err diff --git a/internal/vfs/zip/lru_cache.go b/internal/vfs/zip/lru_cache.go deleted file mode 100644 index 9810e245..00000000 --- a/internal/vfs/zip/lru_cache.go +++ /dev/null @@ -1,62 +0,0 @@ -package zip - -import ( - "time" - - "github.com/karlseguin/ccache/v2" - - "gitlab.com/gitlab-org/gitlab-pages/metrics" -) - -// 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 lruCacheGetsPerPromote = 64 - -// lruCacheItemsToPruneDiv is a value that indicates how much items -// needs to be pruned on OOM, this prunes 1/16 of items -const lruCacheItemsToPruneDiv = 16 - -type lruCache struct { - op string - duration time.Duration - cache *ccache.Cache -} - -func newLruCache(op string, maxEntries int64, duration time.Duration) *lruCache { - configuration := ccache.Configure() - configuration.MaxSize(maxEntries) - configuration.ItemsToPrune(uint32(maxEntries) / lruCacheItemsToPruneDiv) - configuration.GetsPerPromote(lruCacheGetsPerPromote) // if item gets requested frequently promote it - configuration.OnDelete(func(*ccache.Item) { - metrics.ZipCachedEntries.WithLabelValues(op).Dec() - }) - - return &lruCache{ - op: op, - cache: ccache.New(configuration), - duration: duration, - } -} - -func (c *lruCache) findOrFetch(cacheNamespace, key string, fetchFn func() (interface{}, error)) (interface{}, error) { - item := c.cache.Get(cacheNamespace + key) - - if item != nil && !item.Expired() { - metrics.ZipCacheRequests.WithLabelValues(c.op, "hit").Inc() - return item.Value(), nil - } - - value, err := fetchFn() - if err != nil { - metrics.ZipCacheRequests.WithLabelValues(c.op, "error").Inc() - return nil, err - } - - metrics.ZipCacheRequests.WithLabelValues(c.op, "miss").Inc() - metrics.ZipCachedEntries.WithLabelValues(c.op).Inc() - - c.cache.Set(cacheNamespace+key, value, c.duration) - return value, nil -} diff --git a/internal/vfs/zip/vfs.go b/internal/vfs/zip/vfs.go index 1617c033..4118dd4a 100644 --- a/internal/vfs/zip/vfs.go +++ b/internal/vfs/zip/vfs.go @@ -8,13 +8,13 @@ import ( "sync" "time" - "gitlab.com/gitlab-org/gitlab-pages/internal/httpfs" - "gitlab.com/gitlab-org/gitlab-pages/internal/httptransport" - "github.com/patrickmn/go-cache" "gitlab.com/gitlab-org/gitlab-pages/internal/config" + "gitlab.com/gitlab-org/gitlab-pages/internal/httpfs" "gitlab.com/gitlab-org/gitlab-pages/internal/httprange" + "gitlab.com/gitlab-org/gitlab-pages/internal/httptransport" + "gitlab.com/gitlab-org/gitlab-pages/internal/lru" "gitlab.com/gitlab-org/gitlab-pages/internal/vfs" "gitlab.com/gitlab-org/gitlab-pages/metrics" ) @@ -35,6 +35,10 @@ var ( errAlreadyCached = errors.New("archive already cached") ) +type lruCache interface { + FindOrFetch(cacheNamespace, key string, fetchFn func() (interface{}, error)) (interface{}, error) +} + // zipVFS is a simple cached implementation of the vfs.VFS interface type zipVFS struct { cache *cache.Cache @@ -45,8 +49,8 @@ type zipVFS struct { cacheRefreshInterval time.Duration cacheCleanupInterval time.Duration - dataOffsetCache *lruCache - readlinkCache *lruCache + dataOffsetCache lruCache + readlinkCache lruCache // the `int64` needs to be 64bit aligned on some 32bit systems // https://gitlab.com/gitlab-org/gitlab/-/issues/337261 @@ -80,8 +84,20 @@ func New(cfg *config.ZipServing) vfs.VFS { zipVFS.resetCache() // TODO: To be removed with https://gitlab.com/gitlab-org/gitlab-pages/-/issues/480 - zipVFS.dataOffsetCache = newLruCache("data-offset", defaultDataOffsetItems, defaultDataOffsetExpirationInterval) - zipVFS.readlinkCache = newLruCache("readlink", defaultReadlinkItems, defaultReadlinkExpirationInterval) + zipVFS.dataOffsetCache = lru.New( + "data-offset", + defaultDataOffsetItems, + defaultDataOffsetExpirationInterval, + metrics.ZipCachedEntries, + metrics.ZipCacheRequests, + ) + zipVFS.readlinkCache = lru.New( + "readlink", + defaultReadlinkItems, + defaultReadlinkExpirationInterval, + metrics.ZipCachedEntries, + metrics.ZipCacheRequests, + ) return zipVFS } |