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  }