github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/exp/kvutil/model_cache.go (about)

     1  package kvutil
     2  
     3  import (
     4  	"context"
     5  	"encoding"
     6  	"errors"
     7  	"reflect"
     8  	"time"
     9  	"unsafe"
    10  
    11  	"github.com/jxskiss/gopkg/v2/collection/set"
    12  	"github.com/jxskiss/gopkg/v2/easy"
    13  	"github.com/jxskiss/gopkg/v2/internal/linkname"
    14  	"github.com/jxskiss/gopkg/v2/perf/lru"
    15  	"github.com/jxskiss/gopkg/v2/unsafe/reflectx"
    16  )
    17  
    18  // DefaultBatchSize is the default batch size for batch operations.
    19  const DefaultBatchSize = 200
    20  
    21  // DefaultLoaderBatchSize is the default batch size for calling Loader.
    22  const DefaultLoaderBatchSize = 500
    23  
    24  // DefaultLRUExpiration is the default expiration time for data in LRU cache.
    25  const DefaultLRUExpiration = time.Second
    26  
    27  var ErrDataNotFound = errors.New("data not found")
    28  
    29  // Model is the interface implemented by types that can be cached by Cache.
    30  type Model interface {
    31  	encoding.BinaryMarshaler
    32  	encoding.BinaryUnmarshaler
    33  }
    34  
    35  // KVPair represents a key value pair to work with Cache.
    36  type KVPair struct {
    37  	K string
    38  	V []byte
    39  }
    40  
    41  // Storage is the interface which provides storage for Cache.
    42  // Users may use any key-value storage to implement this.
    43  type Storage interface {
    44  	MGet(ctx context.Context, keys ...string) ([][]byte, error)
    45  	MSet(ctx context.Context, kvPairs []KVPair, expiration time.Duration) error
    46  	Delete(ctx context.Context, keys ...string) error
    47  }
    48  
    49  // Loader loads data from underlying persistent storage.
    50  type Loader[K comparable, V Model] func(ctx context.Context, pks []K) (map[K]V, error)
    51  
    52  // CacheConfig configures a Cache instance.
    53  type CacheConfig[K comparable, V Model] struct {
    54  
    55  	// Storage must return a Storage implementation which will be used
    56  	// as the underlying key-value storage.
    57  	Storage func(ctx context.Context) Storage
    58  
    59  	// IDFunc returns the primary key of a Model object.
    60  	IDFunc func(V) K
    61  
    62  	// KeyFunc specifies the key function to use with the storage.
    63  	KeyFunc Key
    64  
    65  	// MGetBatchSize optionally specifies the batch size for one MGet
    66  	// calling to storage. The default is 200.
    67  	MGetBatchSize int
    68  
    69  	// MSetBatchSize optionally specifies the batch size for one MSet
    70  	// calling to storage. The default is 200.
    71  	MSetBatchSize int
    72  
    73  	// DeleteBatchSize optionally specifies the batch size for one Delete
    74  	// calling to storage. The default is 200.
    75  	DeleteBatchSize int
    76  
    77  	// LRUCache optionally enables LRU cache, which may help to improve
    78  	// the performance for high concurrency use-case.
    79  	LRUCache lru.Interface[K, V]
    80  
    81  	// LRUExpiration specifies the expiration time for data in LRU cache.
    82  	// The default is one second.
    83  	LRUExpiration time.Duration
    84  
    85  	// Loader optionally specifies a function to load data from underlying
    86  	// persistent storage when the data is missing from cache.
    87  	Loader Loader[K, V]
    88  
    89  	// LoaderBatchSize optionally specifies the batch size for calling
    90  	// Loader. The default is 500.
    91  	LoaderBatchSize int
    92  
    93  	// CacheExpiration specifies the expiration time to cache the data to
    94  	// Storage, when Loader is configured and data are loaded by Loader.
    95  	// The default is zero, which means no expiration.
    96  	CacheExpiration time.Duration
    97  
    98  	// CacheLoaderResultAsync makes the Cache to save data from Loader
    99  	// to Storage async, errors returned from Storage.MSet will be ignored.
   100  	// The default is false, it reports errors to the caller.
   101  	CacheLoaderResultAsync bool
   102  }
   103  
   104  func (p *CacheConfig[_, _]) checkAndSetDefaults() {
   105  	if p.MGetBatchSize <= 0 {
   106  		p.MGetBatchSize = DefaultBatchSize
   107  	}
   108  	if p.MSetBatchSize <= 0 {
   109  		p.MSetBatchSize = DefaultBatchSize
   110  	}
   111  	if p.DeleteBatchSize <= 0 {
   112  		p.DeleteBatchSize = DefaultBatchSize
   113  	}
   114  	if p.LRUExpiration <= 0 {
   115  		p.LRUExpiration = DefaultLRUExpiration
   116  	}
   117  	if p.LoaderBatchSize <= 0 {
   118  		p.LoaderBatchSize = DefaultLoaderBatchSize
   119  	}
   120  }
   121  
   122  func buildNewElemFunc[V any]() func() V {
   123  	var x V
   124  	typ := reflectx.RTypeOf(x)
   125  	if typ.Kind() == reflect.Ptr {
   126  		valTyp := typ.Elem()
   127  		return func() V {
   128  			elem := linkname.Reflect_unsafe_New(unsafe.Pointer(valTyp))
   129  			return typ.PackInterface(elem).(V)
   130  		}
   131  	}
   132  	return func() V {
   133  		return *new(V)
   134  	}
   135  }
   136  
   137  // NewCache returns a new Cache instance.
   138  func NewCache[K comparable, V Model](config *CacheConfig[K, V]) *Cache[K, V] {
   139  	config.checkAndSetDefaults()
   140  	newElemFn := buildNewElemFunc[V]()
   141  	return &Cache[K, V]{
   142  		config:      config,
   143  		newElemFunc: newElemFn,
   144  	}
   145  }
   146  
   147  // Cache encapsulates frequently used batching cache operations,
   148  // such as MGet, MSet and Delete.
   149  //
   150  // A Cache must not be copied after initialized.
   151  type Cache[K comparable, V Model] struct {
   152  	config *CacheConfig[K, V]
   153  
   154  	newElemFunc func() V
   155  }
   156  
   157  // Get queries Cache for a given pk.
   158  //
   159  // If pk cannot be found either in the cache nor from the Loader,
   160  // it returns an error ErrDataNotFound.
   161  func (p *Cache[K, V]) Get(ctx context.Context, pk K) (V, error) {
   162  	if p.config.LRUCache != nil {
   163  		val, exists := p.config.LRUCache.GetNotStale(pk)
   164  		if exists {
   165  			return val, nil
   166  		}
   167  	}
   168  
   169  	var zeroVal V
   170  	stor := p.config.Storage(ctx)
   171  	key := p.config.KeyFunc(pk)
   172  	cacheResult, err := stor.MGet(ctx, key)
   173  	if err != nil && p.config.Loader == nil {
   174  		return zeroVal, err
   175  	}
   176  	if len(cacheResult) > 0 && len(cacheResult[0]) > 0 {
   177  		elem := p.newElemFunc()
   178  		err = elem.UnmarshalBinary(cacheResult[0])
   179  		if err != nil {
   180  			return zeroVal, err
   181  		}
   182  		if p.config.LRUCache != nil {
   183  			p.config.LRUCache.Set(pk, elem, p.config.LRUExpiration)
   184  		}
   185  		return elem, nil
   186  	}
   187  	if p.config.Loader != nil {
   188  		var loaderResult map[K]V
   189  		loaderResult, err = p.config.Loader(ctx, []K{pk})
   190  		if err != nil {
   191  			return zeroVal, err
   192  		}
   193  		elem, exists := loaderResult[pk]
   194  		if exists {
   195  			if p.config.CacheLoaderResultAsync {
   196  				go func() {
   197  					_ = p.Set(ctx, pk, elem, p.config.CacheExpiration)
   198  				}()
   199  			} else {
   200  				err = p.Set(ctx, pk, elem, p.config.CacheExpiration)
   201  				if err != nil {
   202  					return zeroVal, err
   203  				}
   204  			}
   205  			return elem, nil
   206  		}
   207  	}
   208  	return zeroVal, ErrDataNotFound
   209  }
   210  
   211  // MGetSlice queries Cache and returns the cached values as a slice
   212  // of type []V.
   213  func (p *Cache[K, V]) MGetSlice(ctx context.Context, pks []K) ([]V, error) {
   214  	if len(pks) == 0 {
   215  		return nil, nil
   216  	}
   217  
   218  	// pk 去重
   219  	pks = easy.Unique(pks, false)
   220  
   221  	out := make([]V, 0, len(pks))
   222  	valfunc := func(pk K, elem V) {
   223  		out = append(out, elem)
   224  	}
   225  	err := p.mget(ctx, pks, valfunc)
   226  	return out, err
   227  }
   228  
   229  // MGetMap queries Cache and returns the cached values as a map
   230  // of type map[K]V.
   231  func (p *Cache[K, V]) MGetMap(ctx context.Context, pks []K) (map[K]V, error) {
   232  	if len(pks) == 0 {
   233  		return nil, nil
   234  	}
   235  
   236  	// pk 去重
   237  	pks = easy.Unique(pks, false)
   238  
   239  	out := make(map[K]V, len(pks))
   240  	valfunc := func(pk K, elem V) {
   241  		out[pk] = elem
   242  	}
   243  	err := p.mget(ctx, pks, valfunc)
   244  	return out, err
   245  }
   246  
   247  func (p *Cache[K, V]) mget(ctx context.Context, pks []K, f func(pk K, elem V)) error {
   248  	var lruMissingPKs []K
   249  	var lruMissingKeys []string
   250  	if p.config.LRUCache != nil {
   251  		lruResult := p.config.LRUCache.MGetNotStale(pks...)
   252  		lruMissingKeys = make([]string, 0, len(pks)-len(lruResult))
   253  		for _, pk := range pks {
   254  			if elem, ok := lruResult[pk]; ok {
   255  				f(pk, elem)
   256  			} else {
   257  				key := p.config.KeyFunc(pk)
   258  				lruMissingPKs = append(lruMissingPKs, pk)
   259  				lruMissingKeys = append(lruMissingKeys, key)
   260  			}
   261  		}
   262  	} else {
   263  		lruMissingPKs = pks
   264  		lruMissingKeys = make([]string, len(pks))
   265  		for i, pk := range pks {
   266  			key := p.config.KeyFunc(pk)
   267  			lruMissingKeys[i] = key
   268  		}
   269  	}
   270  
   271  	stor := p.config.Storage(ctx)
   272  
   273  	var err error
   274  	var batchValues [][]byte
   275  	var fromCache map[K]V
   276  	if p.config.LRUCache != nil {
   277  		fromCache = make(map[K]V, len(lruMissingKeys))
   278  	}
   279  	var cachedPKs = set.NewWithSize[K](len(lruMissingKeys))
   280  	var batchKeys = easy.Split(lruMissingKeys, p.config.MGetBatchSize)
   281  	for _, bat := range batchKeys {
   282  		batchValues, err = stor.MGet(ctx, bat...)
   283  		if err != nil {
   284  			return err
   285  		}
   286  		for _, val := range batchValues {
   287  			if len(val) == 0 {
   288  				continue
   289  			}
   290  			elem := p.newElemFunc()
   291  			err = elem.UnmarshalBinary(val)
   292  			if err != nil {
   293  				return err
   294  			}
   295  			pk := p.config.IDFunc(elem)
   296  			if fromCache != nil {
   297  				fromCache[pk] = elem
   298  			}
   299  			f(pk, elem)
   300  			cachedPKs.Add(pk)
   301  		}
   302  	}
   303  
   304  	// load from underlying persistent storage if configured
   305  	var fromLoader map[K]V
   306  	if p.config.Loader != nil && cachedPKs.Size() < len(lruMissingPKs) {
   307  		cacheMissingPKs := cachedPKs.FilterNotContains(lruMissingPKs)
   308  		fromLoader = make(map[K]V, len(cacheMissingPKs))
   309  		batchPKs := easy.Split(cacheMissingPKs, p.config.LoaderBatchSize)
   310  		for _, bat := range batchPKs {
   311  			batchResult, err := p.config.Loader(ctx, bat)
   312  			if err != nil {
   313  				return err
   314  			}
   315  			for pk, elem := range batchResult {
   316  				f(pk, elem)
   317  				fromLoader[pk] = elem
   318  			}
   319  		}
   320  	}
   321  
   322  	if len(fromCache) > 0 {
   323  		p.config.LRUCache.MSet(fromCache, p.config.LRUExpiration)
   324  	}
   325  	if len(fromLoader) > 0 {
   326  		if p.config.CacheLoaderResultAsync {
   327  			go func() {
   328  				_ = p.MSetMap(ctx, fromLoader, p.config.CacheExpiration)
   329  			}()
   330  		} else {
   331  			err = p.MSetMap(ctx, fromLoader, p.config.CacheExpiration)
   332  			if err != nil {
   333  				return err
   334  			}
   335  		}
   336  	}
   337  
   338  	return nil
   339  }
   340  
   341  // Set writes a key value pair to Cache.
   342  func (p *Cache[K, V]) Set(ctx context.Context, pk K, elem V, expiration time.Duration) error {
   343  	key := p.config.KeyFunc(pk)
   344  	buf, err := elem.MarshalBinary()
   345  	if err != nil {
   346  		return err
   347  	}
   348  	kvPairs := []KVPair{{K: key, V: buf}}
   349  	stor := p.config.Storage(ctx)
   350  	err = stor.MSet(ctx, kvPairs, expiration)
   351  	if err != nil {
   352  		return err
   353  	}
   354  	if p.config.LRUCache != nil {
   355  		p.config.LRUCache.Set(pk, elem, p.config.LRUExpiration)
   356  	}
   357  	return nil
   358  }
   359  
   360  // MSetSlice writes the given models to Cache.
   361  func (p *Cache[K, V]) MSetSlice(ctx context.Context, models []V, expiration time.Duration) error {
   362  	if len(models) == 0 {
   363  		return nil
   364  	}
   365  
   366  	stor := p.config.Storage(ctx)
   367  	batchSize := min(p.config.MSetBatchSize, len(models))
   368  	kvPairs := make([]KVPair, 0, batchSize)
   369  	for _, batchModels := range easy.Split(models, batchSize) {
   370  		kvPairs = kvPairs[:0]
   371  		var kvMap map[K]V
   372  		if p.config.LRUCache != nil {
   373  			kvMap = make(map[K]V, len(batchModels))
   374  		}
   375  		for _, elem := range batchModels {
   376  			buf, err := elem.MarshalBinary()
   377  			if err != nil {
   378  				return err
   379  			}
   380  			pk := p.config.IDFunc(elem)
   381  			key := p.config.KeyFunc(pk)
   382  			kvPairs = append(kvPairs, KVPair{key, buf})
   383  			if p.config.LRUCache != nil {
   384  				kvMap[pk] = elem
   385  			}
   386  		}
   387  		err := stor.MSet(ctx, kvPairs, expiration)
   388  		if err != nil {
   389  			return err
   390  		}
   391  		if p.config.LRUCache != nil {
   392  			p.config.LRUCache.MSet(kvMap, p.config.LRUExpiration)
   393  		}
   394  	}
   395  	return nil
   396  }
   397  
   398  // MSetMap writes the given models to Cache.
   399  func (p *Cache[K, V]) MSetMap(ctx context.Context, models map[K]V, expiration time.Duration) error {
   400  	if len(models) == 0 {
   401  		return nil
   402  	}
   403  
   404  	stor := p.config.Storage(ctx)
   405  	batchSize := min(p.config.MSetBatchSize, len(models))
   406  	kvPairs := make([]KVPair, 0, batchSize)
   407  	for pk, elem := range models {
   408  		buf, err := elem.MarshalBinary()
   409  		if err != nil {
   410  			return err
   411  		}
   412  		key := p.config.KeyFunc(pk)
   413  		kvPairs = append(kvPairs, KVPair{key, buf})
   414  		if len(kvPairs) == batchSize {
   415  			err = stor.MSet(ctx, kvPairs, expiration)
   416  			if err != nil {
   417  				return err
   418  			}
   419  			kvPairs = kvPairs[:0]
   420  		}
   421  	}
   422  	if len(kvPairs) > 0 {
   423  		err := stor.MSet(ctx, kvPairs, expiration)
   424  		if err != nil {
   425  			return err
   426  		}
   427  	}
   428  	if p.config.LRUCache != nil {
   429  		p.config.LRUCache.MSet(models, p.config.LRUExpiration)
   430  	}
   431  	return nil
   432  }
   433  
   434  // Delete deletes key values from Cache.
   435  func (p *Cache[K, V]) Delete(ctx context.Context, pks ...K) error {
   436  	if len(pks) == 0 {
   437  		return nil
   438  	}
   439  	if len(pks) > 1 {
   440  		return p.mDelete(ctx, pks)
   441  	}
   442  
   443  	pk := pks[0]
   444  	key := p.config.KeyFunc(pk)
   445  	stor := p.config.Storage(ctx)
   446  	err := stor.Delete(ctx, key)
   447  	if err != nil {
   448  		return err
   449  	}
   450  	if p.config.LRUCache != nil {
   451  		p.config.LRUCache.Delete(pk)
   452  	}
   453  	return nil
   454  }
   455  
   456  // mDelete deletes multiple key values from Cache.
   457  func (p *Cache[K, V]) mDelete(ctx context.Context, pks []K) error {
   458  	if len(pks) == 0 {
   459  		return nil
   460  	}
   461  
   462  	keys := make([]string, 0, len(pks))
   463  	for _, pk := range pks {
   464  		key := p.config.KeyFunc(pk)
   465  		keys = append(keys, key)
   466  	}
   467  
   468  	stor := p.config.Storage(ctx)
   469  	batches := easy.Split(keys, p.config.DeleteBatchSize)
   470  	for _, bat := range batches {
   471  		err := stor.Delete(ctx, bat...)
   472  		if err != nil {
   473  			return err
   474  		}
   475  	}
   476  	if p.config.LRUCache != nil {
   477  		p.config.LRUCache.MDelete(pks...)
   478  	}
   479  	return nil
   480  }
   481  
   482  func min(a, b int) int {
   483  	if a < b {
   484  		return a
   485  	}
   486  	return b
   487  }