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 }