package cache import ( "context" "forge.cadoles.com/arcad/edge/pkg/storage" "github.com/allegro/bigcache/v3" "github.com/hashicorp/golang-lru/v2/expirable" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" ) type BlobStore struct { store storage.BlobStore contentCache *bigcache.BigCache bucketCache *expirable.LRU[string, storage.BlobBucket] blobInfoCache *expirable.LRU[string, storage.BlobInfo] } // DeleteBucket implements storage.BlobStore. func (s *BlobStore) DeleteBucket(ctx context.Context, name string) error { if err := s.store.DeleteBucket(ctx, name); err != nil { return errors.WithStack(err) } s.bucketCache.Remove(name) return nil } // ListBuckets implements storage.BlobStore. func (s *BlobStore) ListBuckets(ctx context.Context) ([]string, error) { buckets, err := s.store.ListBuckets(ctx) if err != nil { return nil, errors.WithStack(err) } return buckets, nil } // OpenBucket implements storage.BlobStore. func (s *BlobStore) OpenBucket(ctx context.Context, name string) (storage.BlobBucket, error) { bucket, ok := s.bucketCache.Get(name) if ok { logger.Debug(ctx, "found bucket in cache", logger.F("name", name)) return &BlobBucket{ bucket: bucket, contentCache: s.contentCache, blobInfoCache: s.blobInfoCache, bucketCache: s.bucketCache, }, nil } bucket, err := s.store.OpenBucket(ctx, name) if err != nil { return nil, errors.WithStack(err) } s.bucketCache.Add(name, bucket) return &BlobBucket{ bucket: bucket, contentCache: s.contentCache, blobInfoCache: s.blobInfoCache, bucketCache: s.bucketCache, }, nil } func NewBlobStore(store storage.BlobStore, funcs ...OptionFunc) (*BlobStore, error) { options := NewOptions(funcs...) cacheConfig := bigcache.DefaultConfig(options.CacheTTL) cacheConfig.Logger = &cacheLogger{} cacheConfig.HardMaxCacheSize = options.BlobCacheMaxMemorySize cacheConfig.Shards = options.BlobCacheShards contentCache, err := bigcache.New(context.Background(), cacheConfig) if err != nil { return nil, errors.WithStack(err) } onBlobBucketEvict := func(key string, bucket storage.BlobBucket) { ctx := context.Background() logger.Debug(ctx, "evicting blob bucket from cache", logger.F("cacheKey", key)) if err := bucket.Close(); err != nil { logger.Error(ctx, "could not close bucket", logger.E(errors.WithStack(err))) } } bucketCache := expirable.NewLRU[string, storage.BlobBucket](options.BucketCacheSize, onBlobBucketEvict, options.CacheTTL) blobInfoCache := expirable.NewLRU[string, storage.BlobInfo](options.BlobInfoCacheSize, nil, options.CacheTTL) return &BlobStore{ store: store, contentCache: contentCache, bucketCache: bucketCache, blobInfoCache: blobInfoCache, }, nil } var _ storage.BlobStore = &BlobStore{}