Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(123)

Side by Side Diff: filter/dscache/dscache.go

Issue 1269113005: A transparent cache for datastore, backed by memcache. (Closed) Base URL: https://github.com/luci/gae.git@add_meta
Patch Set: some minor comments Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package dscache
6
7 import (
8 "bytes"
9 "crypto/sha1"
10 "encoding/base64"
11 "fmt"
12 "sync"
13 "time"
14
15 "github.com/luci/gae/service/datastore"
16 "github.com/luci/gae/service/info"
17 "github.com/luci/gae/service/memcache"
18 "github.com/luci/luci-go/common/clock"
19 "golang.org/x/net/context"
20 )
21
22 var (
23 // InstanceEnabledStatic allows you to statically (e.g. in an init() fun ction)
24 // bypass this filter by setting it to false. This takes effect when the
25 // application calls IsGloballyEnabled.
26 InstanceEnabledStatic = true
27
28 // LockTimeSeconds is the number of seconds that a "lock" memcache entry will
29 // have its expiration set to. It's set to just over half of the fronten d
30 // request handler timeout (currently 60 seconds).
31 LockTimeSeconds = 31
32
33 // CacheTimeSeconds is the default number of seconds that a positively c ached
34 // entity will be retained (memcache contention notwithstanding). A valu e of
35 // 0 is infinite.
36 CacheTimeSeconds int64 = int64((time.Hour * 24).Seconds())
dnj 2015/08/07 16:30:06 WDYT about just making this a "time.Duration" w/ t
iannucci 2015/08/07 19:41:00 I mentioned this elsewhere, but memcache truncates
37
38 // CompressionThreshold is the number of bytes of entity value after whi ch
39 // compression kicks in.
40 CompressionThreshold = 860
41
42 // DefaultShards is the default number of key sharding to do.
43 DefaultShards int = 1
44
45 // DefaultEnable indicates whether or not caching is globally enabled or
46 // disabled by default. Can still be overridden by CacheEnableMeta.
47 DefaultEnabled = true
48 )
49
50 const (
51 MemcacheVersion = "1"
52
53 // KeyFormat is the format string used to generate memcache keys. It's
54 // gae:<version>:<shard#>:<base64_std_nopad(sha1(datastore.Key))>
55 KeyFormat = "gae:" + MemcacheVersion + ":%x:%s"
56 Sha1B64Padding = 1
57 Sha1B64Size = 28 - Sha1B64Padding
58
59 MaxShards = 256
60 MaxShardsLen = len("ff")
61 InternalGAEPadding = 96
62 ValueSizeLimit = (1000 * 1000) - InternalGAEPadding - MaxShardsLen
63
64 CacheEnableMeta = "dscache.enable"
65 CacheExpirationMeta = "dscache.expiration"
66
67 // NonceUint32s is the number of 32 bit uints to use in the 'lock' nonce .
68 NonceUint32s = 2
69
70 // GlobalEnabledCheckInterval is how frequently IsGloballyEnabled should check
71 // the globalEnabled datastore entry.
72 GlobalEnabledCheckInterval = 5 * time.Minute
73 )
74
75 // internalValueSizeLimit is a var for testing purposes.
76 var internalValueSizeLimit = ValueSizeLimit
77
78 type CompressionType byte
79
80 const (
81 NoCompression CompressionType = iota
82 ZlibCompression
83 )
84
85 func (c CompressionType) String() string {
86 switch c {
87 case NoCompression:
88 return "NoCompression"
89 case ZlibCompression:
90 return "ZlibCompression"
91 default:
92 return fmt.Sprintf("UNKNOWN_CompressionType(%d)", c)
93 }
94 }
95
96 // FlagValue is used to indicate if a memcache entry currently contains an
97 // item or a lock.
98 type FlagValue uint32
99
100 const (
101 ItemUKNONWN FlagValue = iota
102 ItemHasData
103 ItemHasLock
104 )
105
106 func MakeMemcacheKey(shard int, k datastore.Key) string {
107 return fmt.Sprintf(KeyFormat, shard, HashKey(k))
108 }
109
110 func HashKey(k datastore.Key) string {
111 // errs can't happen, since we're using a byte buffer.
112 buf := bytes.Buffer{}
113 _ = datastore.WriteKey(&buf, datastore.WithoutContext, k)
114 dgst := sha1.Sum(buf.Bytes())
115 buf.Reset()
116 enc := base64.NewEncoder(base64.StdEncoding, &buf)
117 _, _ = enc.Write(dgst[:])
118 enc.Close()
119 return buf.String()[:buf.Len()-Sha1B64Padding]
120 }
121
122 type GlobalConfig struct {
dnj 2015/08/07 16:30:06 IMO move to a separate ".go" file.
iannucci 2015/08/07 19:41:00 Done.
123 _id int64 `gae:"$id,1"`
124 _kind string `gae:"$kind,dscache"`
125
126 Enable bool
127 }
128
129 var (
130 globalEnabledLock = sync.RWMutex{}
131
132 // globalEnabled is whether or not memcache has been globally enabled. I t is
133 // populated by IsGloballyEnabled when SetDynamicGlobalEnable has been s et to
134 // true.
135 globalEnabled = true
136
137 // globalEnabledNextCheck is IsGloballyEnabled's last successful check o f the
138 // global disable key.
139 globalEnabledNextCheck = time.Time{}
140 )
141
142 // IsGloballyEnabled checks to see if this filter is enabled globally.
143 //
144 // This checks InstanceEnabledStatic, as well as polls the datastore entity
145 // /dscache,1 (a GlobalConfig instance)
146 // Once every GlobalEnabledCheckInterval.
147 //
148 // For correctness, any error encountered returns true. If this assumed false,
149 // then Put operations might incorrectly invalidate the cache.
150 func IsGloballyEnabled(c context.Context) bool {
151 if !InstanceEnabledStatic {
152 return false
153 }
154
155 now := clock.Now(c)
156
157 globalEnabledLock.RLock()
dnj 2015/08/07 16:30:07 For peace of mind (and to avoid C-style cleanup pa
iannucci 2015/08/07 19:41:00 derp, dunno what I was thinking. did this.
158 if now.Before(globalEnabledNextCheck) {
159 globalEnabledLock.RUnlock()
160 return globalEnabled
161 }
162 globalEnabledLock.RUnlock()
163
164 globalEnabledLock.Lock()
165 defer globalEnabledLock.Unlock()
166 // just in case we raced
167 if now.Before(globalEnabledNextCheck) {
168 return globalEnabled
169 }
170
171 // alawys go to the default namespace
dnj 2015/08/07 16:30:06 always
iannucci 2015/08/07 19:41:00 Done.
172 c, err := info.Get(c).Namespace("")
173 if err != nil {
174 return true
dnj 2015/08/07 16:30:06 When the memcache failure case can perpetuate stal
iannucci 2015/08/07 19:41:00 Nope, because then you could end up doing Put oper
175 }
176 cfg := &GlobalConfig{Enable: true}
177 if err := datastore.Get(c).Get(cfg); err != nil && err != datastore.ErrN oSuchEntity {
178 return true
179 }
180 globalEnabled = cfg.Enable
181 globalEnabledNextCheck = now.Add(GlobalEnabledCheckInterval)
182 return globalEnabled
dnj 2015/08/07 16:30:06 Just return "cfg.Enable".
iannucci 2015/08/07 19:41:00 I actually prefer not to. globalEnabled is the val
183 }
184
185 func SetDynamicGlobalEnable(c context.Context, memcacheEnabled bool) error {
dnj 2015/08/07 16:30:07 Why "Dynamic"? Seems inconsistent with the other v
iannucci 2015/08/07 19:41:00 old naming artifact. fixed.
186 // alawys go to the default namespace
187 c, err := info.Get(c).Namespace("")
188 if err != nil {
189 return err
190 }
191 return datastore.Get(c).RunInTransaction(func(c context.Context) error {
192 ds := datastore.Get(c)
193 cfg := &GlobalConfig{Enable: true}
194 if err := ds.Get(cfg); err != nil && err != datastore.ErrNoSuchE ntity {
195 return err
196 }
197 if cfg.Enable == memcacheEnabled {
198 return nil
199 }
200 cfg.Enable = memcacheEnabled
201 if memcacheEnabled {
202 // when going false -> true, wipe memcache.
203 if err := memcache.Get(c).Flush(); err != nil {
204 return err
205 }
206 }
207 return ds.Put(cfg)
208 }, nil)
209 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698