go.mercari.io/datastore@v1.8.2/dsmiddleware/storagecache/storagecache.go (about) 1 package storagecache 2 3 import ( 4 "context" 5 "sync" 6 7 "go.mercari.io/datastore" 8 ) 9 10 var _ datastore.Middleware = &cacheHandler{} 11 12 // New storage cache (interface) middleware creates & returns. 13 func New(s Storage, opts *Options) datastore.Middleware { 14 ch := &cacheHandler{ 15 s: s, 16 } 17 if opts != nil { 18 ch.logf = opts.Logf 19 ch.filters = opts.Filters 20 } 21 22 if ch.logf == nil { 23 ch.logf = func(ctx context.Context, format string, args ...interface{}) {} 24 } 25 26 return ch 27 } 28 29 // Options provides common option values for storage. 30 type Options struct { 31 Logf func(ctx context.Context, format string, args ...interface{}) 32 Filters []KeyFilter 33 } 34 35 // Storage is the abstraction of storage that holds the cache data. 36 type Storage interface { 37 SetMulti(ctx context.Context, is []*CacheItem) error 38 // GetMulti returns slice of CacheItem of the same length as Keys of the argument. 39 // If not in the dsmiddleware, the value of the corresponding element is nil. 40 GetMulti(ctx context.Context, keys []datastore.Key) ([]*CacheItem, error) 41 DeleteMulti(ctx context.Context, keys []datastore.Key) error 42 } 43 44 // KeyFilter represents a function that determines if the specified Key should be cached. 45 type KeyFilter func(ctx context.Context, key datastore.Key) bool 46 47 type contextTx struct{} 48 49 // CacheItem is serialized by Storage. 50 type CacheItem struct { 51 Key datastore.Key 52 PropertyList datastore.PropertyList 53 } 54 55 // txOps represents the type of operation in the transaction. 56 type txOps int 57 58 const ( 59 // txPutOp represents the put operation in the transaction. 60 txPutOp txOps = iota 61 // txGetOp represents the get operation in the transaction. 62 txGetOp 63 // txDeleteOp represents the delete operation in the transaction. 64 txDeleteOp 65 ) 66 67 // txOpLog is a log of operations within a transaction. 68 type txOpLog struct { 69 Ops txOps 70 Key datastore.Key 71 PendingKey datastore.PendingKey 72 PropertyList datastore.PropertyList 73 } 74 75 type cacheHandler struct { 76 s Storage 77 m sync.Mutex 78 logf func(ctx context.Context, format string, args ...interface{}) 79 filters []KeyFilter 80 } 81 82 func (ch *cacheHandler) target(ctx context.Context, key datastore.Key) bool { 83 for _, f := range ch.filters { 84 // If false comes back even once, it is not cached 85 if !f(ctx, key) { 86 return false 87 } 88 } 89 90 return true 91 } 92 93 func (ch *cacheHandler) AllocateIDs(info *datastore.MiddlewareInfo, keys []datastore.Key) ([]datastore.Key, error) { 94 return info.Next.AllocateIDs(info, keys) 95 } 96 97 func (ch *cacheHandler) PutMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) ([]datastore.Key, error) { 98 keys, err := info.Next.PutMultiWithoutTx(info, keys, psList) 99 if err != nil { 100 return nil, err 101 } 102 103 cis := make([]*CacheItem, 0, len(keys)) 104 for idx, key := range keys { 105 if key.Incomplete() { 106 // 発生し得ないはずだが他のMiddlewareがやらかすかもしれないので 107 continue 108 } else if !ch.target(info.Context, key) { 109 continue 110 } 111 cis = append(cis, &CacheItem{ 112 Key: key, 113 PropertyList: psList[idx], 114 }) 115 } 116 if len(cis) == 0 { 117 return keys, nil 118 } 119 err = ch.s.SetMulti(info.Context, cis) 120 if err != nil { 121 ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.SetMulti err=%s", err.Error()) 122 } 123 124 return keys, nil 125 } 126 127 func (ch *cacheHandler) PutMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) ([]datastore.PendingKey, error) { 128 pKeys, err := info.Next.PutMultiWithTx(info, keys, psList) 129 130 ch.m.Lock() 131 defer ch.m.Unlock() 132 133 txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog) 134 if !ok { 135 txOpMap = make(map[datastore.Transaction][]*txOpLog) 136 info.Context = context.WithValue(info.Context, contextTx{}, txOpMap) 137 } 138 139 logs := txOpMap[info.Transaction] 140 for idx, key := range keys { 141 if !ch.target(info.Context, key) { 142 continue 143 } 144 log := &txOpLog{ 145 Ops: txPutOp, 146 PropertyList: psList[idx], 147 } 148 if key.Incomplete() { 149 log.PendingKey = pKeys[idx] 150 } else { 151 log.Key = key 152 } 153 logs = append(logs, log) 154 } 155 txOpMap[info.Transaction] = logs 156 157 return pKeys, err 158 } 159 160 func (ch *cacheHandler) GetMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) error { 161 // strategy summary 162 // When we have a dsmiddleware, don't get it. 163 // When we don't have a dsmiddleware, passes arguments to next strategy. 164 165 // 最終的に各所からかき集めてきたdatastore.PropertyListを統合してpsListにする 166 // 1. psListをkeysと同じ長さまで伸長し、任意の場所にindexアクセスできるようにする 167 // 2. 全てのtargetであるkeysについてキャッシュに問い合わせをし、結果があった場合psListに代入する 168 // 3. キャッシュに無かったものを後段に問い合わせる 結果があった場合psListに代入し、次回のためにキャッシュにも入れる 169 170 // step 1 171 for len(psList) < len(keys) { 172 psList = append(psList, nil) 173 } 174 175 { // step 2 176 filteredIdxList := make([]int, 0, len(keys)) 177 filteredKey := make([]datastore.Key, 0, len(keys)) 178 for idx, key := range keys { 179 if ch.target(info.Context, key) { 180 filteredIdxList = append(filteredIdxList, idx) 181 filteredKey = append(filteredKey, key) 182 } 183 } 184 185 if len(filteredKey) != 0 { 186 cis, err := ch.s.GetMulti(info.Context, filteredKey) 187 if err != nil { 188 ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.GetMulti err=%s", err.Error()) 189 190 return info.Next.GetMultiWithoutTx(info, keys, psList) 191 } 192 193 for idx, ci := range cis { 194 if ci != nil { 195 baseIdx := filteredIdxList[idx] 196 psList[baseIdx] = ci.PropertyList 197 } 198 } 199 } 200 } 201 202 var errs []error 203 { // step 3 204 missingIdxList := make([]int, 0, len(keys)) 205 missingKey := make([]datastore.Key, 0, len(keys)) 206 for idx, ps := range psList { 207 if ps == nil { 208 missingIdxList = append(missingIdxList, idx) 209 missingKey = append(missingKey, keys[idx]) 210 } 211 } 212 213 if len(missingKey) != 0 { 214 cis := make([]*CacheItem, 0, len(missingKey)) 215 216 missingPsList := make([]datastore.PropertyList, len(missingKey)) 217 err := info.Next.GetMultiWithoutTx(info, missingKey, missingPsList) 218 if merr, ok := err.(datastore.MultiError); ok { 219 errs = make([]error, len(keys)) 220 for idx, err := range merr { 221 baseIdx := missingIdxList[idx] 222 if err != nil { 223 errs[baseIdx] = err 224 continue 225 } 226 psList[baseIdx] = missingPsList[idx] 227 if ch.target(info.Context, missingKey[idx]) { 228 cis = append(cis, &CacheItem{ 229 Key: missingKey[idx], 230 PropertyList: missingPsList[idx], 231 }) 232 } 233 } 234 } else if err != nil { 235 return err 236 } else { 237 for idx := range missingKey { 238 baseIdx := missingIdxList[idx] 239 psList[baseIdx] = missingPsList[idx] 240 if ch.target(info.Context, missingKey[idx]) { 241 cis = append(cis, &CacheItem{ 242 Key: missingKey[idx], 243 PropertyList: missingPsList[idx], 244 }) 245 } 246 } 247 } 248 249 if len(cis) != 0 { 250 err := ch.s.SetMulti(info.Context, cis) 251 if err != nil { 252 ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.SetMulti err=%s", err.Error()) 253 } 254 } 255 } 256 } 257 258 if len(errs) != 0 { 259 return datastore.MultiError(errs) 260 } 261 262 return nil 263 } 264 265 func (ch *cacheHandler) GetMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key, psList []datastore.PropertyList) error { 266 err := info.Next.GetMultiWithTx(info, keys, psList) 267 268 // strategy summary 269 // don't add to dsmiddleware in tx. It may be complicated and become a spot of bugs 270 271 ch.m.Lock() 272 defer ch.m.Unlock() 273 274 txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog) 275 if !ok { 276 txOpMap = make(map[datastore.Transaction][]*txOpLog) 277 info.Context = context.WithValue(info.Context, contextTx{}, txOpMap) 278 } 279 280 logs := txOpMap[info.Transaction] 281 for _, key := range keys { 282 if !ch.target(info.Context, key) { 283 continue 284 } 285 log := &txOpLog{ 286 Ops: txGetOp, 287 Key: key, 288 } 289 logs = append(logs, log) 290 } 291 txOpMap[info.Transaction] = logs 292 293 return err 294 } 295 296 func (ch *cacheHandler) DeleteMultiWithoutTx(info *datastore.MiddlewareInfo, keys []datastore.Key) error { 297 err := info.Next.DeleteMultiWithoutTx(info, keys) 298 299 filteredKeys := make([]datastore.Key, 0, len(keys)) 300 for _, key := range keys { 301 if !ch.target(info.Context, key) { 302 continue 303 } 304 305 filteredKeys = append(filteredKeys, key) 306 } 307 if len(filteredKeys) == 0 { 308 return err 309 } 310 311 sErr := ch.s.DeleteMulti(info.Context, filteredKeys) 312 if sErr != nil { 313 ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.DeleteMulti err=%s", sErr.Error()) 314 } 315 316 return err 317 } 318 319 func (ch *cacheHandler) DeleteMultiWithTx(info *datastore.MiddlewareInfo, keys []datastore.Key) error { 320 err := info.Next.DeleteMultiWithTx(info, keys) 321 322 ch.m.Lock() 323 defer ch.m.Unlock() 324 325 txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog) 326 if !ok { 327 txOpMap = make(map[datastore.Transaction][]*txOpLog) 328 info.Context = context.WithValue(info.Context, contextTx{}, txOpMap) 329 } 330 331 logs := txOpMap[info.Transaction] 332 for _, key := range keys { 333 if !ch.target(info.Context, key) { 334 continue 335 } 336 337 log := &txOpLog{ 338 Ops: txDeleteOp, 339 Key: key, 340 } 341 logs = append(logs, log) 342 } 343 txOpMap[info.Transaction] = logs 344 345 return err 346 } 347 348 func (ch *cacheHandler) PostCommit(info *datastore.MiddlewareInfo, tx datastore.Transaction, commit datastore.Commit) error { 349 350 ch.m.Lock() 351 defer ch.m.Unlock() 352 353 txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog) 354 if !ok { 355 return info.Next.PostCommit(info, tx, commit) 356 } 357 358 logs := txOpMap[tx] 359 360 keys := make([]datastore.Key, len(logs)) 361 for idx, log := range logs { 362 switch log.Ops { 363 case txPutOp: 364 key := log.Key 365 if log.PendingKey != nil { 366 key = commit.Key(log.PendingKey) 367 } 368 keys[idx] = key 369 370 case txGetOp, txDeleteOp: 371 keys[idx] = log.Key 372 } 373 } 374 375 filteredKeys := make([]datastore.Key, 0, len(keys)) 376 for _, key := range keys { 377 if !ch.target(info.Context, key) { 378 continue 379 } 380 381 filteredKeys = append(filteredKeys, key) 382 } 383 if len(filteredKeys) == 0 { 384 return info.Next.PostCommit(info, tx, commit) 385 } 386 387 // don't pass txCtx to appengine.APICall 388 // otherwise, `transaction context has expired` will be occur 389 baseCtx := info.Client.Context() 390 sErr := ch.s.DeleteMulti(baseCtx, filteredKeys) 391 nErr := info.Next.PostCommit(info, tx, commit) 392 if sErr != nil { 393 ch.logf(info.Context, "dsmiddleware/storagecache.GetMultiWithoutTx: error on storage.DeleteMulti err=%s", sErr.Error()) 394 } 395 396 return nErr 397 } 398 399 func (ch *cacheHandler) PostRollback(info *datastore.MiddlewareInfo, tx datastore.Transaction) error { 400 ch.m.Lock() 401 defer ch.m.Unlock() 402 403 txOpMap, ok := info.Context.Value(contextTx{}).(map[datastore.Transaction][]*txOpLog) 404 if !ok { 405 return info.Next.PostRollback(info, tx) 406 } 407 408 delete(txOpMap, tx) 409 410 return info.Next.PostRollback(info, tx) 411 } 412 413 func (ch *cacheHandler) Run(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump) datastore.Iterator { 414 return info.Next.Run(info, q, qDump) 415 } 416 417 func (ch *cacheHandler) GetAll(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump, psList *[]datastore.PropertyList) ([]datastore.Key, error) { 418 return info.Next.GetAll(info, q, qDump, psList) 419 } 420 421 func (ch *cacheHandler) Next(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump, iter datastore.Iterator, ps *datastore.PropertyList) (datastore.Key, error) { 422 return info.Next.Next(info, q, qDump, iter, ps) 423 } 424 425 func (ch *cacheHandler) Count(info *datastore.MiddlewareInfo, q datastore.Query, qDump *datastore.QueryDump) (int, error) { 426 return info.Next.Count(info, q, qDump) 427 }