edge/pkg/storage/driver/cache/blob_bucket.go

181 lines
4.2 KiB
Go

package cache
import (
"context"
"fmt"
"io"
"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 BlobBucket struct {
bucket storage.BlobBucket
contentCache *bigcache.BigCache
blobInfoCache *expirable.LRU[string, storage.BlobInfo]
bucketCache *expirable.LRU[string, storage.BlobBucket]
}
// Close implements storage.BlobBucket.
func (b *BlobBucket) Close() error {
// Close only when bucket is evicted from cache
return nil
}
// Delete implements storage.BlobBucket.
func (b *BlobBucket) Delete(ctx context.Context, id storage.BlobID) error {
defer b.clearCache(ctx, id)
if err := b.bucket.Delete(ctx, id); err != nil {
return errors.WithStack(err)
}
return nil
}
// Get implements storage.BlobBucket.
func (b *BlobBucket) Get(ctx context.Context, id storage.BlobID) (storage.BlobInfo, error) {
key := b.getCacheKey(id)
if blobInfo, ok := b.blobInfoCache.Get(key); ok {
logger.Debug(
ctx, "found blob info in cache",
logger.F("cacheKey", key),
)
return blobInfo, nil
}
info, err := b.bucket.Get(ctx, id)
if err != nil {
if errors.Is(err, storage.ErrBucketClosed) {
b.clearCache(ctx, id)
b.bucketCache.Remove(b.Name())
}
return nil, errors.WithStack(err)
}
b.blobInfoCache.Add(key, info)
return info, nil
}
// List implements storage.BlobBucket.
func (b *BlobBucket) List(ctx context.Context) ([]storage.BlobInfo, error) {
infos, err := b.bucket.List(ctx)
if err != nil {
if errors.Is(err, storage.ErrBucketClosed) {
b.bucketCache.Remove(b.Name())
}
return nil, errors.WithStack(err)
}
for _, ifo := range infos {
key := b.getCacheKey(ifo.ID())
b.blobInfoCache.Add(key, ifo)
}
return infos, nil
}
// Name implements storage.BlobBucket.
func (b *BlobBucket) Name() string {
return b.bucket.Name()
}
// NewReader implements storage.BlobBucket.
func (b *BlobBucket) NewReader(ctx context.Context, id storage.BlobID) (io.ReadSeekCloser, error) {
if cached, exist := b.inContentCache(id); exist {
logger.Debug(
ctx, "found blob content in cache",
logger.F("cacheKey", b.getCacheKey(id)),
logger.F("cacheStats", b.contentCache.Stats()),
)
return cached, nil
}
reader, err := b.bucket.NewReader(ctx, id)
if err != nil {
if errors.Is(err, storage.ErrBucketClosed) {
b.clearCache(ctx, id)
b.bucketCache.Remove(b.Name())
}
return nil, errors.WithStack(err)
}
return &readCacher{
reader: reader,
cache: b.contentCache,
key: b.getCacheKey(id),
}, nil
}
func (b *BlobBucket) getCacheKey(id storage.BlobID) string {
return fmt.Sprintf("%s-%s", b.Name(), id)
}
func (b *BlobBucket) inContentCache(id storage.BlobID) (io.ReadSeekCloser, bool) {
key := b.getCacheKey(id)
data, err := b.contentCache.Get(key)
if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) {
return nil, false
}
logger.Error(context.Background(), "could not retrieve cache value", logger.CapturedE(errors.WithStack(err)))
return nil, false
}
return &cachedReader{data, 0}, true
}
func (b *BlobBucket) clearCache(ctx context.Context, id storage.BlobID) {
key := b.getCacheKey(id)
logger.Debug(ctx, "clearing cache", logger.F("cacheKey", key))
if err := b.contentCache.Delete(key); err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) {
logger.Error(ctx, "could not clear cache", logger.CapturedE(errors.WithStack(err)))
}
b.blobInfoCache.Remove(key)
}
// NewWriter implements storage.BlobBucket.
func (b *BlobBucket) NewWriter(ctx context.Context, id storage.BlobID) (io.WriteCloser, error) {
defer b.clearCache(ctx, id)
writer, err := b.bucket.NewWriter(ctx, id)
if err != nil {
if errors.Is(err, storage.ErrBucketClosed) {
b.bucketCache.Remove(b.Name())
}
return nil, errors.WithStack(err)
}
return writer, nil
}
// Size implements storage.BlobBucket.
func (b *BlobBucket) Size(ctx context.Context) (int64, error) {
size, err := b.bucket.Size(ctx)
if err != nil {
if errors.Is(err, storage.ErrBucketClosed) {
b.bucketCache.Remove(b.Name())
}
return 0, errors.WithStack(err)
}
return size, nil
}
var _ storage.BlobBucket = &BlobBucket{}