github.com/Finschia/finschia-sdk@v0.48.1/store/cache/cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/VictoriaMetrics/fastcache"
     8  
     9  	"github.com/Finschia/finschia-sdk/store/cachekv"
    10  	"github.com/Finschia/finschia-sdk/store/types"
    11  )
    12  
    13  const (
    14  	DefaultCommitKVStoreCacheSize = 1024 * 1024 * 100 // 100 MB
    15  )
    16  
    17  var (
    18  	_ types.CommitKVStore             = (*CommitKVStoreCache)(nil)
    19  	_ types.MultiStorePersistentCache = (*CommitKVStoreCacheManager)(nil)
    20  )
    21  
    22  type (
    23  	// CommitKVStoreCache implements an inter-block (persistent) cache that wraps a
    24  	// CommitKVStore. Reads first hit the internal ARC (Adaptive Replacement Cache).
    25  	// During a cache miss, the read is delegated to the underlying CommitKVStore
    26  	// and cached. Deletes and writes always happen to both the cache and the
    27  	// CommitKVStore in a write-through manner. Caching performed in the
    28  	// CommitKVStore and below is completely irrelevant to this layer.
    29  	CommitKVStoreCache struct {
    30  		types.CommitKVStore
    31  		cache   *fastcache.Cache
    32  		prefix  []byte
    33  		metrics *Metrics
    34  	}
    35  
    36  	// CommitKVStoreCacheManager maintains a mapping from a StoreKey to a
    37  	// CommitKVStoreCache. Each CommitKVStore, per StoreKey, is meant to be used
    38  	// in an inter-block (persistent) manner and typically provided by a
    39  	// CommitMultiStore.
    40  	CommitKVStoreCacheManager struct {
    41  		mutex   sync.Mutex
    42  		cache   *fastcache.Cache
    43  		caches  map[string]types.CommitKVStore
    44  		metrics *Metrics
    45  
    46  		// All cache stores use the unique prefix that has one byte length
    47  		// Contract: The number of all cache stores cannot exceed 127(max byte)
    48  		prefixMap   map[string][]byte
    49  		prefixOrder byte
    50  	}
    51  )
    52  
    53  func NewCommitKVStoreCache(store types.CommitKVStore, prefix []byte, cache *fastcache.Cache,
    54  	metrics *Metrics) *CommitKVStoreCache {
    55  	return &CommitKVStoreCache{
    56  		CommitKVStore: store,
    57  		prefix:        prefix,
    58  		cache:         cache,
    59  		metrics:       metrics,
    60  	}
    61  }
    62  
    63  func NewCommitKVStoreCacheManager(cacheSize int, provider MetricsProvider) *CommitKVStoreCacheManager {
    64  	if cacheSize <= 0 {
    65  		// This function was called because it intended to use the inter block cache, creating a cache of minimal size.
    66  		cacheSize = DefaultCommitKVStoreCacheSize
    67  	}
    68  	cm := &CommitKVStoreCacheManager{
    69  		cache:       fastcache.New(cacheSize),
    70  		caches:      make(map[string]types.CommitKVStore),
    71  		metrics:     provider(),
    72  		prefixMap:   make(map[string][]byte),
    73  		prefixOrder: 0,
    74  	}
    75  	startCacheMetricUpdator(cm.cache, cm.metrics)
    76  	return cm
    77  }
    78  
    79  func startCacheMetricUpdator(cache *fastcache.Cache, metrics *Metrics) {
    80  	// Execution time of `fastcache.UpdateStats()` can increase linearly as cache entries grows
    81  	// So we update the metrics with a separate go route.
    82  	go func() {
    83  		for {
    84  			stats := fastcache.Stats{}
    85  			cache.UpdateStats(&stats)
    86  			metrics.InterBlockCacheEntries.Set(float64(stats.EntriesCount))
    87  			metrics.InterBlockCacheBytes.Set(float64(stats.BytesSize))
    88  			time.Sleep(1 * time.Minute)
    89  		}
    90  	}()
    91  }
    92  
    93  // GetStoreCache returns a Cache from the CommitStoreCacheManager for a given
    94  // StoreKey. If no Cache exists for the StoreKey, then one is created and set.
    95  // The returned Cache is meant to be used in a persistent manner.
    96  func (cmgr *CommitKVStoreCacheManager) GetStoreCache(key types.StoreKey, store types.CommitKVStore) types.CommitKVStore {
    97  	if cmgr.caches[key.Name()] == nil {
    98  		// After concurrent checkTx, delieverTx becomes to be possible, this should be protected by a mutex
    99  		cmgr.mutex.Lock()
   100  		if cmgr.caches[key.Name()] == nil { // recheck after acquiring lock
   101  			cmgr.prefixMap[key.Name()] = []byte{cmgr.prefixOrder}
   102  			cmgr.prefixOrder++
   103  			if cmgr.prefixOrder <= 0 {
   104  				panic("The number of cache stores exceed the maximum(127)")
   105  			}
   106  			cmgr.caches[key.Name()] = NewCommitKVStoreCache(store, cmgr.prefixMap[key.Name()], cmgr.cache, cmgr.metrics)
   107  		}
   108  		cmgr.mutex.Unlock()
   109  	}
   110  
   111  	return cmgr.caches[key.Name()]
   112  }
   113  
   114  // Unwrap returns the underlying CommitKVStore for a given StoreKey.
   115  func (cmgr *CommitKVStoreCacheManager) Unwrap(key types.StoreKey) types.CommitKVStore {
   116  	if ckv, ok := cmgr.caches[key.Name()]; ok {
   117  		return ckv.(*CommitKVStoreCache).CommitKVStore
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  // Reset resets in the internal caches.
   124  func (cmgr *CommitKVStoreCacheManager) Reset() {
   125  	// Clear the map.
   126  	// Please note that we are purposefully using the map clearing idiom.
   127  	// See https://github.com/cosmos/cosmos-sdk/issues/6681.
   128  	for key := range cmgr.caches {
   129  		delete(cmgr.caches, key)
   130  	}
   131  }
   132  
   133  // CacheWrap implements the CacheWrapper interface
   134  func (ckv *CommitKVStoreCache) CacheWrap() types.CacheWrap {
   135  	return cachekv.NewStore(ckv)
   136  }
   137  
   138  // Get retrieves a value by key. It will first look in the write-through cache.
   139  // If the value doesn't exist in the write-through cache, the query is delegated
   140  // to the underlying CommitKVStore.
   141  func (ckv *CommitKVStoreCache) Get(key []byte) []byte {
   142  	types.AssertValidKey(key)
   143  	prefixedKey := append(ckv.prefix, key...)
   144  
   145  	valueI := ckv.cache.Get(nil, prefixedKey)
   146  	if valueI != nil {
   147  		// cache hit
   148  		ckv.metrics.InterBlockCacheHits.Add(1)
   149  		return valueI
   150  	}
   151  
   152  	// cache miss; write to cache
   153  	ckv.metrics.InterBlockCacheMisses.Add(1)
   154  	value := ckv.CommitKVStore.Get(key)
   155  	ckv.cache.Set(prefixedKey, value)
   156  	return value
   157  }
   158  
   159  // Set inserts a key/value pair into both the write-through cache and the
   160  // underlying CommitKVStore.
   161  func (ckv *CommitKVStoreCache) Set(key, value []byte) {
   162  	types.AssertValidKey(key)
   163  	types.AssertValidValue(value)
   164  
   165  	prefixedKey := append(ckv.prefix, key...)
   166  	ckv.cache.Set(prefixedKey, value)
   167  	ckv.CommitKVStore.Set(key, value)
   168  }
   169  
   170  // Delete removes a key/value pair from both the write-through cache and the
   171  // underlying CommitKVStore.
   172  func (ckv *CommitKVStoreCache) Delete(key []byte) {
   173  	prefixedKey := append(ckv.prefix, key...)
   174  	ckv.cache.Del(prefixedKey)
   175  	ckv.CommitKVStore.Delete(key)
   176  }