@@ -20,17 +20,17 @@ import (
20
20
// If value is nil but deleted is false, it means the parent doesn't have the
21
21
// key. (No need to delete upon Write())
22
22
type cValue struct {
23
- value []byte
24
- deleted bool
25
- dirty bool
23
+ value []byte
24
+ dirty bool
26
25
}
27
26
28
27
// Store wraps an in-memory cache around an underlying types.KVStore.
29
28
type Store struct {
30
29
mtx sync.Mutex
31
30
cache map [string ]* cValue
31
+ deleted map [string ]struct {}
32
32
unsortedCache map [string ]struct {}
33
- sortedCache * kv. List // always ascending sorted
33
+ sortedCache * dbm. MemDB // always ascending sorted
34
34
parent types.KVStore
35
35
}
36
36
@@ -40,8 +40,9 @@ var _ types.CacheKVStore = (*Store)(nil)
40
40
func NewStore (parent types.KVStore ) * Store {
41
41
return & Store {
42
42
cache : make (map [string ]* cValue ),
43
+ deleted : make (map [string ]struct {}),
43
44
unsortedCache : make (map [string ]struct {}),
44
- sortedCache : kv . NewList (),
45
+ sortedCache : dbm . NewMemDB (),
45
46
parent : parent ,
46
47
}
47
48
}
@@ -120,7 +121,7 @@ func (store *Store) Write() {
120
121
cacheValue := store .cache [key ]
121
122
122
123
switch {
123
- case cacheValue . deleted :
124
+ case store . isDeleted ( key ) :
124
125
store .parent .Delete ([]byte (key ))
125
126
case cacheValue .value == nil :
126
127
// Skip, it already doesn't exist in parent.
@@ -131,8 +132,9 @@ func (store *Store) Write() {
131
132
132
133
// Clear the cache
133
134
store .cache = make (map [string ]* cValue )
135
+ store .deleted = make (map [string ]struct {})
134
136
store .unsortedCache = make (map [string ]struct {})
135
- store .sortedCache = kv . NewList ()
137
+ store .sortedCache = dbm . NewMemDB ()
136
138
}
137
139
138
140
// CacheWrap implements CacheWrapper.
@@ -171,7 +173,7 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator {
171
173
}
172
174
173
175
store .dirtyItems (start , end )
174
- cache = newMemIterator (start , end , store .sortedCache , ascending )
176
+ cache = newMemIterator (start , end , store .sortedCache , store . deleted , ascending )
175
177
176
178
return newCacheMergeIterator (parent , cache , ascending )
177
179
}
@@ -180,7 +182,7 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator {
180
182
// from string -> []byte to speed up operations, it is not meant
181
183
// to be used generally, but for a specific pattern to check for available
182
184
// keys within a domain.
183
- func strToByte (s string ) []byte {
185
+ func strToBytes (s string ) []byte {
184
186
var b []byte
185
187
hdr := (* reflect .SliceHeader )(unsafe .Pointer (& b ))
186
188
hdr .Cap = len (s )
@@ -200,16 +202,33 @@ func byteSliceToStr(b []byte) string {
200
202
201
203
// Constructs a slice of dirty items, to use w/ memIterator.
202
204
func (store * Store ) dirtyItems (start , end []byte ) {
203
- unsorted := make ([]* kv.Pair , 0 )
204
-
205
205
n := len (store .unsortedCache )
206
- for key := range store .unsortedCache {
207
- if dbm .IsKeyInDomain (strToByte (key ), start , end ) {
206
+ unsorted := make ([]* kv.Pair , 0 )
207
+ // If the unsortedCache is too big, its costs too much to determine
208
+ // whats in the subset we are concerned about.
209
+ // If you are interleaving iterator calls with writes, this can easily become an
210
+ // O(N^2) overhead.
211
+ // Even without that, too many range checks eventually becomes more expensive
212
+ // than just not having the cache.
213
+ if n >= 1024 {
214
+ for key := range store .unsortedCache {
208
215
cacheValue := store .cache [key ]
209
216
unsorted = append (unsorted , & kv.Pair {Key : []byte (key ), Value : cacheValue .value })
210
217
}
218
+ } else {
219
+ // else do a linear scan to determine if the unsorted pairs are in the pool.
220
+ for key := range store .unsortedCache {
221
+ if dbm .IsKeyInDomain (strToBytes (key ), start , end ) {
222
+ cacheValue := store .cache [key ]
223
+ unsorted = append (unsorted , & kv.Pair {Key : []byte (key ), Value : cacheValue .value })
224
+ }
225
+ }
211
226
}
227
+ store .clearUnsortedCacheSubset (unsorted )
228
+ }
212
229
230
+ func (store * Store ) clearUnsortedCacheSubset (unsorted []* kv.Pair ) {
231
+ n := len (store .unsortedCache )
213
232
if len (unsorted ) == n { // This pattern allows the Go compiler to emit the map clearing idiom for the entire map.
214
233
for key := range store .unsortedCache {
215
234
delete (store .unsortedCache , key )
@@ -219,32 +238,21 @@ func (store *Store) dirtyItems(start, end []byte) {
219
238
delete (store .unsortedCache , byteSliceToStr (kv .Key ))
220
239
}
221
240
}
222
-
223
241
sort .Slice (unsorted , func (i , j int ) bool {
224
242
return bytes .Compare (unsorted [i ].Key , unsorted [j ].Key ) < 0
225
243
})
226
244
227
- for e := store .sortedCache .Front (); e != nil && len (unsorted ) != 0 ; {
228
- uitem := unsorted [0 ]
229
- sitem := e .Value
230
- comp := bytes .Compare (uitem .Key , sitem .Key )
231
-
232
- switch comp {
233
- case - 1 :
234
- unsorted = unsorted [1 :]
235
-
236
- store .sortedCache .InsertBefore (uitem , e )
237
- case 1 :
238
- e = e .Next ()
239
- case 0 :
240
- unsorted = unsorted [1 :]
241
- e .Value = uitem
242
- e = e .Next ()
245
+ for _ , item := range unsorted {
246
+ if item .Value == nil {
247
+ // deleted element, tracked by store.deleted
248
+ // setting arbitrary value
249
+ store .sortedCache .Set (item .Key , []byte {})
250
+ continue
251
+ }
252
+ err := store .sortedCache .Set (item .Key , item .Value )
253
+ if err != nil {
254
+ panic (err )
243
255
}
244
- }
245
-
246
- for _ , kvp := range unsorted {
247
- store .sortedCache .PushBack (kvp )
248
256
}
249
257
}
250
258
@@ -253,12 +261,22 @@ func (store *Store) dirtyItems(start, end []byte) {
253
261
254
262
// Only entrypoint to mutate store.cache.
255
263
func (store * Store ) setCacheValue (key , value []byte , deleted bool , dirty bool ) {
256
- store .cache [string (key )] = & cValue {
257
- value : value ,
258
- deleted : deleted ,
259
- dirty : dirty ,
264
+ keyStr := byteSliceToStr (key )
265
+ store .cache [keyStr ] = & cValue {
266
+ value : value ,
267
+ dirty : dirty ,
268
+ }
269
+ if deleted {
270
+ store .deleted [keyStr ] = struct {}{}
271
+ } else {
272
+ delete (store .deleted , keyStr )
260
273
}
261
274
if dirty {
262
275
store .unsortedCache [string (key )] = struct {}{}
263
276
}
264
277
}
278
+
279
+ func (store * Store ) isDeleted (key string ) bool {
280
+ _ , ok := store .deleted [key ]
281
+ return ok
282
+ }
0 commit comments