github.com/ethersphere/bee/v2@v2.2.0/pkg/storer/cachestore.go (about) 1 // Copyright 2023 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package storer 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "time" 12 13 storage "github.com/ethersphere/bee/v2/pkg/storage" 14 "github.com/ethersphere/bee/v2/pkg/storer/internal/transaction" 15 "github.com/ethersphere/bee/v2/pkg/swarm" 16 ) 17 18 const ( 19 cacheOverCapacity = "cacheOverCapacity" 20 ) 21 22 func (db *DB) cacheWorker(ctx context.Context) { 23 24 defer db.inFlight.Done() 25 26 overCapTrigger, overCapUnsub := db.events.Subscribe(cacheOverCapacity) 27 defer overCapUnsub() 28 29 db.triggerCacheEviction() 30 31 for { 32 select { 33 case <-ctx.Done(): 34 return 35 case <-overCapTrigger: 36 37 size, capc := db.cacheObj.Size(), db.cacheObj.Capacity() 38 if size <= capc { 39 continue 40 } 41 42 evict := size - capc 43 if evict < db.opts.cacheMinEvictCount { // evict at least a min count 44 evict = db.opts.cacheMinEvictCount 45 } 46 47 dur := captureDuration(time.Now()) 48 err := db.cacheObj.RemoveOldest(ctx, db.storage, evict) 49 db.metrics.MethodCallsDuration.WithLabelValues("cachestore", "RemoveOldest").Observe(dur()) 50 if err != nil { 51 db.metrics.MethodCalls.WithLabelValues("cachestore", "RemoveOldest", "failure").Inc() 52 db.logger.Warning("cache eviction failure", "error", err) 53 } else { 54 db.logger.Debug("cache eviction finished", "evicted", evict, "duration_sec", dur()) 55 db.metrics.MethodCalls.WithLabelValues("cachestore", "RemoveOldest", "success").Inc() 56 } 57 db.triggerCacheEviction() 58 case <-db.quit: 59 return 60 } 61 } 62 } 63 64 // Lookup is the implementation of the CacheStore.Lookup method. 65 func (db *DB) Lookup() storage.Getter { 66 return getterWithMetrics{ 67 storage.GetterFunc(func(ctx context.Context, address swarm.Address) (swarm.Chunk, error) { 68 ch, err := db.cacheObj.Getter(db.storage).Get(ctx, address) 69 switch { 70 case err == nil: 71 return ch, nil 72 case errors.Is(err, storage.ErrNotFound): 73 // here we would ideally have nothing to do but just to return this 74 // error to the client. The commit is mainly done to end the txn. 75 return nil, err 76 } 77 // if we are here, it means there was some unexpected error, in which 78 // case we need to rollback any changes that were already made. 79 return nil, fmt.Errorf("cache.Get: %w", err) 80 }), 81 db.metrics, 82 "cachestore", 83 } 84 } 85 86 // Cache is the implementation of the CacheStore.Cache method. 87 func (db *DB) Cache() storage.Putter { 88 return putterWithMetrics{ 89 storage.PutterFunc(func(ctx context.Context, ch swarm.Chunk) error { 90 defer db.triggerCacheEviction() 91 err := db.cacheObj.Putter(db.storage).Put(ctx, ch) 92 if err != nil { 93 return fmt.Errorf("cache.Put: %w", err) 94 } 95 return nil 96 }), 97 db.metrics, 98 "cachestore", 99 } 100 } 101 102 // CacheShallowCopy creates cache entries with the expectation that the chunk already exists in the chunkstore. 103 func (db *DB) CacheShallowCopy(ctx context.Context, store transaction.Storage, addrs ...swarm.Address) error { 104 defer db.triggerCacheEviction() 105 dur := captureDuration(time.Now()) 106 err := db.cacheObj.ShallowCopy(ctx, store, addrs...) 107 db.metrics.MethodCallsDuration.WithLabelValues("cachestore", "ShallowCopy").Observe(dur()) 108 if err != nil { 109 err = fmt.Errorf("cache shallow copy: %w", err) 110 db.metrics.MethodCalls.WithLabelValues("cachestore", "ShallowCopy", "failure").Inc() 111 } else { 112 db.metrics.MethodCalls.WithLabelValues("cachestore", "ShallowCopy", "success").Inc() 113 } 114 return err 115 } 116 117 func (db *DB) triggerCacheEviction() { 118 119 var ( 120 size = db.cacheObj.Size() 121 capc = db.cacheObj.Capacity() 122 ) 123 db.metrics.CacheSize.Set(float64(size)) 124 125 if size > capc { 126 db.events.Trigger(cacheOverCapacity) 127 } 128 }