github.com/Finschia/finschia-sdk@v0.49.1/store/cachekv/store.go (about)

     1  package cachekv
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"sort"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/Finschia/ostracon/libs/math"
    11  	dbm "github.com/tendermint/tm-db"
    12  
    13  	"github.com/Finschia/finschia-sdk/internal/conv"
    14  	"github.com/Finschia/finschia-sdk/store/listenkv"
    15  	"github.com/Finschia/finschia-sdk/store/tracekv"
    16  	"github.com/Finschia/finschia-sdk/store/types"
    17  	"github.com/Finschia/finschia-sdk/telemetry"
    18  	"github.com/Finschia/finschia-sdk/types/kv"
    19  )
    20  
    21  // If value is nil but deleted is false, it means the parent doesn't have the
    22  // key.  (No need to delete upon Write())
    23  type cValue struct {
    24  	value []byte
    25  	dirty bool
    26  }
    27  
    28  // Store wraps an in-memory cache around an underlying types.KVStore.
    29  // Set, Delete and Write for the same key must be called sequentially.
    30  type Store struct {
    31  	mtx           sync.Mutex
    32  	cache         map[string]*cValue
    33  	deleted       map[string]struct{}
    34  	unsortedCache map[string]struct{}
    35  	sortedCache   *dbm.MemDB // always ascending sorted
    36  	parent        types.KVStore
    37  }
    38  
    39  var _ types.CacheKVStore = (*Store)(nil)
    40  
    41  // NewStore creates a new Store object
    42  func NewStore(parent types.KVStore) *Store {
    43  	return &Store{
    44  		cache:         make(map[string]*cValue),
    45  		deleted:       make(map[string]struct{}),
    46  		unsortedCache: make(map[string]struct{}),
    47  		sortedCache:   dbm.NewMemDB(),
    48  		parent:        parent,
    49  	}
    50  }
    51  
    52  // GetStoreType implements Store.
    53  func (store *Store) GetStoreType() types.StoreType {
    54  	return store.parent.GetStoreType()
    55  }
    56  
    57  // Get implements types.KVStore.
    58  func (store *Store) Get(key []byte) (value []byte) {
    59  	store.mtx.Lock()
    60  	defer store.mtx.Unlock()
    61  
    62  	types.AssertValidKey(key)
    63  
    64  	cacheValue, ok := store.cache[conv.UnsafeBytesToStr(key)]
    65  	if !ok {
    66  		value = store.parent.Get(key)
    67  		store.setCacheValue(key, value, false, false)
    68  	} else {
    69  		value = cacheValue.value
    70  	}
    71  
    72  	return value
    73  }
    74  
    75  // Set implements types.KVStore.
    76  func (store *Store) Set(key, value []byte) {
    77  	store.mtx.Lock()
    78  	defer store.mtx.Unlock()
    79  
    80  	types.AssertValidKey(key)
    81  	types.AssertValidValue(value)
    82  
    83  	store.setCacheValue(key, value, false, true)
    84  }
    85  
    86  // Has implements types.KVStore.
    87  func (store *Store) Has(key []byte) bool {
    88  	value := store.Get(key)
    89  	return value != nil
    90  }
    91  
    92  // Delete implements types.KVStore.
    93  func (store *Store) Delete(key []byte) {
    94  	store.mtx.Lock()
    95  	defer store.mtx.Unlock()
    96  	defer telemetry.MeasureSince(time.Now(), "store", "cachekv", "delete")
    97  
    98  	types.AssertValidKey(key)
    99  	store.setCacheValue(key, nil, true, true)
   100  }
   101  
   102  // Implements Cachetypes.KVStore.
   103  func (store *Store) Write() {
   104  	store.mtx.Lock()
   105  	defer store.mtx.Unlock()
   106  	defer telemetry.MeasureSince(time.Now(), "store", "cachekv", "write")
   107  
   108  	// We need a copy of all of the keys.
   109  	// Not the best, but probably not a bottleneck depending.
   110  	keys := make([]string, 0, len(store.cache))
   111  
   112  	for key, dbValue := range store.cache {
   113  		if dbValue.dirty {
   114  			keys = append(keys, key)
   115  		}
   116  	}
   117  
   118  	sort.Strings(keys)
   119  
   120  	// TODO: Consider allowing usage of Batch, which would allow the write to
   121  	// at least happen atomically.
   122  	for _, key := range keys {
   123  		if store.isDeleted(key) {
   124  			// We use []byte(key) instead of conv.UnsafeStrToBytes because we cannot
   125  			// be sure if the underlying store might do a save with the byteslice or
   126  			// not. Once we get confirmation that .Delete is guaranteed not to
   127  			// save the byteslice, then we can assume only a read-only copy is sufficient.
   128  			store.parent.Delete([]byte(key))
   129  			continue
   130  		}
   131  
   132  		cacheValue := store.cache[key]
   133  		if cacheValue.value != nil {
   134  			// It already exists in the parent, hence delete it.
   135  			store.parent.Set([]byte(key), cacheValue.value)
   136  		}
   137  	}
   138  
   139  	// Clear the cache using the map clearing idiom
   140  	// and not allocating fresh objects.
   141  	// Please see https://bencher.orijtech.com/perfclinic/mapclearing/
   142  	for key := range store.cache {
   143  		delete(store.cache, key)
   144  	}
   145  	for key := range store.deleted {
   146  		delete(store.deleted, key)
   147  	}
   148  	for key := range store.unsortedCache {
   149  		delete(store.unsortedCache, key)
   150  	}
   151  	store.sortedCache = dbm.NewMemDB()
   152  }
   153  
   154  // CacheWrap implements CacheWrapper.
   155  func (store *Store) CacheWrap() types.CacheWrap {
   156  	return NewStore(store)
   157  }
   158  
   159  // CacheWrapWithTrace implements the CacheWrapper interface.
   160  func (store *Store) CacheWrapWithTrace(w io.Writer, tc types.TraceContext) types.CacheWrap {
   161  	return NewStore(tracekv.NewStore(store, w, tc))
   162  }
   163  
   164  // CacheWrapWithListeners implements the CacheWrapper interface.
   165  func (store *Store) CacheWrapWithListeners(storeKey types.StoreKey, listeners []types.WriteListener) types.CacheWrap {
   166  	return NewStore(listenkv.NewStore(store, storeKey, listeners))
   167  }
   168  
   169  //----------------------------------------
   170  // Iteration
   171  
   172  // Iterator implements types.KVStore.
   173  func (store *Store) Iterator(start, end []byte) types.Iterator {
   174  	return store.iterator(start, end, true)
   175  }
   176  
   177  // ReverseIterator implements types.KVStore.
   178  func (store *Store) ReverseIterator(start, end []byte) types.Iterator {
   179  	return store.iterator(start, end, false)
   180  }
   181  
   182  func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator {
   183  	store.mtx.Lock()
   184  	defer store.mtx.Unlock()
   185  
   186  	var parent, cache types.Iterator
   187  
   188  	if ascending {
   189  		parent = store.parent.Iterator(start, end)
   190  	} else {
   191  		parent = store.parent.ReverseIterator(start, end)
   192  	}
   193  
   194  	store.dirtyItems(start, end)
   195  	cache = newMemIterator(start, end, store.sortedCache, store.deleted, ascending)
   196  
   197  	return newCacheMergeIterator(parent, cache, ascending)
   198  }
   199  
   200  func findStartIndex(strL []string, startQ string) int {
   201  	// Modified binary search to find the very first element in >=startQ.
   202  	if len(strL) == 0 {
   203  		return -1
   204  	}
   205  
   206  	var left, right, mid int
   207  	right = len(strL) - 1
   208  	for left <= right {
   209  		mid = (left + right) >> 1
   210  		midStr := strL[mid]
   211  		if midStr == startQ {
   212  			// Handle condition where there might be multiple values equal to startQ.
   213  			// We are looking for the very first value < midStL, that i+1 will be the first
   214  			// element >= midStr.
   215  			for i := mid - 1; i >= 0; i-- {
   216  				if strL[i] != midStr {
   217  					return i + 1
   218  				}
   219  			}
   220  			return 0
   221  		}
   222  		if midStr < startQ {
   223  			left = mid + 1
   224  		} else { // midStrL > startQ
   225  			right = mid - 1
   226  		}
   227  	}
   228  	if left >= 0 && left < len(strL) && strL[left] >= startQ {
   229  		return left
   230  	}
   231  	return -1
   232  }
   233  
   234  func findEndIndex(strL []string, endQ string) int {
   235  	if len(strL) == 0 {
   236  		return -1
   237  	}
   238  
   239  	// Modified binary search to find the very first element <endQ.
   240  	var left, right, mid int
   241  	right = len(strL) - 1
   242  	for left <= right {
   243  		mid = (left + right) >> 1
   244  		midStr := strL[mid]
   245  		if midStr == endQ {
   246  			// Handle condition where there might be multiple values equal to startQ.
   247  			// We are looking for the very first value < midStL, that i+1 will be the first
   248  			// element >= midStr.
   249  			for i := mid - 1; i >= 0; i-- {
   250  				if strL[i] < midStr {
   251  					return i + 1
   252  				}
   253  			}
   254  			return 0
   255  		}
   256  		if midStr < endQ {
   257  			left = mid + 1
   258  		} else { // midStrL > startQ
   259  			right = mid - 1
   260  		}
   261  	}
   262  
   263  	// Binary search failed, now let's find a value less than endQ.
   264  	for i := right; i >= 0; i-- {
   265  		if strL[i] < endQ {
   266  			return i
   267  		}
   268  	}
   269  
   270  	return -1
   271  }
   272  
   273  type sortState int
   274  
   275  const (
   276  	stateUnsorted sortState = iota
   277  	stateAlreadySorted
   278  )
   279  
   280  const minSortSize = 1024
   281  
   282  // Constructs a slice of dirty items, to use w/ memIterator.
   283  func (store *Store) dirtyItems(start, end []byte) {
   284  	startStr, endStr := conv.UnsafeBytesToStr(start), conv.UnsafeBytesToStr(end)
   285  	if startStr > endStr {
   286  		// Nothing to do here.
   287  		return
   288  	}
   289  
   290  	n := len(store.unsortedCache)
   291  	unsorted := make([]*kv.Pair, 0)
   292  	// If the unsortedCache is too big, its costs too much to determine
   293  	// whats in the subset we are concerned about.
   294  	// If you are interleaving iterator calls with writes, this can easily become an
   295  	// O(N^2) overhead.
   296  	// Even without that, too many range checks eventually becomes more expensive
   297  	// than just not having the cache.
   298  	if n < minSortSize {
   299  		for key := range store.unsortedCache {
   300  			if dbm.IsKeyInDomain(conv.UnsafeStrToBytes(key), start, end) {
   301  				cacheValue := store.cache[key]
   302  				unsorted = append(unsorted, &kv.Pair{Key: []byte(key), Value: cacheValue.value})
   303  			}
   304  		}
   305  		store.clearUnsortedCacheSubset(unsorted, stateUnsorted)
   306  		return
   307  	}
   308  
   309  	// Otherwise it is large so perform a modified binary search to find
   310  	// the target ranges for the keys that we should be looking for.
   311  	strL := make([]string, 0, n)
   312  	for key := range store.unsortedCache {
   313  		strL = append(strL, key)
   314  	}
   315  	sort.Strings(strL)
   316  
   317  	startIndex, endIndex := findStartEndIndex(strL, startStr, endStr)
   318  
   319  	// Since we spent cycles to sort the values, we should process and remove a reasonable amount
   320  	// ensure start to end is at least minSortSize in size
   321  	// if below minSortSize, expand it to cover additional values
   322  	// this amortizes the cost of processing elements across multiple calls
   323  	if endIndex-startIndex < minSortSize {
   324  		endIndex = math.MinInt(startIndex+minSortSize, len(strL)-1)
   325  		if endIndex-startIndex < minSortSize {
   326  			startIndex = math.MaxInt(endIndex-minSortSize, 0)
   327  		}
   328  	}
   329  
   330  	kvL := make([]*kv.Pair, 0)
   331  	for i := startIndex; i <= endIndex; i++ {
   332  		key := strL[i]
   333  		cacheValue := store.cache[key]
   334  		kvL = append(kvL, &kv.Pair{Key: []byte(key), Value: cacheValue.value})
   335  	}
   336  
   337  	// kvL was already sorted so pass it in as is.
   338  	store.clearUnsortedCacheSubset(kvL, stateAlreadySorted)
   339  }
   340  
   341  func findStartEndIndex(strL []string, startStr, endStr string) (int, int) {
   342  	// Now find the values within the domain
   343  	//  [start, end)
   344  	startIndex := findStartIndex(strL, startStr)
   345  	endIndex := findEndIndex(strL, endStr)
   346  
   347  	if endIndex < 0 {
   348  		endIndex = len(strL) - 1
   349  	}
   350  	if startIndex < 0 {
   351  		startIndex = 0
   352  	}
   353  	return startIndex, endIndex
   354  }
   355  
   356  func (store *Store) clearUnsortedCacheSubset(unsorted []*kv.Pair, sortState sortState) {
   357  	store.deleteKeysFromUnsortedCache(unsorted)
   358  
   359  	if sortState == stateUnsorted {
   360  		sort.Slice(unsorted, func(i, j int) bool {
   361  			return bytes.Compare(unsorted[i].Key, unsorted[j].Key) < 0
   362  		})
   363  	}
   364  
   365  	for _, item := range unsorted {
   366  		if item.Value == nil {
   367  			// deleted element, tracked by store.deleted
   368  			// setting arbitrary value
   369  			// TODO: Don't ignore this error.
   370  			err := store.sortedCache.Set(item.Key, []byte{})
   371  			if err != nil {
   372  				panic(err)
   373  			}
   374  			continue
   375  		}
   376  		err := store.sortedCache.Set(item.Key, item.Value)
   377  		if err != nil {
   378  			panic(err)
   379  		}
   380  	}
   381  }
   382  
   383  func (store *Store) deleteKeysFromUnsortedCache(unsorted []*kv.Pair) {
   384  	n := len(store.unsortedCache)
   385  	if len(unsorted) == n { // This pattern allows the Go compiler to emit the map clearing idiom for the entire map.
   386  		for key := range store.unsortedCache {
   387  			delete(store.unsortedCache, key)
   388  		}
   389  	} else { // Otherwise, normally delete the unsorted keys from the map.
   390  		for _, kv := range unsorted {
   391  			delete(store.unsortedCache, conv.UnsafeBytesToStr(kv.Key))
   392  		}
   393  	}
   394  }
   395  
   396  //----------------------------------------
   397  // etc
   398  
   399  // Only entrypoint to mutate store.cache.
   400  func (store *Store) setCacheValue(key, value []byte, deleted, dirty bool) {
   401  	types.AssertValidKey(key)
   402  
   403  	keyStr := conv.UnsafeBytesToStr(key)
   404  	store.cache[keyStr] = &cValue{
   405  		value: value,
   406  		dirty: dirty,
   407  	}
   408  	if deleted {
   409  		store.deleted[keyStr] = struct{}{}
   410  	} else {
   411  		delete(store.deleted, keyStr)
   412  	}
   413  	if dirty {
   414  		store.unsortedCache[keyStr] = struct{}{}
   415  	}
   416  }
   417  
   418  func (store *Store) isDeleted(key string) bool {
   419  	_, ok := store.deleted[key]
   420  	return ok
   421  }