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 }