Chromium Code Reviews| Index: appengine/gaeconfig/cache.go |
| diff --git a/appengine/gaeconfig/cache.go b/appengine/gaeconfig/cache.go |
| index 731aab93de00eac29de15c658ebd7f0ca2acd471..06743dcdc21300c2bc3f0509bfba372577a9c8e3 100644 |
| --- a/appengine/gaeconfig/cache.go |
| +++ b/appengine/gaeconfig/cache.go |
| @@ -5,93 +5,90 @@ |
| package gaeconfig |
|
iannucci
2017/01/07 21:05:26
this package could be split into luci-config/cachi
dnj
2017/01/10 03:30:07
Sure, done.
|
| import ( |
| - "bytes" |
| - "encoding/binary" |
| - "fmt" |
| + "encoding/hex" |
| "time" |
| - ds "github.com/luci/gae/service/datastore" |
| mc "github.com/luci/gae/service/memcache" |
| - "github.com/luci/luci-go/common/clock" |
| - "github.com/luci/luci-go/common/config" |
| - "github.com/luci/luci-go/common/config/filters/caching" |
| - "github.com/luci/luci-go/common/data/caching/proccache" |
| + "github.com/luci/luci-go/common/errors" |
| log "github.com/luci/luci-go/common/logging" |
| + "github.com/luci/luci-go/server/config" |
| + "github.com/luci/luci-go/server/config/caching" |
| "golang.org/x/net/context" |
| ) |
| -// WrapWithCache wraps config client with proccache-and-memcache-caching layer. |
| -func WrapWithCache(cfg config.Interface, expire time.Duration) config.Interface { |
| - return caching.Wrap(cfg, caching.Options{ |
| - Cache: &cache{}, |
| - Expiration: expire, |
| - }) |
| -} |
| +const ( |
| + memCacheSchema = "v1" |
| + maxMemCacheSize = 1024 * 1024 // 1MB |
| +) |
| -type cache struct{} |
| +func memcacheBackend(b config.Backend, exp time.Duration) config.Backend { |
| + return &caching.Backend{ |
| + Backend: b, |
| + CacheGet: func(c context.Context, key caching.Key, l caching.Loader) (*caching.Value, error) { |
| + if key.Authority != config.AsService { |
| + return l(c, key, nil) |
| + } |
| -type proccacheKey string |
| + // Is the item already cached? |
| + k := memcacheKey(&key) |
| + mci, err := mc.GetKey(c, k) |
| + switch err { |
| + case nil: |
| + // Value was cached, successfully retrieved. |
| + v, err := caching.DecodeValue(mci.Value()) |
| + if err != nil { |
| + return nil, errors.Annotate(err).Reason("failed to decode cache value from %(key)q"). |
| + D("key", k).Err() |
| + } |
| + return v, nil |
| -func (c *cache) Store(ctx context.Context, baseKey string, expire time.Duration, value []byte) { |
| - k := cacheKey(baseKey) |
| + case mc.ErrCacheMiss: |
| + // Value was not cached. Load from Loader and cache. |
| + v, err := l(c, key, nil) |
| + if err != nil { |
| + return nil, err |
| + } |
| - proccache.Put(ctx, k, value, expire) |
| + // Attempt to cache the value. If this fails, we'll log a warning and |
| + // move on. |
| + err = func() error { |
| + d, err := v.Encode() |
| + if err != nil { |
| + return errors.Annotate(err).Reason("failed to encode value").Err() |
| + } |
| - // value in memcache is [varint(expiration_ts.Millis) ++ value] |
| - // value in proccache is [value] |
| - // |
| - // This is because memcache doesn't populate the .Expiration field of the |
| - // memcache Item on Get operations :( |
| - stamp := ds.TimeToInt(clock.Now(ctx).UTC().Add(expire)) |
| - buf := make([]byte, binary.MaxVarintLen64) |
| - value = append(buf[:binary.PutVarint(buf, stamp)], value...) |
| + if len(d) > maxMemCacheSize { |
| + return errors.Reason("entry exceeds memcache size (%(size)d > %(max)d)"). |
|
iannucci
2017/01/07 21:05:26
should this be logged instead of an error?
dnj
2017/01/10 03:30:07
I don't think so. If config entries are really big
|
| + D("size", len(d)).D("max", maxMemCacheSize).Err() |
| + } |
| - itm := mc.NewItem(ctx, string(k)).SetExpiration(expire).SetValue(value) |
| - if err := mc.Set(ctx, itm); err != nil { |
| - log.Fields{ |
| - log.ErrorKey: err, |
| - "key": baseKey, |
| - "expire": expire, |
| - }.Warningf(ctx, "Failed to store cache value.") |
| - } |
| -} |
| + item := mc.NewItem(c, k).SetValue(d).SetExpiration(exp) |
| + if err := mc.Set(c, item); err != nil { |
| + return errors.Annotate(err).Err() |
| + } |
| + return nil |
| + }() |
| + if err != nil { |
| + log.Fields{ |
| + log.ErrorKey: err, |
| + "key": k, |
| + }.Warningf(c, "Failed to cache config.") |
| + } |
| -func (c *cache) Retrieve(ctx context.Context, baseKey string) []byte { |
| - k := cacheKey(baseKey) |
| - ret, err := proccache.GetOrMake(ctx, k, func() (value interface{}, exp time.Duration, err error) { |
| - item, err := mc.GetKey(ctx, string(k)) |
| - if err != nil { |
| - if err != mc.ErrCacheMiss { |
| + // Return the loaded value. |
| + return v, nil |
| + |
| + default: |
| + // Unknown memcache error. |
| log.Fields{ |
| log.ErrorKey: err, |
| - "key": baseKey, |
| - }.Warningf(ctx, "Failed to retrieve memcache value.") |
| + "key": k, |
| + }.Warningf(c, "Failed to decode memcached config.") |
| + return l(c, key, nil) |
| } |
| - return |
| - } |
| - |
| - buf := bytes.NewBuffer(item.Value()) |
| - expStamp, err := binary.ReadVarint(buf) |
| - if err != nil { |
| - log.Fields{ |
| - log.ErrorKey: err, |
| - "key": baseKey, |
| - }.Warningf(ctx, "Failed to decode stamp in memcache value.") |
| - return |
| - } |
| - |
| - // proccache will ignore this value if exp is in the past |
| - exp = ds.IntToTime(expStamp).Sub(clock.Now(ctx)) |
| - value = buf.Bytes() |
| - return |
| - }) |
| - if err != nil { |
| - return nil |
| + }, |
| } |
| - return ret.([]byte) |
| } |
| -func cacheKey(baseKey string) proccacheKey { |
| - return proccacheKey(fmt.Sprintf("luci-config:v2:%s", baseKey)) |
| -} |
| +func memcacheKey(key *caching.Key) string { return hex.EncodeToString(key.ParamHash()) } |