Skip to content

Commit 0c64902

Browse files
authored
feat: [cache/memory] use ristretto as memory backend (#917)
* feat: [cache/memory] use dgraph-io/ristretto as memory backend Signed-off-by: Chris Randles <randles.chris@gmail.com> --------- Signed-off-by: Chris Randles <randles.chris@gmail.com>
1 parent d197bcf commit 0c64902

File tree

27 files changed

+324
-133
lines changed

27 files changed

+324
-133
lines changed

examples/conf/example.full.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ frontend:
8484
# # max_size_backoff_objects indicates how far under max_size_objects the cache size must be to complete object-size-based eviction exercise. default is 100
8585
# max_size_backoff_objects: 100
8686

87+
# ## Configuration options when using a Memory Cache #######
88+
# memory:
89+
# # max_size_bytes is the maximum total byte cost will admit to the cache.
90+
# # unlike index.max_size_bytes (a soft eviction threshold), this is enforced at
91+
# # admission time. default is 512MB
92+
# max_size_bytes: 536870912
93+
# # num_counters is the number of keys to track for admission and eviction decisions. This is not a hard limit but a max sampling size.
94+
# # Recommended to use ~10x the number of unique keys you expect to hold for full utilization.
95+
# # default is 500000
96+
# num_counters: 500000
97+
8798
# ## Configuration options when using a Redis Cache
8899
# redis:
89100
# # client_type indicates which kind of Redis client to use. Options are: standard, cluster and sentinel

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/alicebob/miniredis/v2 v2.35.0
77
github.com/andybalholm/brotli v1.2.0
88
github.com/dgraph-io/badger/v4 v4.9.1
9+
github.com/dgraph-io/ristretto/v2 v2.4.0
910
github.com/golang/snappy v1.0.0
1011
github.com/influxdata/influxdb v1.12.2
1112
github.com/influxdata/influxql v1.4.1
@@ -85,7 +86,6 @@ require (
8586
github.com/dave/dst v0.27.3 // indirect
8687
github.com/davecgh/go-spew v1.1.1 // indirect
8788
github.com/denis-tingaikin/go-header v0.5.0 // indirect
88-
github.com/dgraph-io/ristretto/v2 v2.4.0 // indirect
8989
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
9090
github.com/dlclark/regexp2 v1.11.5 // indirect
9191
github.com/dustin/go-humanize v1.0.1 // indirect

pkg/backends/alb/mech/nlm/newest_last_modified_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,3 @@ func TestHandleNewestResponse(t *testing.T) {
6565
t.Error("expected 200 got", w.Code)
6666
}
6767
}
68-

pkg/backends/prometheus/model/alerts_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ import (
2828
"github.com/trickstercache/trickster/v2/pkg/proxy/response/merge"
2929
)
3030

31-
var (
32-
testResources = request.NewResources(nil, nil, nil, nil, nil, nil)
33-
)
31+
var testResources = request.NewResources(nil, nil, nil, nil, nil, nil)
3432

3533
func TestCalculateHash(t *testing.T) {
3634
a := &WFAlert{

pkg/backends/prometheus/model/vector_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,4 @@ func TestMergeAndWriteVector(t *testing.T) {
8181
if w.Code != http.StatusOK {
8282
t.Errorf("expected %d got %d", http.StatusOK, w.Code)
8383
}
84-
8584
}

pkg/cache/index/client_test.go

Lines changed: 127 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,22 @@ import (
2727
"github.com/trickstercache/trickster/v2/pkg/cache/filesystem"
2828
fso "github.com/trickstercache/trickster/v2/pkg/cache/filesystem/options"
2929
"github.com/trickstercache/trickster/v2/pkg/cache/index/options"
30-
"github.com/trickstercache/trickster/v2/pkg/cache/memory"
3130
co "github.com/trickstercache/trickster/v2/pkg/cache/options"
3231
"github.com/trickstercache/trickster/v2/pkg/cache/status"
3332
)
3433

3534
func TestIndexedClient(t *testing.T) {
36-
const provider = "memory"
37-
38-
// init memory cache client
39-
cacheConfig := co.Options{Provider: provider}
40-
mc := memory.New("test", &cacheConfig)
41-
4235
t.Run("basic", func(t *testing.T) {
36+
const provider = "filesystem"
37+
38+
// init filesystem cache client
39+
cacheConfig := co.Options{
40+
Provider: provider,
41+
Filesystem: &fso.Options{
42+
CachePath: t.TempDir(),
43+
},
44+
}
45+
fsc := filesystem.NewCache("test", &cacheConfig)
4346
// init indexed client
4447
ic := NewIndexedClient("test", provider, &options.Options{
4548
ReapInterval: time.Second * time.Duration(10),
@@ -49,7 +52,7 @@ func TestIndexedClient(t *testing.T) {
4952
MaxSizeBytes: 100,
5053
MaxSizeBackoffBytes: 30,
5154
IndexExpiry: 1 * time.Hour,
52-
}, mc)
55+
}, fsc)
5356
t.Log("basic")
5457
state := getIndexedClientState(ic)
5558
require.Equal(t, int64(0), state.ObjectCount)
@@ -65,7 +68,8 @@ func TestIndexedClient(t *testing.T) {
6568

6669
// store & retrieve
6770
val := []byte("bar")
68-
require.NoError(t, ic.Store(key, val, 0))
71+
ttl := 60 * time.Second
72+
require.NoError(t, ic.Store(key, val, ttl))
6973

7074
state = getIndexedClientState(ic)
7175
require.Equal(t, int64(1), state.ObjectCount)
@@ -95,6 +99,16 @@ func TestIndexedClient(t *testing.T) {
9599
})
96100

97101
t.Run("atime", func(t *testing.T) {
102+
const provider = "filesystem"
103+
104+
// init filesystem cache client
105+
cacheConfig := co.Options{
106+
Provider: provider,
107+
Filesystem: &fso.Options{
108+
CachePath: t.TempDir(),
109+
},
110+
}
111+
fsc := filesystem.NewCache("test", &cacheConfig)
98112
// init indexed client
99113
ic := NewIndexedClient("test", provider, &options.Options{
100114
ReapInterval: time.Second * time.Duration(10),
@@ -104,11 +118,12 @@ func TestIndexedClient(t *testing.T) {
104118
MaxSizeBytes: 100,
105119
MaxSizeBackoffBytes: 30,
106120
IndexExpiry: 1 * time.Hour,
107-
}, mc)
121+
}, fsc)
108122

109123
// store & retrieve
110124
val := []byte("bar")
111-
require.NoError(t, ic.Store("foo", val, 0))
125+
ttl := 60 * time.Second
126+
require.NoError(t, ic.Store("foo", val, ttl))
112127
// expect atime to be set
113128
o, ok := ic.Objects.Load("foo")
114129
require.True(t, ok)
@@ -202,6 +217,16 @@ func TestIndexedClient(t *testing.T) {
202217
// test the actual flush loop by forcing it to flush, this utilizes goroutines
203218
// and should detect more potential race conditions vs the existing flush tests that use
204219
// flush internal methods
220+
221+
// Create fresh filesystem cache for this subtest
222+
freshCacheConfig := co.Options{
223+
Provider: provider,
224+
Filesystem: &fso.Options{
225+
CachePath: t.TempDir(),
226+
},
227+
}
228+
freshFs := filesystem.NewCache("flushTest", &freshCacheConfig)
229+
ttl := 60 * time.Second
205230
ic1 := NewIndexedClient("flushTest", provider, &options.Options{
206231
ReapInterval: time.Second * 60 * 60 * 24,
207232
FlushInterval: time.Second * 60 * 60 * 24,
@@ -210,12 +235,12 @@ func TestIndexedClient(t *testing.T) {
210235
MaxSizeBytes: 100,
211236
MaxSizeBackoffBytes: 30,
212237
IndexExpiry: 1 * time.Hour,
213-
}, mc)
238+
}, freshFs)
214239
defer ic1.Close()
215240
for i := range 5 {
216241
index := fmt.Sprintf("%d", i)
217242
key := "key." + index
218-
require.NoError(t, ic1.Store(key, []byte("value1."+index), 0))
243+
require.NoError(t, ic1.Store(key, []byte("value1."+index), ttl))
219244
}
220245
_, s, err := ic1.Client.Retrieve(IndexKey)
221246
require.Equal(t, cache.ErrKNF, err)
@@ -231,6 +256,16 @@ func TestIndexedClient(t *testing.T) {
231256
// test the actual reap loop by forcing it to reap, this utilizes goroutines
232257
// and should detect more potential race conditions vs the existing reap tests that use
233258
// reap internal methods
259+
260+
// Create fresh filesystem cache for this subtest
261+
freshCacheConfig := co.Options{
262+
Provider: provider,
263+
Filesystem: &fso.Options{
264+
CachePath: t.TempDir(),
265+
},
266+
}
267+
freshFs := filesystem.NewCache("reapTest", &freshCacheConfig)
268+
ttl := 60 * time.Second
234269
ic2 := NewIndexedClient("reapTest", provider, &options.Options{
235270
ReapInterval: time.Second * 60 * 60 * 24,
236271
FlushInterval: time.Second * 60 * 60 * 24,
@@ -239,14 +274,14 @@ func TestIndexedClient(t *testing.T) {
239274
MaxSizeBytes: 10000,
240275
MaxSizeBackoffBytes: 300,
241276
IndexExpiry: 1 * time.Hour,
242-
}, mc)
277+
}, freshFs)
243278
defer ic2.Close()
244279

245280
// write 5 objects, expect 5 objects
246281
for i := range 5 {
247282
index := fmt.Sprintf("%d", i)
248283
key := "key." + index
249-
require.NoError(t, ic2.Store(key, []byte("value1."+index), 0))
284+
require.NoError(t, ic2.Store(key, []byte("value1."+index), ttl))
250285
}
251286
state := getIndexedClientState(ic2)
252287
require.Equal(t, int64(5), state.ObjectCount)
@@ -262,7 +297,7 @@ func TestIndexedClient(t *testing.T) {
262297
for i := range 5 {
263298
index := fmt.Sprintf("%d", i)
264299
key := "another.key." + index
265-
require.NoError(t, ic2.Store(key, []byte("value1."+index), 0))
300+
require.NoError(t, ic2.Store(key, []byte("value1."+index), ttl))
266301
}
267302
state = getIndexedClientState(ic2)
268303
require.Equal(t, int64(10), state.ObjectCount)
@@ -276,90 +311,100 @@ func TestIndexedClient(t *testing.T) {
276311
})
277312
})
278313

279-
/* converting */
280-
// init indexed client
281-
ic := NewIndexedClient("test", provider, &options.Options{
282-
ReapInterval: time.Second * time.Duration(10),
283-
FlushInterval: time.Second * time.Duration(10),
284-
MaxSizeObjects: 5,
285-
MaxSizeBackoffObjects: 3,
286-
MaxSizeBytes: 100,
287-
MaxSizeBackoffBytes: 30,
288-
IndexExpiry: 1 * time.Hour,
289-
}, mc, func(ico *IndexedClientOptions) {
290-
ico.NeedsFlushInterval = true
291-
ico.NeedsReapInterval = true
292-
})
293-
t.Log("wip, converting prior test")
294-
ttl := 60 * time.Second
314+
t.Run("reap eviction", func(t *testing.T) {
315+
const provider = "filesystem"
295316

296-
// add expired key to cover the case that the reaper remove it
297-
ic.Store("test.1", []byte("test_value"), ttl)
317+
// init filesystem cache client
318+
cacheConfig := co.Options{
319+
Provider: provider,
320+
Filesystem: &fso.Options{
321+
CachePath: t.TempDir(),
322+
},
323+
}
324+
fsc := filesystem.NewCache("test", &cacheConfig)
325+
// init indexed client
326+
ic := NewIndexedClient("test", provider, &options.Options{
327+
ReapInterval: time.Second * time.Duration(10),
328+
FlushInterval: time.Second * time.Duration(10),
329+
MaxSizeObjects: 5,
330+
MaxSizeBackoffObjects: 3,
331+
MaxSizeBytes: 100,
332+
MaxSizeBackoffBytes: 30,
333+
IndexExpiry: 1 * time.Hour,
334+
}, fsc, func(ico *IndexedClientOptions) {
335+
ico.NeedsFlushInterval = true
336+
ico.NeedsReapInterval = true
337+
})
338+
ttl := 60 * time.Second
298339

299-
// add key with no expiration which should not be reaped
300-
ic.Store("test.2", []byte("test_value"), ttl)
340+
// add expired key to cover the case that the reaper remove it
341+
ic.Store("test.1", []byte("test_value"), ttl)
301342

302-
// add key with future expiration which should not be reaped
303-
ic.Store("test.3", []byte("test_value"), ttl)
343+
// add key with no expiration which should not be reaped
344+
ic.Store("test.2", []byte("test_value"), ttl)
304345

305-
// trigger a reap that will only remove expired elements but not size down the full cache
306-
keyCount := len(ic.Objects.Keys())
307-
ic.reap()
308-
require.Equal(t, keyCount, len(ic.Objects.Keys()))
346+
// add key with future expiration which should not be reaped
347+
ic.Store("test.3", []byte("test_value"), ttl)
309348

310-
state := getIndexedClientState(ic)
311-
require.Equal(t, int64(3), state.ObjectCount)
312-
require.Equal(t, int64(30), state.CacheSize)
313-
require.Len(t, state.Objects, 3)
349+
// trigger a reap that will only remove expired elements but not size down the full cache
350+
keyCount := len(ic.Objects.Keys())
351+
ic.reap()
352+
require.Equal(t, keyCount, len(ic.Objects.Keys()))
314353

315-
// add key with future expiration which should not be reaped
316-
ic.Store("test.4", []byte("test_value"), ttl)
354+
state := getIndexedClientState(ic)
355+
require.Equal(t, int64(3), state.ObjectCount)
356+
require.Equal(t, int64(30), state.CacheSize)
357+
require.Len(t, state.Objects, 3)
317358

318-
// add key with future expiration which should not be reaped
319-
ic.Store("test.5", []byte("test_value"), ttl)
359+
// add key with future expiration which should not be reaped
360+
ic.Store("test.4", []byte("test_value"), ttl)
320361

321-
// add key with future expiration which should not be reaped
322-
ic.Store("test.6", []byte("test_value"), ttl)
362+
// add key with future expiration which should not be reaped
363+
ic.Store("test.5", []byte("test_value"), ttl)
323364

324-
// trigger size-based reap eviction of some elements
325-
keyCount = len(ic.Objects.Keys())
326-
require.Equal(t, 6, keyCount)
327-
ic.reap()
365+
// add key with future expiration which should not be reaped
366+
ic.Store("test.6", []byte("test_value"), ttl)
328367

329-
_, ok := ic.Objects.Load("test.1")
330-
require.False(t, ok, "expected key %s to be missing", "test.1")
368+
// trigger size-based reap eviction of some elements
369+
keyCount = len(ic.Objects.Keys())
370+
require.Equal(t, 6, keyCount)
371+
ic.reap()
331372

332-
_, ok = ic.Objects.Load("test.2")
333-
require.False(t, ok, "expected key test.2 to be missing")
373+
_, ok := ic.Objects.Load("test.1")
374+
require.False(t, ok, "expected key %s to be missing", "test.1")
334375

335-
_, ok = ic.Objects.Load("test.3")
336-
require.False(t, ok, "expected key test.3 to be missing")
376+
_, ok = ic.Objects.Load("test.2")
377+
require.False(t, ok, "expected key test.2 to be missing")
337378

338-
_, ok = ic.Objects.Load("test.4")
339-
require.False(t, ok, "expected key test.4 to be missing")
379+
_, ok = ic.Objects.Load("test.3")
380+
require.False(t, ok, "expected key test.3 to be missing")
340381

341-
_, ok = ic.Objects.Load("test.5")
342-
require.True(t, ok, "expected key test.5 to be present")
382+
_, ok = ic.Objects.Load("test.4")
383+
require.False(t, ok, "expected key test.4 to be missing")
343384

344-
_, ok = ic.Objects.Load("test.6")
345-
require.True(t, ok, "expected key test.6 to be present")
385+
_, ok = ic.Objects.Load("test.5")
386+
require.True(t, ok, "expected key test.5 to be present")
346387

347-
// add key with large body to reach byte size threshold
348-
ic.Store("test.7", []byte("test_value00000000000000000000000000000000000000000000000000000000000000000000000000000"), ttl)
388+
_, ok = ic.Objects.Load("test.6")
389+
require.True(t, ok, "expected key test.6 to be present")
349390

350-
// trigger a byte-based reap
351-
keyCount = len(ic.Objects.Keys())
352-
require.Equal(t, 3, keyCount)
353-
ic.reap()
354-
require.Len(t, ic.Objects.Keys(), 0)
391+
// add key with large body to reach byte size threshold
392+
ic.Store("test.7", []byte("test_value00000000000000000000000000000000000000000000000000000000000000000000000000000"), ttl)
355393

356-
// expect index to be empty
357-
objects := ic.Objects.ToObjects()
358-
require.Len(t, objects, 0)
359-
state = getIndexedClientState(ic)
360-
require.Len(t, state.Objects, 0)
361-
require.Equal(t, int64(0), state.ObjectCount)
362-
require.Equal(t, int64(0), state.CacheSize)
394+
// trigger a byte-based reap
395+
keyCount = len(ic.Objects.Keys())
396+
require.Equal(t, 3, keyCount)
397+
ic.reap()
398+
require.Len(t, ic.Objects.Keys(), 0)
399+
400+
// expect index to be empty
401+
objects := ic.Objects.ToObjects()
402+
require.Len(t, objects, 0)
403+
state = getIndexedClientState(ic)
404+
require.Len(t, state.Objects, 0)
405+
require.Equal(t, int64(0), state.ObjectCount)
406+
require.Equal(t, int64(0), state.CacheSize)
407+
})
363408
}
364409

365410
type indexedClientState struct {

0 commit comments

Comments
 (0)