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

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

Issue 1269113005: A transparent cache for datastore, backed by memcache. (Closed) Base URL: https://github.com/luci/gae.git@add_meta
Patch Set: add test for per-model expiration 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 "encoding/binary"
10 "errors"
11 "math/rand"
12 "testing"
13 "time"
14
15 "github.com/luci/gae/impl/memory"
16 "github.com/luci/gae/service/datastore"
17 "github.com/luci/gae/service/memcache"
18 "github.com/luci/luci-go/common/clock"
19 "github.com/luci/luci-go/common/clock/testclock"
20 "github.com/luci/luci-go/common/mathrand"
21 . "github.com/smartystreets/goconvey/convey"
22 "golang.org/x/net/context"
23 )
24
25 type object struct {
26 ID int64 `gae:"$id"`
27
28 Value string
29 BigData []byte
30 }
31
32 type shardObj struct { // see shardsForKey() at top
33 ID int64 `gae:"$id"`
34
35 Value string
36 }
37
38 type noCacheObj struct { // see shardsForKey() at top
39 ID string `gae:"$id"`
40
41 Value bool
42 }
43
44 func init() {
45 datastore.WritePropertyMapDeterministic = true
46
47 internalValueSizeLimit = 2048
48 }
49
50 func TestDSCache(t *testing.T) {
51 t.Parallel()
52
53 zeroTime, err := time.Parse("2006-01-02T15:04:05.999999999Z", "2006-01-0 2T15:04:05.999999999Z")
54 if err != nil {
55 panic(err)
56 }
57
58 Convey("Test dscache", t, func() {
59 c := mathrand.Set(context.Background(), rand.New(rand.NewSource( 1)))
60 clk := testclock.New(zeroTime)
61 c = clock.Set(c, clk)
62 c = memory.Use(c)
63 dsUnder := datastore.Get(c)
64
65 shardsForKey := func(k datastore.Key) int {
66 if k.Kind() == "shardObj" {
67 return int(k.IntID())
68 }
69 if k.Kind() == "noCacheObj" {
70 return 0
71 }
72 return DefaultShards
73 }
74
75 ds := datastore.Get(FilterRDS(c, shardsForKey))
76 mc := memcache.Get(c)
77
78 itmFor := func(i int, k datastore.Key) memcache.Item {
79 return mc.NewItem(MakeMemcacheKey(i, k))
80 }
81
82 numMemcacheItems := func() uint64 {
83 stats, err := mc.Stats()
84 So(err, ShouldBeNil)
85 return stats.Items
86 }
87
88 So(dsUnder, ShouldNotBeNil)
89 So(ds, ShouldNotBeNil)
90 So(mc, ShouldNotBeNil)
91
92 Convey("basically works", func() {
93 pm := datastore.PropertyMap{
94 "BigData": {datastore.MkProperty([]byte(""))},
95 "Value": {datastore.MkProperty("hi")},
96 }
97 buf := &bytes.Buffer{}
98 So(pm.Write(buf, datastore.WithoutContext), ShouldBeNil)
99 encoded := append([]byte{0}, buf.Bytes()...)
100
101 o := object{ID: 1, Value: "hi"}
102 So(ds.Put(&o), ShouldBeNil)
103
104 o = object{ID: 1}
105 So(dsUnder.Get(&o), ShouldBeNil)
106 So(o.Value, ShouldEqual, "hi")
107
108 itm := itmFor(0, ds.KeyForObj(&o))
109 So(mc.Get(itm), ShouldEqual, memcache.ErrCacheMiss)
110
111 o = object{ID: 1}
112 So(ds.Get(&o), ShouldBeNil)
113 So(o.Value, ShouldEqual, "hi")
114 So(mc.Get(itm), ShouldBeNil)
115 So(itm.Value(), ShouldResemble, encoded)
116
117 Convey("now we don't need the datastore!", func() {
118 o := object{ID: 1}
119
120 // delete it, bypassing the cache filter. Don't do this in production
121 // unless you want a crappy cache.
122 So(dsUnder.Delete(ds.KeyForObj(&o)), ShouldBeNil )
123
124 itm := itmFor(0, ds.KeyForObj(&o))
125 So(mc.Get(itm), ShouldBeNil)
126 So(itm.Value(), ShouldResemble, encoded)
127
128 So(ds.Get(&o), ShouldBeNil)
129 So(o.Value, ShouldEqual, "hi")
130 })
131
132 Convey("deleting it properly records that fact, however" , func() {
133 o := object{ID: 1}
134 So(ds.Delete(ds.KeyForObj(&o)), ShouldBeNil)
135
136 itm := itmFor(0, ds.KeyForObj(&o))
137 So(mc.Get(itm), ShouldEqual, memcache.ErrCacheMi ss)
138 So(ds.Get(&o), ShouldEqual, datastore.ErrNoSuchE ntity)
139
140 So(mc.Get(itm), ShouldBeNil)
141 So(itm.Value(), ShouldResemble, []byte{})
142
143 // this one hits memcache
144 So(ds.Get(&o), ShouldEqual, datastore.ErrNoSuchE ntity)
145 })
146 })
147
148 Convey("compression works", func() {
149 o := object{ID: 2, Value: `¯\_(ツ)_/¯`}
150 data := make([]byte, 4000)
151 for i := range data {
152 const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg hijklmnopqrstuvwxyz0123456789!@#$%^&*()"
153 data[i] = alpha[i%len(alpha)]
154 }
155 o.BigData = data
156
157 So(ds.Put(&o), ShouldBeNil)
158 So(ds.Get(&o), ShouldBeNil)
159
160 itm := itmFor(0, ds.KeyForObj(&o))
161 So(mc.Get(itm), ShouldBeNil)
162
163 So(itm.Value()[0], ShouldEqual, ZlibCompression)
164 So(len(itm.Value()), ShouldEqual, 653) // a bit smaller than 4k
165
166 // ensure the next Get comes from the cache
167 So(dsUnder.Delete(ds.KeyForObj(&o)), ShouldBeNil)
168
169 o = object{ID: 2}
170 So(ds.Get(&o), ShouldBeNil)
171 So(o.Value, ShouldEqual, `¯\_(ツ)_/¯`)
172 So(o.BigData, ShouldResemble, data)
173 })
174
175 Convey("transactions", func() {
176 Convey("work", func() {
177 // populate an object @ ID1
178 So(ds.Put(&object{ID: 1, Value: "something"}), S houldBeNil)
179 So(ds.Get(&object{ID: 1}), ShouldBeNil)
180
181 So(ds.Put(&object{ID: 2, Value: "nurbs"}), Shoul dBeNil)
182 So(ds.Get(&object{ID: 2}), ShouldBeNil)
183
184 // memcache now has the wrong value (simulated r ace)
185 So(dsUnder.Put(&object{ID: 1, Value: "else"}), S houldBeNil)
186 So(ds.RunInTransaction(func(c context.Context) e rror {
187 ds := datastore.Get(c)
188 o := &object{ID: 1}
189 So(ds.Get(o), ShouldBeNil)
190 So(o.Value, ShouldEqual, "else")
191 o.Value = "txn"
192 So(ds.Put(o), ShouldBeNil)
193
194 So(ds.Delete(ds.KeyForObj(&object{ID: 2} )), ShouldBeNil)
195 return nil
196 }, &datastore.TransactionOptions{XG: true}), Sho uldBeNil)
197
198 So(mc.Get(itmFor(0, ds.KeyForObj(&object{ID: 1}) )),
199 ShouldEqual, memcache.ErrCacheMiss)
200 So(mc.Get(itmFor(0, ds.KeyForObj(&object{ID: 2}) )),
201 ShouldEqual, memcache.ErrCacheMiss)
202 o := &object{ID: 1}
203 So(ds.Get(o), ShouldBeNil)
204 So(o.Value, ShouldEqual, "txn")
205 })
206
207 Convey("errors don't invalidate", func() {
208 // populate an object @ ID1
209 So(ds.Put(&object{ID: 1, Value: "something"}), S houldBeNil)
210 So(ds.Get(&object{ID: 1}), ShouldBeNil)
211 So(numMemcacheItems(), ShouldEqual, 1)
212
213 So(ds.RunInTransaction(func(c context.Context) e rror {
214 ds := datastore.Get(c)
215 o := &object{ID: 1}
216 So(ds.Get(o), ShouldBeNil)
217 So(o.Value, ShouldEqual, "something")
218 o.Value = "txn"
219 So(ds.Put(o), ShouldBeNil)
220 return errors.New("OH NOES")
221 }, nil).Error(), ShouldContainSubstring, "OH NOE S")
222
223 // memcache still has the original
224 So(numMemcacheItems(), ShouldEqual, 1)
225 So(dsUnder.Delete(ds.KeyForObj(&object{ID: 1})), ShouldBeNil)
226 o := &object{ID: 1}
227 So(ds.Get(o), ShouldBeNil)
228 So(o.Value, ShouldEqual, "something")
229 })
230 })
231
232 Convey("control", func() {
233 Convey("per-model bypass", func() {
234 type model struct {
235 ID string `gae:"$id"`
236 UseDSCache datastore.Toggle `gae:"$dscac he.enable,false"`
237
238 Value string
239 }
240
241 itms := []model{
242 {ID: "hi", Value: "something"},
243 {ID: "there", Value: "else", UseDSCache: datastore.On},
244 }
245
246 So(ds.PutMulti(itms), ShouldBeNil)
247 So(ds.GetMulti(itms), ShouldBeNil)
248
249 So(numMemcacheItems(), ShouldEqual, 1)
250 })
251
252 Convey("per-key shard count", func() {
253 s := &shardObj{ID: 4, Value: "hi"}
254 So(ds.Put(s), ShouldBeNil)
255 So(ds.Get(s), ShouldBeNil)
256
257 So(numMemcacheItems(), ShouldEqual, 1)
258 for i := 0; i < 20; i++ {
259 So(ds.Get(s), ShouldBeNil)
260 }
261 So(numMemcacheItems(), ShouldEqual, 4)
262 })
263
264 Convey("per-key cache disablement", func() {
265 n := &noCacheObj{ID: "nurbs", Value: true}
266 So(ds.Put(n), ShouldBeNil)
267 So(ds.Get(n), ShouldBeNil)
268 So(numMemcacheItems(), ShouldEqual, 0)
269 })
270
271 Convey("per-model expiration", func() {
272 type model struct {
273 ID int64 `gae:"$id"`
274 DSCacheExp int64 `gae:"$dscache.expirati on,7"`
275
276 Value string
277 }
278
279 So(ds.Put(&model{ID: 1, Value: "mooo"}), ShouldB eNil)
280 So(ds.Get(&model{ID: 1}), ShouldBeNil)
281
282 itm := itmFor(0, ds.KeyForObj(&model{ID: 1}))
283 So(mc.Get(itm), ShouldBeNil)
284
285 clk.Add(10 * time.Second)
286 So(mc.Get(itm), ShouldEqual, memcache.ErrCacheMi ss)
287 })
288 })
289
290 Convey("screw cases", func() {
291 Convey("memcache contains bogus value (simulated failed AddMulti)", func() {
292 o := &object{ID: 1, Value: "spleen"}
293 So(ds.Put(o), ShouldBeNil)
294
295 sekret := []byte("I am a banana")
296 itm := itmFor(0, ds.KeyForObj(o)).SetValue(sekre t)
297 So(mc.Set(itm), ShouldBeNil)
298
299 o = &object{ID: 1}
300 So(ds.Get(o), ShouldBeNil)
301 So(o.Value, ShouldEqual, "spleen")
302
303 So(mc.Get(itm), ShouldBeNil)
304 So(itm.Flags(), ShouldEqual, ItemUKNONWN)
305 So(itm.Value(), ShouldResemble, sekret)
306 })
307
308 Convey("memcache contains bogus value (corrupt entry)", func() {
309 o := &object{ID: 1, Value: "spleen"}
310 So(ds.Put(o), ShouldBeNil)
311
312 sekret := []byte("I am a banana")
313 itm := (itmFor(0, ds.KeyForObj(o)).
314 SetValue(sekret).
315 SetFlags(uint32(ItemHasData)))
316 So(mc.Set(itm), ShouldBeNil)
317
318 o = &object{ID: 1}
319 So(ds.Get(o), ShouldBeNil)
320 So(o.Value, ShouldEqual, "spleen")
321
322 So(mc.Get(itm), ShouldBeNil)
323 So(itm.Flags(), ShouldEqual, ItemHasData)
324 So(itm.Value(), ShouldResemble, sekret)
325 })
326
327 Convey("other entity has the lock", func() {
328 o := &object{ID: 1, Value: "spleen"}
329 So(ds.Put(o), ShouldBeNil)
330
331 sekret := []byte("r@vmarod!#)%9T")
332 itm := (itmFor(0, ds.KeyForObj(o)).
333 SetValue(sekret).
334 SetFlags(uint32(ItemHasLock)))
335 So(mc.Set(itm), ShouldBeNil)
336
337 o = &object{ID: 1}
338 So(ds.Get(o), ShouldBeNil)
339 So(o.Value, ShouldEqual, "spleen")
340
341 So(mc.Get(itm), ShouldBeNil)
342 So(itm.Flags(), ShouldEqual, ItemHasLock)
343 So(itm.Value(), ShouldResemble, sekret)
344 })
345
346 Convey("massive entities can't be cached", func() {
347 o := &object{ID: 1, Value: "spleen"}
348 mr := mathrand.Get(c)
349 numRounds := (internalValueSizeLimit / 8) * 2
350 buf := bytes.Buffer{}
351 for i := 0; i < numRounds; i++ {
352 So(binary.Write(&buf, binary.LittleEndia n, mr.Int63()), ShouldBeNil)
353 }
354 o.BigData = buf.Bytes()
355 Println(buf.Len())
356 So(ds.Put(o), ShouldBeNil)
357
358 o.BigData = nil
359 So(ds.Get(o), ShouldBeNil)
360
361 itm := itmFor(0, ds.KeyForObj(o))
362 So(mc.Get(itm), ShouldBeNil)
363
364 // Is locked until the next put, forcing all acc ess to the datastore.
365 So(itm.Value(), ShouldResemble, []byte("M\x90\xc 1\xe8z$\xe3<"))
366 So(itm.Flags(), ShouldEqual, ItemHasLock)
367
368 o.BigData = []byte("hi :)")
369 So(ds.Put(o), ShouldBeNil)
370 So(ds.Get(o), ShouldBeNil)
371
372 So(mc.Get(itm), ShouldBeNil)
373 So(itm.Flags(), ShouldEqual, ItemHasData)
374 })
375
376 })
377
378 Convey("misc", func() {
379 Convey("verify numShards caps at MaxShards", func() {
380 sc := supportContext{shardsForKey: shardsForKey}
381 So(sc.numShards(ds.KeyForObj(&shardObj{ID: 9001} )), ShouldEqual, MaxShards)
382 })
383
384 Convey("CompressionType.String", func() {
385 So(NoCompression.String(), ShouldEqual, "NoCompr ession")
386 So(ZlibCompression.String(), ShouldEqual, "ZlibC ompression")
387 So(CompressionType(100).String(), ShouldEqual, " UNKNOWN_CompressionType(100)")
388 })
389 })
390 })
391 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698