github.com/iotexproject/iotex-core@v1.14.1-rc1/db/kvstorewithcache.go (about)

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"sync"
     7  
     8  	"github.com/iotexproject/go-pkgs/cache"
     9  	"github.com/iotexproject/iotex-core/db/batch"
    10  	"github.com/iotexproject/iotex-core/pkg/log"
    11  )
    12  
    13  // kvStoreWithCache is an implementation of KVStore, wrapping kvstore with LRU caches of latest states
    14  type kvStoreWithCache struct {
    15  	mutex       sync.RWMutex // lock for stateCaches
    16  	store       KVStore
    17  	stateCaches map[string]cache.LRUCache // map having lru cache to store current states for speed up
    18  	cacheSize   int
    19  }
    20  
    21  // NewKvStoreWithCache wraps kvstore with stateCaches
    22  func NewKvStoreWithCache(kvstore KVStore, cacheSize int) KVStore {
    23  	return &kvStoreWithCache{
    24  		store:       kvstore,
    25  		stateCaches: make(map[string]cache.LRUCache),
    26  		cacheSize:   cacheSize,
    27  	}
    28  }
    29  
    30  // Start starts the kvStoreWithCache
    31  func (kvc *kvStoreWithCache) Start(ctx context.Context) error {
    32  	return kvc.store.Start(ctx)
    33  }
    34  
    35  // Stop stops the kvStoreWithCache
    36  func (kvc *kvStoreWithCache) Stop(ctx context.Context) error {
    37  	kvc.mutex.Lock()
    38  	defer kvc.mutex.Unlock()
    39  	for _, sc := range kvc.stateCaches {
    40  		sc.Clear()
    41  	}
    42  	return kvc.store.Stop(ctx)
    43  }
    44  
    45  // Put inserts a <key, value> record into stateCaches and kvstore
    46  func (kvc *kvStoreWithCache) Put(namespace string, key, value []byte) (err error) {
    47  	if err := kvc.store.Put(namespace, key, value); err != nil {
    48  		return err
    49  	}
    50  	kvc.updateStateCachesIfExists(namespace, key, value)
    51  	return nil
    52  }
    53  
    54  // Get retrieves a <key, value> record from stateCaches, and if not exists, retrieves from kvstore
    55  func (kvc *kvStoreWithCache) Get(namespace string, key []byte) ([]byte, error) {
    56  	if cachedData, isExist := kvc.getStateCaches(namespace, key); isExist {
    57  		return cachedData, nil
    58  	}
    59  	kvStoreData, err := kvc.store.Get(namespace, key)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	// in case of read-miss, put into statecaches
    64  	kvc.putStateCaches(namespace, key, kvStoreData)
    65  	return kvStoreData, nil
    66  }
    67  
    68  // Filter returns <k, v> pair in a bucket that meet the condition
    69  func (kvc *kvStoreWithCache) Filter(namespace string, cond Condition, minKey, maxKey []byte) ([][]byte, [][]byte, error) {
    70  	return kvc.store.Filter(namespace, cond, minKey, maxKey)
    71  }
    72  
    73  // Delete deletes a record from statecaches if exists, and from kvstore
    74  func (kvc *kvStoreWithCache) Delete(namespace string, key []byte) (err error) {
    75  	if err := kvc.store.Delete(namespace, key); err != nil {
    76  		return err
    77  	}
    78  	kvc.deleteStateCaches(namespace, key)
    79  	return nil
    80  }
    81  
    82  // WriteBatch commits a batch into stateCaches and kvstore
    83  func (kvc *kvStoreWithCache) WriteBatch(kvsb batch.KVStoreBatch) (err error) {
    84  	if err := kvc.store.WriteBatch(kvsb); err != nil {
    85  		return err
    86  	}
    87  	kvsb.Lock()
    88  	defer kvsb.ClearAndUnlock()
    89  	for i := 0; i < kvsb.Size(); i++ {
    90  		write, e := kvsb.Entry(i)
    91  		if e != nil {
    92  			return e
    93  		}
    94  		ns := write.Namespace()
    95  		switch write.WriteType() {
    96  		case batch.Put:
    97  			kvc.updateStateCachesIfExists(ns, write.Key(), write.Value())
    98  		case batch.Delete:
    99  			kvc.deleteStateCaches(ns, write.Key())
   100  		}
   101  	}
   102  	return nil
   103  }
   104  
   105  // ======================================
   106  // private functions
   107  // ======================================
   108  
   109  // store on stateCaches
   110  func (kvc *kvStoreWithCache) putStateCaches(namespace string, key, value []byte) {
   111  	kvc.mutex.Lock()
   112  	defer kvc.mutex.Unlock()
   113  	if sc, ok := kvc.stateCaches[namespace]; ok {
   114  		sc.Add(hex.EncodeToString(key), value)
   115  	} else {
   116  		sc := cache.NewThreadSafeLruCache(kvc.cacheSize)
   117  		sc.Add(hex.EncodeToString(key), value)
   118  		kvc.stateCaches[namespace] = sc
   119  	}
   120  }
   121  
   122  // update on stateCaches if the key exists
   123  func (kvc *kvStoreWithCache) updateStateCachesIfExists(namespace string, key, value []byte) {
   124  	kvc.mutex.Lock()
   125  	defer kvc.mutex.Unlock()
   126  	if sc, ok := kvc.stateCaches[namespace]; ok {
   127  		if _, ok := sc.Get(hex.EncodeToString(key)); ok {
   128  			sc.Add(hex.EncodeToString(key), value)
   129  		}
   130  	}
   131  }
   132  
   133  // remove on stateCaches if the key exists
   134  func (kvc *kvStoreWithCache) deleteStateCaches(namespace string, key []byte) {
   135  	kvc.mutex.Lock()
   136  	defer kvc.mutex.Unlock()
   137  	if sc, ok := kvc.stateCaches[namespace]; ok {
   138  		sc.Remove(hex.EncodeToString(key))
   139  	}
   140  }
   141  
   142  // look up stateCachces
   143  func (kvc *kvStoreWithCache) getStateCaches(namespace string, key []byte) ([]byte, bool) {
   144  	kvc.mutex.RLock()
   145  	defer kvc.mutex.RUnlock()
   146  	if sc, ok := kvc.stateCaches[namespace]; ok {
   147  		if data, ok := sc.Get(hex.EncodeToString(key)); ok {
   148  			if byteData, ok := data.([]byte); ok {
   149  				return byteData, true
   150  			}
   151  			log.L().Fatal("failed to convert interface{} to bytes from stateCaches")
   152  			return nil, true
   153  		}
   154  	}
   155  	return nil, false
   156  }