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

Side by Side Diff: impl/cloud/memcache.go

Issue 2513253002: impl/cloud: Add support for "memcached" memcache. (Closed)
Patch Set: Namespace all keys to luci/gae/impl/cloud. Created 4 years 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 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package cloud
6
7 import (
8 "crypto/sha256"
9 "encoding/hex"
10 "strconv"
11 "time"
12
13 "github.com/luci/gae/service/info"
14 mc "github.com/luci/gae/service/memcache"
15
16 "github.com/bradfitz/gomemcache/memcache"
17 "golang.org/x/net/context"
18 )
19
20 // memcacheKeyPrefix is the common prefix prepended to memcached keys created
21 // by this package. It is intended to ensure that keys do not conflict with
22 // other users of the service.
23 const memcacheKeyPrefix = "github.com/luci/gae/impl/cloud:"
24
25 // memcacheClient is a "service/memcache" implementation built on top of a
26 // "memcached" client connection.
27 //
28 // Because "memcached" has no concept of a namespace, we differentiate memcache
29 // entries by prepending "memcacheKeyPrefix:SHA256(namespace):" to each key.
iannucci 2016/11/20 19:35:16 note that this is basically how it's done in prod.
dnj 2016/11/20 22:28:47 Oh cool, I'll add null-byte restriction and >250 b
dnj 2016/11/21 05:24:33 Actually on second thought: The null-byte restric
30 type memcacheClient struct {
31 client *memcache.Client
32 }
33
34 func (m *memcacheClient) use(c context.Context) context.Context {
35 return mc.SetRawFactory(c, func(ic context.Context) mc.RawInterface {
36 return bindMemcacheClient(m, info.GetNamespace(ic))
37 })
38 }
39
40 type memcacheItem struct {
41 native *memcache.Item
42 }
43
44 func (it *memcacheItem) Key() string { return it.native.Key }
45 func (it *memcacheItem) Value() []byte { return it.native.Value }
46 func (it *memcacheItem) Flags() uint32 { return it.native.Flags }
47 func (it *memcacheItem) Expiration() time.Duration {
48 return time.Duration(it.native.Expiration) * time.Second
49 }
50
51 func (it *memcacheItem) SetKey(v string) mc.Item {
52 it.native.Key = v
53 return it
54 }
55
56 func (it *memcacheItem) SetValue(v []byte) mc.Item {
57 it.native.Value = v
58 return it
59 }
60
61 func (it *memcacheItem) SetFlags(v uint32) mc.Item {
62 it.native.Flags = v
63 return it
64 }
65
66 func (it *memcacheItem) SetExpiration(v time.Duration) mc.Item {
67 it.native.Expiration = int32(v.Seconds())
68 return it
69 }
70
71 func (it *memcacheItem) SetAll(other mc.Item) {
72 origKey := it.native.Key
73
74 var on memcache.Item
75 if other != nil {
76 on = *(other.(*memcacheItem).native)
77 }
78 it.native = &on
79 it.native.Key = origKey
80 }
81
82 type boundMemcacheClient struct {
83 *memcacheClient
84 keyPrefix string
85 }
86
87 func bindMemcacheClient(mc *memcacheClient, ns string) *boundMemcacheClient {
88 nsHash := sha256.Sum256([]byte(ns))
89 nsPrefix := hex.EncodeToString(nsHash[:])
90 return &boundMemcacheClient{
91 memcacheClient: mc,
92 keyPrefix: memcacheKeyPrefix + nsPrefix + ":",
93 }
94 }
95
96 func (*boundMemcacheClient) newMemcacheItem(nativeKey string) *memcacheItem {
97 return &memcacheItem{
98 native: &memcache.Item{
99 Key: nativeKey,
100 },
101 }
102 }
103
104 func (bmc *boundMemcacheClient) makeKey(base string) string { return bmc.keyPref ix + base }
105 func (bmc *boundMemcacheClient) userKey(key string) string { return key[len(bmc .keyPrefix):] }
106
107 func (bmc *boundMemcacheClient) nativeItem(itm mc.Item) *memcache.Item {
108 ni := *(itm.(*memcacheItem).native)
109 ni.Key = bmc.makeKey(ni.Key)
110 return &ni
111 }
112
113 func (bmc *boundMemcacheClient) NewItem(key string) mc.Item { return bmc.newMemc acheItem(key) }
114
115 func (bmc *boundMemcacheClient) AddMulti(items []mc.Item, cb mc.RawCB) error {
116 for _, itm := range items {
117 err := bmc.client.Add(bmc.nativeItem(itm))
118 cb(bmc.translateErr(err))
119 }
120 return nil
121 }
122
123 func (bmc *boundMemcacheClient) SetMulti(items []mc.Item, cb mc.RawCB) error {
124 for _, itm := range items {
125 err := bmc.client.Set(bmc.nativeItem(itm))
126 cb(bmc.translateErr(err))
127 }
128 return nil
129 }
130
131 func (bmc *boundMemcacheClient) GetMulti(keys []string, cb mc.RawItemCB) error {
132 nativeKeys := make([]string, len(keys))
133 for i, key := range keys {
134 nativeKeys[i] = bmc.makeKey(key)
135 }
136
137 itemMap, err := bmc.client.GetMulti(nativeKeys)
138 if err != nil {
139 return bmc.translateErr(err)
140 }
141
142 // Translate the item keys back to user keys.
143 for _, v := range itemMap {
144 v.Key = bmc.userKey(v.Key)
145 }
146
147 for _, k := range nativeKeys {
148 if it := itemMap[k]; it != nil {
149 cb(&memcacheItem{native: it}, nil)
150 } else {
151 cb(nil, mc.ErrCacheMiss)
152 }
153 }
154 return nil
155 }
156
157 func (bmc *boundMemcacheClient) DeleteMulti(keys []string, cb mc.RawCB) error {
158 for _, k := range keys {
159 err := bmc.client.Delete(bmc.makeKey(k))
160 cb(bmc.translateErr(err))
161 }
162 return nil
163 }
164
165 func (bmc *boundMemcacheClient) CompareAndSwapMulti(items []mc.Item, cb mc.RawCB ) error {
166 for _, itm := range items {
167 err := bmc.client.CompareAndSwap(bmc.nativeItem(itm))
168 cb(bmc.translateErr(err))
169 }
170 return nil
171 }
172
173 func (bmc *boundMemcacheClient) Increment(key string, delta int64, initialValue *uint64) (uint64, error) {
174 // key is now the native key (namespaced).
175 key = bmc.makeKey(key)
176
177 op := func() (newValue uint64, err error) {
178 switch {
179 case delta > 0:
180 newValue, err = bmc.client.Increment(key, uint64(delta))
181 case delta < 0:
182 newValue, err = bmc.client.Decrement(key, uint64(-delta) )
183 default:
184 // We don't want to change the value, but we want to ret urn ErrNotStored
185 // if the value doesn't exist. Use Get.
186 _, err = bmc.client.Get(key)
187 }
188 err = bmc.translateErr(err)
189 return
190 }
191
192 if initialValue == nil {
193 return op()
194 }
195
196 // The Memcache service doesn't have an "IncrementExisting" equivalent. We
iannucci 2016/11/20 19:35:16 Should we move this up to a top-level function? I
dnj 2016/11/20 22:28:47 TBH I think the implementation here is pretty opti
197 // will emulate this with other memcache operations, using Add to set th e
198 // initial value if appropriate.
199 var (
200 itm *memcacheItem
201 iv = *initialValue
202 )
203 for {
204 // Perform compare-and-swap.
205 nv, err := op()
206 if err != mc.ErrCacheMiss {
207 return nv, err
208 }
209
210 // The value doesn't exist. Use "Add" to set the initial value. We will
211 // calculate the "initial value" as if delta were applied so we can do this
212 // in one operation.
213 //
214 // We only need to do this once per invocation, so we will use " itm == nil"
215 // as a sentinel for uninitialized.
216 if itm == nil {
217 // Overflow wraps around (to zero), and underflow is cap ped at 0.
218 if delta < 0 {
219 udelta := uint64(-delta)
220 if udelta >= iv {
221 // Would underflow, cap at 0.
222 iv = 0
223 } else {
224 iv -= udelta
225 }
226 } else {
227 // Apply delta. This will automatically wrap on overflow.
228 iv += uint64(delta)
229 }
230
231 itm = bmc.newMemcacheItem(key)
232 itm.SetValue([]byte(strconv.FormatUint(iv, 10)))
233 }
234 switch err := bmc.client.Add(itm.native); err {
235 case nil:
236 // Item was successfully set.
237 return iv, nil
238
239 case mc.ErrNotStored:
240 // Something else set it in between "op" and "Add". Try "op" again.
241 break
242
243 default:
244 return 0, err
245 }
246 }
247 }
248
249 func (bmc *boundMemcacheClient) Flush() error {
250 // Unfortunately there's not really a good way to flush just a single
251 // namespace, so Flush will flush all memcache.
iannucci 2016/11/20 19:35:16 I'm pretty sure this is the same behavior in prod.
dnj 2016/11/20 22:28:47 Hopefully prod at least flushes per-customer. I en
iannucci 2016/11/23 21:14:37 GAE flushes all of an app's keys (but all gae-name
252 return bmc.translateErr(bmc.client.FlushAll())
253 }
254
255 func (bmc *boundMemcacheClient) Stats() (*mc.Statistics, error) { return nil, mc .ErrNoStats }
256
257 func (*boundMemcacheClient) translateErr(err error) error {
258 switch err {
259 case memcache.ErrCacheMiss:
260 return mc.ErrCacheMiss
261 case memcache.ErrCASConflict:
262 return mc.ErrCASConflict
263 case memcache.ErrNotStored:
264 return mc.ErrNotStored
265 case memcache.ErrServerError:
266 return mc.ErrServerError
267 case memcache.ErrNoStats:
268 return mc.ErrNoStats
269 default:
270 return err
271 }
272 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698