github.com/ethersphere/bee/v2@v2.2.0/pkg/storage/cache/cache.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 cache
     6  
     7  import (
     8  	"github.com/ethersphere/bee/v2/pkg/storage"
     9  	"github.com/ethersphere/bee/v2/pkg/storage/storageutil"
    10  	lru "github.com/hashicorp/golang-lru/v2"
    11  )
    12  
    13  // key returns a string representation of the given key.
    14  func key(key storage.Key) string {
    15  	return storageutil.JoinFields(key.Namespace(), key.ID())
    16  }
    17  
    18  var _ storage.IndexStore = (*Cache)(nil)
    19  
    20  // Cache is a wrapper around a storage.Store that adds a layer
    21  // of in-memory caching for the Get and Has operations.
    22  type Cache struct {
    23  	storage.IndexStore
    24  
    25  	lru     *lru.Cache[string, []byte]
    26  	metrics metrics
    27  }
    28  
    29  // Wrap adds a layer of in-memory caching to storage.Reader Get and Has operations.
    30  // It returns an error if the capacity is less than or equal to zero or if the
    31  // given store implements storage.Tx
    32  func Wrap(store storage.IndexStore, capacity int) (*Cache, error) {
    33  	lru, err := lru.New[string, []byte](capacity)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	return &Cache{store, lru, newMetrics()}, nil
    39  }
    40  
    41  // add caches given item.
    42  func (c *Cache) add(i storage.Item) {
    43  	b, err := i.Marshal()
    44  	if err != nil {
    45  		return
    46  	}
    47  	c.lru.Add(key(i), b)
    48  }
    49  
    50  // Get implements storage.Store interface.
    51  // On a call it tries to first retrieve the item from cache.
    52  // If the item does not exist in cache, it tries to retrieve
    53  // it from the underlying store.
    54  func (c *Cache) Get(i storage.Item) error {
    55  	if val, ok := c.lru.Get(key(i)); ok {
    56  		c.metrics.CacheHit.Inc()
    57  		return i.Unmarshal(val)
    58  	}
    59  
    60  	if err := c.IndexStore.Get(i); err != nil {
    61  		return err
    62  	}
    63  
    64  	c.metrics.CacheMiss.Inc()
    65  	c.add(i)
    66  
    67  	return nil
    68  }
    69  
    70  // Has implements storage.Store interface.
    71  // On a call it tries to first retrieve the item from cache.
    72  // If the item does not exist in cache, it tries to retrieve
    73  // it from the underlying store.
    74  func (c *Cache) Has(k storage.Key) (bool, error) {
    75  	if _, ok := c.lru.Get(key(k)); ok {
    76  		c.metrics.CacheHit.Inc()
    77  		return true, nil
    78  	}
    79  
    80  	c.metrics.CacheMiss.Inc()
    81  	return c.IndexStore.Has(k)
    82  }
    83  
    84  // Put implements storage.Store interface.
    85  // On a call it also inserts the item into the cache so that the next
    86  // call to Put and Has will be able to retrieve the item from cache.
    87  func (c *Cache) Put(i storage.Item) error {
    88  	c.add(i)
    89  	return c.IndexStore.Put(i)
    90  }
    91  
    92  // Delete implements storage.Store interface.
    93  // On a call it also removes the item from the cache.
    94  func (c *Cache) Delete(i storage.Item) error {
    95  	_ = c.lru.Remove(key(i))
    96  	return c.IndexStore.Delete(i)
    97  }
    98  
    99  func (c *Cache) Close() error {
   100  	c.lru.Purge()
   101  	return nil
   102  }