go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/filter/dscache/plan.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package dscache 16 17 import ( 18 "bytes" 19 20 "go.chromium.org/luci/common/errors" 21 "go.chromium.org/luci/common/logging" 22 23 ds "go.chromium.org/luci/gae/service/datastore" 24 ) 25 26 type facts struct { 27 getKeys []*ds.Key 28 getMeta ds.MultiMetaGetter 29 lockItems []CacheItem 30 nonce []byte 31 } 32 33 type plan struct { 34 keepMeta bool 35 36 // idxMap maps from the original list of keys to the reduced set of keys in 37 // toGet. E.g. given the index `j` while looping over toGet, idxMap[j] will 38 // equal the index in the original facts.getKeys list. 39 idxMap []int 40 41 // toGet is a list of datstore keys to retrieve in the call down to the 42 // underlying datastore. It will have length >= facts.getKeys. All the keys 43 // in this will be valid (not nil). 44 toGet []*ds.Key 45 46 // toGetMeta is a MultiMetaGetter to be passed in the call down to the 47 // underlying datastore.GetMulti. It has the same length as toGet. 48 toGetMeta ds.MultiMetaGetter 49 50 // toSave is the list of memcache items to save the results from the 51 // underlying datastore.GetMulti. It MAY contain nils, which is an indicator 52 // that this entry SHOULD NOT be saved to memcache. Items that are not nils 53 // are necessarily locks owned by us (since we can save only stuff we promised 54 // to save by locking it). 55 toSave []CacheItem 56 57 // decoded is a list of all the decoded property maps. Its length always == 58 // len(facts.getKeys). After the plan is formed, it may contain nils. These 59 // nils will be filled in from the underlying datastore.GetMulti call in 60 // ds.go. 61 decoded []ds.PropertyMap 62 63 // lme is a LazyMultiError whose target Size == len(facts.getKeys). The errors 64 // will eventually bubble back to the layer above this filter in callbacks. 65 lme errors.LazyMultiError 66 } 67 68 // add adds a new entry to be retrieved from the actual underlying datastore 69 // (via GetMulti). 70 // 71 // - idx is the index into the original facts.getKeys 72 // - get and m are the pair of values that will be passed to datastore.GetMulti 73 // - save is the memcache item to save the result back to. If it's nil, then 74 // it will not be saved back to memcache. 75 func (p *plan) add(idx int, get *ds.Key, m ds.MetaGetter, save CacheItem) { 76 p.idxMap = append(p.idxMap, idx) 77 p.toGet = append(p.toGet, get) 78 79 p.toSave = append(p.toSave, save) 80 81 if p.keepMeta { 82 p.toGetMeta = append(p.toGetMeta, m) 83 } 84 } 85 86 func (p *plan) empty() bool { 87 return len(p.idxMap) == 0 88 } 89 90 // makeFetchPlan takes the input facts and makes a plan about what to do with them. 91 // 92 // Possible scenarios: 93 // - all entries we got from memcache are valid data, and so we don't need 94 // to call through to the underlying datastore at all. 95 // - some entries are 'lock' entries, owned by us, and so we should get them 96 // from datastore and then attempt to save them back to memcache. 97 // - some entries are 'lock' entries, owned by something else, so we should 98 // get them from datastore and then NOT save them to memcache. 99 // 100 // Or some combination thereof. This also handles memcache entries with invalid 101 // data in them, cases where items have caching disabled entirely, etc. 102 func (d *dsCache) makeFetchPlan(f facts) *plan { 103 p := plan{ 104 keepMeta: f.getMeta != nil, 105 decoded: make([]ds.PropertyMap, len(f.lockItems)), 106 lme: errors.NewLazyMultiError(len(f.lockItems)), 107 } 108 for i, lockItm := range f.lockItems { 109 m := f.getMeta.GetSingle(i) 110 getKey := f.getKeys[i] 111 112 if lockItm == nil { 113 // this item wasn't cacheable (e.g. the model had caching disabled, 114 // shardsForKey returned 0, etc.) 115 p.add(i, getKey, m, nil) 116 continue 117 } 118 119 switch nonce := lockItm.Nonce(); { 120 case bytes.Equal(f.nonce, nonce): 121 // we have the lock, need to fetch and store the items in the cache 122 p.add(i, getKey, m, lockItm) 123 case nonce != nil: 124 // someone else has the lock, don't save 125 p.add(i, getKey, m, nil) 126 default: 127 // this is a data item, just load it, don't touch datastore 128 pmap, err := decodeItemValue(lockItm.Data(), d.KeyContext) 129 switch err { 130 case nil: 131 p.decoded[i] = pmap 132 case ds.ErrNoSuchEntity: 133 p.lme.Assign(i, ds.ErrNoSuchEntity) 134 default: 135 logging.WithError(err).Warningf(d.c, "dscache: error decoding %s, %s", lockItm.Key(), getKey) 136 p.add(i, getKey, m, nil) 137 } 138 } 139 } 140 return &p 141 }