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

Side by Side Diff: filter/dscache/support.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 "fmt"
9 "math/rand"
10 "time"
11
12 ds "github.com/luci/gae/service/datastore"
13 "github.com/luci/gae/service/memcache"
14 "github.com/luci/luci-go/common/logging"
15 )
16
17 type supportContext struct {
18 aid string
19 ns string
20
21 log logging.Logger
22 mc memcache.Interface
23 mr *rand.Rand
24 shardsForKey func(ds.Key) int
25 }
26
27 func (s *supportContext) numShards(k ds.Key) int {
28 ret := DefaultShards
29 if s.shardsForKey != nil {
30 ret = s.shardsForKey(k)
31 }
32 if ret < 1 {
33 return 0 // disable caching entirely
34 }
35 if ret > MaxShards {
36 ret = MaxShards
37 }
38 return ret
39 }
40
41 func (s *supportContext) mkRandKeys(keys []ds.Key, metas ds.MultiMetaGetter) []s tring {
42 ret := []string(nil)
43 for i, key := range keys {
44 if !metas.GetMetaDefault(i, CacheEnableMeta, true).(bool) {
45 continue
46 }
47 shards := s.numShards(key)
48 if shards == 0 {
49 continue
50 }
51 if ret == nil {
52 ret = make([]string, len(keys))
53 }
54 ret[i] = MakeMemcacheKey(s.mr.Intn(shards), key)
Vadim Sh. 2015/08/06 01:23:34 so shards reduce contention on reads by making wri
iannucci 2015/08/06 01:54:01 Right
55 }
56 if len(ret) == 0 {
dnj 2015/08/05 18:32:18 Is this necessary? Either it is allocated and gets
iannucci 2015/08/06 01:54:02 o rite
57 ret = nil
58 }
59 return ret
60 }
61
62 func (s *supportContext) mkAllKeys(keys []ds.Key) []string {
63 size := 0
64 nums := make([]int, len(keys))
65 for i, k := range keys {
66 shards := s.numShards(k)
67 nums[i] = shards
68 size += shards
69 }
70 if size == 0 {
71 return nil
72 }
73 ret := make([]string, 0, size)
74 for i, key := range keys {
75 keySuffix := HashKey(key)
76 for shard := 0; shard < nums[i]; shard++ {
77 ret = append(ret, fmt.Sprintf(KeyFormat, shard, keySuffi x))
78 }
79 }
80 return ret
81 }
82
83 // crappyNonce creates a really crappy nonce using math/rand. This is generally
Vadim Sh. 2015/08/06 01:23:34 lol
84 // unacceptable for cryptographic purposes, but since mathrand is the only
85 // mocked randomness source, we use that.
dnj 2015/08/05 18:32:18 Worth mentioning that we're using it for sharding,
iannucci 2015/08/06 01:54:01 ya, did
86 //
87 // Do not use this function for anything other than mkRandLockItems or your hair
88 // will fall out. You've been warned.
89 //
90 // TODO(riannucci): make this use crypto/rand instead
Vadim Sh. 2015/08/06 01:23:34 do you see any exploitation possibilities? Not all
iannucci 2015/08/06 01:54:01 Yeah it's actually fine... just felt dirty extract
91 func (s *supportContext) crappyNonce() []byte {
92 ret := make([]byte, NonceUint32s*4)
93 for w := uint(0); w < NonceUint32s; w++ {
94 word := s.mr.Uint32()
95 for i := uint(0); i < 4; i++ {
96 ret[(w*4)+i] = byte(word >> (8 * i))
97 }
98 }
99 return ret
100 }
101
102 func (s *supportContext) mutation(keys []ds.Key, f func() error) error {
103 lockItems, lockKeys := s.mkAllLockItems(keys)
104 if lockItems == nil {
105 return f()
106 }
107 if err := s.mc.SetMulti(lockItems); err != nil {
108 s.log.Errorf("dscache: mc.SetMulti: %s", err)
dnj 2015/08/05 18:32:18 This could be a lot of logging if memcache is not
iannucci 2015/08/06 01:54:02 changed to a hard failz
109 }
110 err := f()
dnj 2015/08/05 18:32:18 if err := f(); err == nil { ... }
iannucci 2015/08/06 01:54:01 Nah, I return it in the else case.
111 if err == nil {
112 if err := s.mc.DeleteMulti(lockKeys); err != nil {
113 s.log.Errorf("dscache: mc.DeleteMulti: %s", err)
dnj 2015/08/05 18:32:18 (same)
iannucci 2015/08/06 01:54:02 a warning now
114 }
115 }
116 return err
117 }
118
119 func (s *supportContext) mkRandLockItems(keys []ds.Key, metas ds.MultiMetaGetter ) ([]memcache.Item, []byte) {
120 mcKeys := s.mkRandKeys(keys, metas)
121 if len(mcKeys) == 0 {
122 return nil, nil
123 }
124 nonce := s.crappyNonce()
125 ret := make([]memcache.Item, len(mcKeys))
126 for i, k := range mcKeys {
127 if k == "" {
Vadim Sh. 2015/08/06 01:23:34 is it possible?
iannucci 2015/08/06 01:54:01 Yeah. This is done if mkRandKeys detects that some
128 continue
129 }
130 ret[i] = (s.mc.NewItem(k).
131 SetFlags(uint32(ItemHasLock)).
Vadim Sh. 2015/08/06 01:23:34 ah, I didn't know about flags. Cool.
132 SetExpiration(time.Second * time.Duration(LockTimeSecond s)).
Vadim Sh. 2015/08/06 01:23:34 why not define LockTimeSeconds to be of type time.
iannucci 2015/08/06 01:54:01 Because SetExpiration truncates anything shorter t
133 SetValue(nonce))
134 }
135 return ret, nonce
136 }
137
138 func (s *supportContext) mkAllLockItems(keys []ds.Key) ([]memcache.Item, []strin g) {
139 mcKeys := s.mkAllKeys(keys)
140 if mcKeys == nil {
141 return nil, nil
142 }
143 ret := make([]memcache.Item, len(mcKeys))
144 for i := range ret {
145 ret[i] = (s.mc.NewItem(mcKeys[i]).
146 SetFlags(uint32(ItemHasLock)).
147 SetExpiration(time.Second * time.Duration(LockTimeSecond s)))
148 }
149 return ret, mcKeys
150 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698