github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/exp/kvutil/model_cache_test.go (about) 1 package kvutil 2 3 import ( 4 "context" 5 "strconv" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 11 "github.com/jxskiss/gopkg/v2/easy" 12 "github.com/jxskiss/gopkg/v2/perf/lru" 13 ) 14 15 var ( 16 testModelList = []*TestModel{ 17 { 18 IntId: 111, 19 StrId: "aaa", 20 }, 21 { 22 IntId: 112, 23 StrId: "aab", 24 }, 25 } 26 testModelMapInt = map[int64]*TestModel{ 27 113: { 28 IntId: 113, 29 StrId: "aac", 30 }, 31 } 32 testModelMapStr = map[string]*TestModel{ 33 "aac": { 34 IntId: 113, 35 StrId: "aac", 36 }, 37 } 38 testIntIds = []int64{111, 112, 113} 39 testStrIds = []string{"aaa", "aab", "aac"} 40 testDeleteIntIds = []int64{111, 112} 41 testDeleteStrIds = []string{"aab", "aac"} 42 ) 43 44 func TestCache(t *testing.T) { 45 mcInt := makeTestingCache("TestIntCache", 46 func(m *TestModel) int64 { 47 return m.IntId 48 }) 49 mcStr := makeTestingCache("TestStrCache", 50 func(m *TestModel) string { 51 return m.StrId 52 }) 53 54 ctx := context.Background() 55 var modelList []*TestModel 56 var modelIntMap map[int64]*TestModel 57 var modelStrMap map[string]*TestModel 58 var err error 59 60 modelList, err = mcInt.MGetSlice(ctx, testIntIds) 61 assert.Nil(t, err) 62 assert.Len(t, modelList, 0) 63 64 modelList, err = mcStr.MGetSlice(ctx, testStrIds) 65 assert.Nil(t, err) 66 assert.Len(t, modelList, 0) 67 68 modelIntMap, err = mcInt.MGetMap(ctx, testIntIds) 69 assert.Nil(t, err) 70 assert.Len(t, modelIntMap, 0) 71 72 modelStrMap, err = mcStr.MGetMap(ctx, testStrIds) 73 assert.Nil(t, err) 74 assert.Len(t, modelStrMap, 0) 75 76 // we can populate cache using either a list or a map 77 err = mcInt.MSetSlice(ctx, testModelList, 0) 78 assert.Nil(t, err) 79 err = mcInt.MSetMap(ctx, testModelMapInt, 0) 80 assert.Nil(t, err) 81 82 err = mcStr.MSetSlice(ctx, testModelList, 0) 83 assert.Nil(t, err) 84 err = mcStr.MSetMap(ctx, testModelMapStr, 0) 85 assert.Nil(t, err) 86 87 modelList, err = mcInt.MGetSlice(ctx, testIntIds) 88 assert.Nil(t, err) 89 assert.Len(t, modelList, 3) 90 91 modelList, err = mcStr.MGetSlice(ctx, testStrIds) 92 assert.Nil(t, err) 93 assert.Len(t, modelList, 3) 94 95 modelIntMap, err = mcInt.MGetMap(ctx, testIntIds) 96 assert.Nil(t, err) 97 assert.Len(t, modelIntMap, 3) 98 99 modelStrMap, err = mcStr.MGetMap(ctx, testStrIds) 100 assert.Nil(t, err) 101 assert.Len(t, modelStrMap, 3) 102 103 err = mcInt.Delete(ctx, testDeleteIntIds...) 104 assert.Nil(t, err) 105 assert.Len(t, mcInt.config.Storage(ctx).(*memoryStorage).data, 1) 106 107 err = mcStr.Delete(ctx, testDeleteStrIds...) 108 assert.Nil(t, err) 109 assert.Len(t, mcStr.config.Storage(ctx).(*memoryStorage).data, 1) 110 } 111 112 func TestCacheWithLRUCache(t *testing.T) { 113 mc := makeTestingCache("TestCacheWithLRUCache", 114 func(m *TestModel) int64 { 115 return m.IntId 116 }) 117 mc.config.LRUCache = lru.NewCache[int64, *TestModel](10) 118 mc.config.LRUExpiration = time.Minute 119 120 ctx := context.Background() 121 var modelList []*TestModel 122 var modelMap map[int64]*TestModel 123 var err error 124 125 modelList, err = mc.MGetSlice(ctx, testIntIds) 126 assert.Nil(t, err) 127 assert.Len(t, modelList, 0) 128 129 err = mc.MSetSlice(ctx, testModelList, 0) 130 assert.Nil(t, err) 131 assert.Len(t, mc.config.Storage(ctx).(*memoryStorage).data, 2) 132 133 got1, exists1, expired1 := mc.config.LRUCache.Get(111) 134 assert.NotNil(t, got1) 135 assert.True(t, exists1 && !expired1) 136 137 got2, exists2, expired2 := mc.config.LRUCache.Get(112) 138 assert.NotNil(t, got2) 139 assert.True(t, exists2 && !expired2) 140 141 got3, exists3, expired3 := mc.config.LRUCache.Get(113) 142 assert.Nil(t, got3) 143 assert.False(t, exists3 || expired3) 144 145 modelList, err = mc.MGetSlice(ctx, testIntIds) 146 assert.Nil(t, err) 147 assert.Len(t, modelList, 2) 148 149 modelMap, err = mc.MGetMap(ctx, testIntIds) 150 assert.Nil(t, err) 151 assert.Len(t, modelMap, 2) 152 153 err = mc.Delete(ctx, testIntIds...) 154 assert.Nil(t, err) 155 assert.Len(t, mc.config.Storage(ctx).(*memoryStorage).data, 0) 156 got1, exists1, expired1 = mc.config.LRUCache.Get(111) 157 assert.Nil(t, got1) 158 assert.False(t, exists1 || expired1) 159 } 160 161 func TestCacheWithLoader(t *testing.T) { 162 mc := makeTestingCache("TestCacheWithLoader", 163 func(m *TestModel) int64 { 164 return m.IntId 165 }) 166 mc.config.LRUCache = lru.NewShardedCache[int64, *TestModel](4, 30) 167 mc.config.LRUExpiration = time.Second 168 mc.config.Loader = testLoaderFunc 169 mc.config.CacheExpiration = time.Hour 170 171 ctx := context.Background() 172 var modelList []*TestModel 173 var modelMap map[int64]*TestModel 174 var err error 175 176 modelList, err = mc.MGetSlice(ctx, []int64{111, 112, 113}) 177 assert.Nil(t, err) 178 assert.Len(t, modelList, 2) 179 assert.Equal(t, 2, mc.config.LRUCache.Len()) 180 _, exists := mc.config.LRUCache.GetNotStale(int64(111)) 181 assert.True(t, exists) 182 _, exists = mc.config.LRUCache.GetNotStale(int64(112)) 183 assert.False(t, exists) 184 _, exists = mc.config.LRUCache.GetNotStale(int64(113)) 185 assert.True(t, exists) 186 187 time.Sleep(mc.config.LRUExpiration) 188 _, exists = mc.config.LRUCache.GetNotStale(int64(111)) 189 assert.False(t, exists) 190 _, exists = mc.config.LRUCache.GetNotStale(int64(112)) 191 assert.False(t, exists) 192 _, exists = mc.config.LRUCache.GetNotStale(int64(113)) 193 assert.False(t, exists) 194 195 moreIntIds := []int64{111, 112, 113, 114, 115, 116, 117} 196 modelMap, err = mc.MGetMap(ctx, moreIntIds) 197 assert.Nil(t, err) 198 assert.Len(t, modelMap, 4) 199 assert.Equal(t, 4, mc.config.LRUCache.Len()) 200 assert.ElementsMatch(t, []int64{111, 113, 115, 117}, easy.Keys(modelMap, nil)) 201 202 cacheKeys := make([]string, len(moreIntIds)) 203 for i, id := range moreIntIds { 204 cacheKeys[i] = mc.config.KeyFunc(id) 205 } 206 fromCache, err := mc.config.Storage(ctx).MGet(ctx, cacheKeys...) 207 assert.Nil(t, err) 208 assert.Len(t, fromCache, len(cacheKeys)) 209 valid := easy.Filter(func(_ int, elem []byte) bool { return len(elem) > 0 }, fromCache) 210 assert.Len(t, valid, 4) 211 } 212 213 func TestCacheSingleKeyValue(t *testing.T) { 214 mc := makeTestingCache("TestCacheSingleKeyValue", 215 func(m *TestModel) int64 { 216 return m.IntId 217 }) 218 mc.config.LRUCache = lru.NewCache[int64, *TestModel](5) 219 mc.config.LRUExpiration = time.Second 220 mc.config.Loader = testLoaderFunc 221 mc.config.CacheExpiration = time.Hour 222 223 ctx := context.Background() 224 val111, err := mc.Get(ctx, 111) 225 assert.Nil(t, err) 226 assert.Equal(t, int64(111), val111.IntId) 227 _, exists := mc.config.LRUCache.GetNotStale(111) 228 assert.True(t, exists) 229 230 mc.config.LRUCache.Delete(111) 231 val111, err = mc.Get(ctx, 111) 232 assert.Nil(t, err) 233 assert.Equal(t, int64(111), val111.IntId) 234 _, exists = mc.config.LRUCache.GetNotStale(111) 235 assert.True(t, exists) 236 237 val112, err := mc.Get(ctx, 112) 238 assert.Equal(t, ErrDataNotFound, err) 239 assert.Nil(t, val112) 240 _, exists = mc.config.LRUCache.GetNotStale(112) 241 assert.False(t, exists) 242 243 err = mc.Delete(ctx, 111) 244 assert.Nil(t, err) 245 _, exists = mc.config.LRUCache.GetNotStale(111) 246 assert.False(t, exists) 247 } 248 249 func makeTestingCache[K comparable, V Model](testName string, idFunc func(V) K) *Cache[K, V] { 250 kf := KeyFactory{} 251 return NewCache(&CacheConfig[K, V]{ 252 Storage: testClientFunc(testName), 253 IDFunc: idFunc, 254 KeyFunc: kf.NewKey("test_model:{id}"), 255 MGetBatchSize: 2, 256 MSetBatchSize: 2, 257 DeleteBatchSize: 2, 258 }) 259 } 260 261 func testClientFunc(testName string) func(ctx context.Context) Storage { 262 data := make(map[string][]byte) 263 return func(ctx context.Context) Storage { 264 return &memoryStorage{data: data} 265 } 266 } 267 268 type memoryStorage struct { 269 data map[string][]byte 270 } 271 272 func (m *memoryStorage) MGet(ctx context.Context, keys ...string) ([][]byte, error) { 273 out := make([][]byte, 0, len(keys)) 274 for _, k := range keys { 275 out = append(out, m.data[k]) 276 } 277 return out, nil 278 } 279 280 func (m *memoryStorage) MSet(ctx context.Context, kvPairs []KVPair, expiration time.Duration) error { 281 for _, kv := range kvPairs { 282 m.data[kv.K] = kv.V 283 } 284 return nil 285 } 286 287 func (m *memoryStorage) Delete(ctx context.Context, keys ...string) error { 288 for _, k := range keys { 289 delete(m.data, k) 290 } 291 return nil 292 } 293 294 func clearMemoryStorage(ctx context.Context, cliFunc func(ctx context.Context) Storage) { 295 stor := cliFunc(ctx).(*memoryStorage) 296 stor.data = make(map[string][]byte) 297 } 298 299 func getMemoryStorage(ctx context.Context, cliFunc func(ctx context.Context) Storage) *memoryStorage { 300 stor := cliFunc(ctx).(*memoryStorage) 301 return stor 302 } 303 304 type TestModel struct { 305 IntId int64 306 StrId string 307 } 308 309 func (m *TestModel) MarshalBinary() ([]byte, error) { 310 var buf []byte 311 buf = append(buf, []byte(strconv.FormatInt(m.IntId, 10))...) 312 buf = append(buf, []byte(m.StrId)...) 313 return buf, nil 314 } 315 316 func (m *TestModel) UnmarshalBinary(b []byte) error { 317 m.IntId, _ = strconv.ParseInt(string(b[:3]), 10, 64) 318 m.StrId = string(b[3:6]) 319 return nil 320 } 321 322 func testLoaderFunc(ctx context.Context, ids []int64) (map[int64]*TestModel, error) { 323 out := make(map[int64]*TestModel, len(ids)) 324 for _, id := range ids { 325 if id%2 == 0 { 326 continue 327 } 328 out[id] = &TestModel{ 329 IntId: id, 330 StrId: strconv.FormatInt(id, 10), 331 } 332 } 333 return out, nil 334 }