reflector.go/store/gcache.go

164 lines
4.5 KiB
Go
Raw Normal View History

2020-11-21 01:26:32 +01:00
package store
import (
2021-01-09 05:08:20 +01:00
"time"
2020-11-21 01:26:32 +01:00
"github.com/lbryio/reflector.go/internal/metrics"
2021-01-09 05:08:20 +01:00
"github.com/lbryio/reflector.go/shared"
2021-07-20 02:09:14 +02:00
"github.com/lbryio/lbry.go/v2/extras/errors"
"github.com/lbryio/lbry.go/v2/stream"
2021-07-23 23:44:21 +02:00
"github.com/bluele/gcache"
2020-11-22 05:06:36 +01:00
"github.com/sirupsen/logrus"
2020-11-21 01:26:32 +01:00
)
2021-07-23 23:44:21 +02:00
// GcacheStore adds a max cache size and Greedy-Dual-Size-Frequency cache eviction strategy to a BlobStore
type GcacheStore struct {
2020-11-21 01:26:32 +01:00
// underlying store
store BlobStore
2021-07-23 23:44:21 +02:00
// cache implementation
cache gcache.Cache
2020-11-21 01:26:32 +01:00
}
2021-07-23 23:44:21 +02:00
type EvictionStrategy int
const (
//LFU Discards the least frequently used items first.
LFU EvictionStrategy = iota
//ARC Constantly balances between LRU and LFU, to improve the combined result.
ARC
//LRU Discards the least recently used items first.
LRU
//SIMPLE has no clear priority for evict cache. It depends on key-value map order.
SIMPLE
)
2020-11-21 01:26:32 +01:00
2021-07-23 23:44:21 +02:00
// NewGcacheStore initialize a new LRUStore
func NewGcacheStore(component string, store BlobStore, maxSize int, strategy EvictionStrategy) *GcacheStore {
cacheBuilder := gcache.New(maxSize)
var cache gcache.Cache
evictFunc := func(key interface{}, value interface{}) {
logrus.Infof("evicting %s", key)
2020-11-21 01:26:32 +01:00
metrics.CacheLRUEvictCount.With(metrics.CacheLabels(store.Name(), component)).Inc()
_ = store.Delete(key.(string)) // TODO: log this error. may happen if underlying entry is gone but cache entry still there
2021-07-23 23:44:21 +02:00
}
switch strategy {
case LFU:
cache = cacheBuilder.LFU().EvictedFunc(evictFunc).Build()
case ARC:
cache = cacheBuilder.ARC().EvictedFunc(evictFunc).Build()
case LRU:
cache = cacheBuilder.LRU().EvictedFunc(evictFunc).Build()
case SIMPLE:
cache = cacheBuilder.Simple().EvictedFunc(evictFunc).Build()
}
l := &GcacheStore{
2020-11-21 01:26:32 +01:00
store: store,
2021-07-23 23:44:21 +02:00
cache: cache,
2020-11-21 01:26:32 +01:00
}
go func() {
if lstr, ok := store.(lister); ok {
2020-11-22 05:06:36 +01:00
err := l.loadExisting(lstr, int(maxSize))
2020-11-21 01:26:32 +01:00
if err != nil {
panic(err) // TODO: what should happen here? panic? return nil? just keep going?
}
}
}()
return l
}
2021-07-23 23:44:21 +02:00
const nameGcache = "gcache"
2020-11-21 01:26:32 +01:00
// Name is the cache type name
2021-07-23 23:44:21 +02:00
func (l *GcacheStore) Name() string { return nameGcache }
2020-11-21 01:26:32 +01:00
// Has returns whether the blob is in the store, without updating the recent-ness.
2021-07-23 23:44:21 +02:00
func (l *GcacheStore) Has(hash string) (bool, error) {
return l.cache.Has(hash), nil
2020-11-21 01:26:32 +01:00
}
// Get returns the blob or an error if the blob doesn't exist.
2021-07-23 23:44:21 +02:00
func (l *GcacheStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
2021-01-09 05:08:20 +01:00
start := time.Now()
2021-07-23 23:44:21 +02:00
_, err := l.cache.Get(hash)
if err != nil {
2021-01-09 05:08:20 +01:00
return nil, shared.NewBlobTrace(time.Since(start), l.Name()), errors.Err(ErrBlobNotFound)
2020-11-21 01:26:32 +01:00
}
2021-01-09 05:08:20 +01:00
blob, stack, err := l.store.Get(hash)
2020-11-21 01:26:32 +01:00
if errors.Is(err, ErrBlobNotFound) {
// Blob disappeared from underlying store
2021-07-23 23:44:21 +02:00
l.cache.Remove(hash)
2020-11-21 01:26:32 +01:00
}
2021-01-09 05:08:20 +01:00
return blob, stack.Stack(time.Since(start), l.Name()), err
2020-11-21 01:26:32 +01:00
}
// Put stores the blob. Following LFUDA rules it's not guaranteed that a SET will store the value!!!
2021-07-23 23:44:21 +02:00
func (l *GcacheStore) Put(hash string, blob stream.Blob) error {
2021-07-24 00:08:13 +02:00
_ = l.cache.Set(hash, true)
has, _ := l.Has(hash)
if has {
err := l.store.Put(hash, blob)
if err != nil {
return err
}
}
2020-11-21 01:26:32 +01:00
return nil
}
2020-12-22 21:19:48 +01:00
// PutSD stores the sd blob. Following LFUDA rules it's not guaranteed that a SET will store the value!!!
2021-07-23 23:44:21 +02:00
func (l *GcacheStore) PutSD(hash string, blob stream.Blob) error {
2021-07-24 00:08:13 +02:00
_ = l.cache.Set(hash, true)
2020-12-22 21:19:48 +01:00
has, _ := l.Has(hash)
if has {
err := l.store.PutSD(hash, blob)
if err != nil {
return err
}
2020-11-21 01:26:32 +01:00
}
return nil
}
// Delete deletes the blob from the store
2021-07-23 23:44:21 +02:00
func (l *GcacheStore) Delete(hash string) error {
2020-11-21 01:26:32 +01:00
err := l.store.Delete(hash)
if err != nil {
return err
}
// This must come after store.Delete()
// Remove triggers onEvict function, which also tries to delete blob from store
// We need to delete it manually first so any errors can be propagated up
2021-07-23 23:44:21 +02:00
l.cache.Remove(hash)
2020-11-21 01:26:32 +01:00
return nil
}
// loadExisting imports existing blobs from the underlying store into the LRU cache
2021-07-23 23:44:21 +02:00
func (l *GcacheStore) loadExisting(store lister, maxItems int) error {
2020-11-22 05:06:36 +01:00
logrus.Infof("loading at most %d items", maxItems)
2020-11-21 01:26:32 +01:00
existing, err := store.list()
if err != nil {
return err
}
logrus.Infof("read %d files from underlying store", len(existing))
2020-11-21 01:26:32 +01:00
added := 0
for i, h := range existing {
2021-07-24 00:08:13 +02:00
_ = l.cache.Set(h, true)
2020-11-21 01:26:32 +01:00
added++
2020-12-22 21:19:48 +01:00
if maxItems > 0 && added >= maxItems { // underlying cache is bigger than the cache
err := l.Delete(h)
logrus.Infof("deleted overflowing blob: %s (%d/%d)", h, i, len(existing))
if err != nil {
logrus.Warnf("error while deleting a blob that's overflowing the cache: %s", err.Error())
}
2020-11-21 01:26:32 +01:00
}
}
return nil
}
// Shutdown shuts down the store gracefully
2021-07-23 23:44:21 +02:00
func (l *GcacheStore) Shutdown() {
}