Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package gaeconfig | 5 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.
| |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 » "bytes" | 8 » "encoding/hex" |
| 9 » "encoding/binary" | |
| 10 » "fmt" | |
| 11 "time" | 9 "time" |
| 12 | 10 |
| 13 ds "github.com/luci/gae/service/datastore" | |
| 14 mc "github.com/luci/gae/service/memcache" | 11 mc "github.com/luci/gae/service/memcache" |
| 15 » "github.com/luci/luci-go/common/clock" | 12 » "github.com/luci/luci-go/common/errors" |
| 16 » "github.com/luci/luci-go/common/config" | |
| 17 » "github.com/luci/luci-go/common/config/filters/caching" | |
| 18 » "github.com/luci/luci-go/common/data/caching/proccache" | |
| 19 log "github.com/luci/luci-go/common/logging" | 13 log "github.com/luci/luci-go/common/logging" |
| 14 "github.com/luci/luci-go/server/config" | |
| 15 "github.com/luci/luci-go/server/config/caching" | |
| 20 | 16 |
| 21 "golang.org/x/net/context" | 17 "golang.org/x/net/context" |
| 22 ) | 18 ) |
| 23 | 19 |
| 24 // WrapWithCache wraps config client with proccache-and-memcache-caching layer. | 20 const ( |
| 25 func WrapWithCache(cfg config.Interface, expire time.Duration) config.Interface { | 21 » memCacheSchema = "v1" |
| 26 » return caching.Wrap(cfg, caching.Options{ | 22 » maxMemCacheSize = 1024 * 1024 // 1MB |
| 27 » » Cache: &cache{}, | 23 ) |
| 28 » » Expiration: expire, | |
| 29 » }) | |
| 30 } | |
| 31 | 24 |
| 32 type cache struct{} | 25 func memcacheBackend(b config.Backend, exp time.Duration) config.Backend { |
| 26 » return &caching.Backend{ | |
| 27 » » Backend: b, | |
| 28 » » CacheGet: func(c context.Context, key caching.Key, l caching.Loa der) (*caching.Value, error) { | |
| 29 » » » if key.Authority != config.AsService { | |
| 30 » » » » return l(c, key, nil) | |
| 31 » » » } | |
| 33 | 32 |
| 34 type proccacheKey string | 33 » » » // Is the item already cached? |
| 34 » » » k := memcacheKey(&key) | |
| 35 » » » mci, err := mc.GetKey(c, k) | |
| 36 » » » switch err { | |
| 37 » » » case nil: | |
| 38 » » » » // Value was cached, successfully retrieved. | |
| 39 » » » » v, err := caching.DecodeValue(mci.Value()) | |
| 40 » » » » if err != nil { | |
| 41 » » » » » return nil, errors.Annotate(err).Reason( "failed to decode cache value from %(key)q"). | |
| 42 » » » » » » D("key", k).Err() | |
| 43 » » » » } | |
| 44 » » » » return v, nil | |
| 35 | 45 |
| 36 func (c *cache) Store(ctx context.Context, baseKey string, expire time.Duration, value []byte) { | 46 » » » case mc.ErrCacheMiss: |
| 37 » k := cacheKey(baseKey) | 47 » » » » // Value was not cached. Load from Loader and ca che. |
| 48 » » » » v, err := l(c, key, nil) | |
| 49 » » » » if err != nil { | |
| 50 » » » » » return nil, err | |
| 51 » » » » } | |
| 38 | 52 |
| 39 » proccache.Put(ctx, k, value, expire) | 53 » » » » // Attempt to cache the value. If this fails, we 'll log a warning and |
| 54 » » » » // move on. | |
| 55 » » » » err = func() error { | |
| 56 » » » » » d, err := v.Encode() | |
| 57 » » » » » if err != nil { | |
| 58 » » » » » » return errors.Annotate(err).Reas on("failed to encode value").Err() | |
| 59 » » » » » } | |
| 40 | 60 |
| 41 » // value in memcache is [varint(expiration_ts.Millis) ++ value] | 61 » » » » » if len(d) > maxMemCacheSize { |
| 42 » // value in proccache is [value] | 62 » » » » » » return errors.Reason("entry exce eds 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
| |
| 43 » // | 63 » » » » » » » D("size", len(d)).D("max ", maxMemCacheSize).Err() |
| 44 » // This is because memcache doesn't populate the .Expiration field of th e | 64 » » » » » } |
| 45 » // memcache Item on Get operations :( | |
| 46 » stamp := ds.TimeToInt(clock.Now(ctx).UTC().Add(expire)) | |
| 47 » buf := make([]byte, binary.MaxVarintLen64) | |
| 48 » value = append(buf[:binary.PutVarint(buf, stamp)], value...) | |
| 49 | 65 |
| 50 » itm := mc.NewItem(ctx, string(k)).SetExpiration(expire).SetValue(value) | 66 » » » » » item := mc.NewItem(c, k).SetValue(d).Set Expiration(exp) |
| 51 » if err := mc.Set(ctx, itm); err != nil { | 67 » » » » » if err := mc.Set(c, item); err != nil { |
| 52 » » log.Fields{ | 68 » » » » » » return errors.Annotate(err).Err( ) |
| 53 » » » log.ErrorKey: err, | 69 » » » » » } |
| 54 » » » "key": baseKey, | 70 » » » » » return nil |
| 55 » » » "expire": expire, | 71 » » » » }() |
| 56 » » }.Warningf(ctx, "Failed to store cache value.") | 72 » » » » if err != nil { |
| 73 » » » » » log.Fields{ | |
| 74 » » » » » » log.ErrorKey: err, | |
| 75 » » » » » » "key": k, | |
| 76 » » » » » }.Warningf(c, "Failed to cache config.") | |
| 77 » » » » } | |
| 78 | |
| 79 » » » » // Return the loaded value. | |
| 80 » » » » return v, nil | |
| 81 | |
| 82 » » » default: | |
| 83 » » » » // Unknown memcache error. | |
| 84 » » » » log.Fields{ | |
| 85 » » » » » log.ErrorKey: err, | |
| 86 » » » » » "key": k, | |
| 87 » » » » }.Warningf(c, "Failed to decode memcached config .") | |
| 88 » » » » return l(c, key, nil) | |
| 89 » » » } | |
| 90 » » }, | |
| 57 } | 91 } |
| 58 } | 92 } |
| 59 | 93 |
| 60 func (c *cache) Retrieve(ctx context.Context, baseKey string) []byte { | 94 func memcacheKey(key *caching.Key) string { return hex.EncodeToString(key.ParamH ash()) } |
| 61 » k := cacheKey(baseKey) | |
| 62 » ret, err := proccache.GetOrMake(ctx, k, func() (value interface{}, exp t ime.Duration, err error) { | |
| 63 » » item, err := mc.GetKey(ctx, string(k)) | |
| 64 » » if err != nil { | |
| 65 » » » if err != mc.ErrCacheMiss { | |
| 66 » » » » log.Fields{ | |
| 67 » » » » » log.ErrorKey: err, | |
| 68 » » » » » "key": baseKey, | |
| 69 » » » » }.Warningf(ctx, "Failed to retrieve memcache val ue.") | |
| 70 » » » } | |
| 71 » » » return | |
| 72 » » } | |
| 73 | |
| 74 » » buf := bytes.NewBuffer(item.Value()) | |
| 75 » » expStamp, err := binary.ReadVarint(buf) | |
| 76 » » if err != nil { | |
| 77 » » » log.Fields{ | |
| 78 » » » » log.ErrorKey: err, | |
| 79 » » » » "key": baseKey, | |
| 80 » » » }.Warningf(ctx, "Failed to decode stamp in memcache valu e.") | |
| 81 » » » return | |
| 82 » » } | |
| 83 | |
| 84 » » // proccache will ignore this value if exp is in the past | |
| 85 » » exp = ds.IntToTime(expStamp).Sub(clock.Now(ctx)) | |
| 86 » » value = buf.Bytes() | |
| 87 » » return | |
| 88 » }) | |
| 89 » if err != nil { | |
| 90 » » return nil | |
| 91 » } | |
| 92 » return ret.([]byte) | |
| 93 } | |
| 94 | |
| 95 func cacheKey(baseKey string) proccacheKey { | |
| 96 » return proccacheKey(fmt.Sprintf("luci-config:v2:%s", baseKey)) | |
| 97 } | |
| OLD | NEW |