diff --git a/lib/store/options.go b/lib/store/options.go index 361de0be..5e58a7d0 100644 --- a/lib/store/options.go +++ b/lib/store/options.go @@ -8,6 +8,7 @@ import ( type Option func(o *Options) type Options struct { + SynchronousSet bool Cost int64 Expiration time.Duration Tags []string @@ -47,6 +48,14 @@ func WithCost(cost int64) Option { } } +// WithSynchronousSet allows setting the behavior when setting a value, whether to wait until all buffered writes have been applied or not. +// Currently to be used by Ristretto library only. +func WithSynchronousSet() Option { + return func(o *Options) { + o.SynchronousSet = true + } +} + // WithExpiration allows to specify an expiration time when setting a value. func WithExpiration(expiration time.Duration) Option { return func(o *Options) { diff --git a/store/ristretto/ristretto.go b/store/ristretto/ristretto.go index 66f6be94..146ae8db 100644 --- a/store/ristretto/ristretto.go +++ b/store/ristretto/ristretto.go @@ -23,6 +23,7 @@ type RistrettoClientInterface interface { SetWithTTL(key, value any, cost int64, ttl time.Duration) bool Del(key any) Clear() + Wait() } // RistrettoStore is a store for Ristretto (memory) library @@ -71,6 +72,10 @@ func (s *RistrettoStore) Set(ctx context.Context, key any, value any, options .. return err } + if opts.SynchronousSet { + s.client.Wait() + } + if tags := opts.Tags; len(tags) > 0 { s.setTags(ctx, key, tags) } diff --git a/store/ristretto/ristretto_bench_test.go b/store/ristretto/ristretto_bench_test.go index 640fbe16..5d153381 100644 --- a/store/ristretto/ristretto_bench_test.go +++ b/store/ristretto/ristretto_bench_test.go @@ -21,7 +21,33 @@ func BenchmarkRistrettoSet(b *testing.B) { if err != nil { panic(err) } - store := NewRistretto(client, nil) + store := NewRistretto(client) + + for k := 0.; k <= 10; k++ { + n := int(math.Pow(2, k)) + b.Run(fmt.Sprintf("%d", n), func(b *testing.B) { + for i := 0; i < b.N*n; i++ { + key := fmt.Sprintf("test-%d", n) + value := []byte(fmt.Sprintf("value-%d", n)) + + store.Set(ctx, key, value, lib_store.WithTags([]string{fmt.Sprintf("tag-%d", n)})) + } + }) + } +} + +func BenchmarkRistrettoSetWithSynchronousSet(b *testing.B) { + ctx := context.Background() + + client, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1000, + MaxCost: 100, + BufferItems: 64, + }) + if err != nil { + panic(err) + } + store := NewRistretto(client, lib_store.WithSynchronousSet()) for k := 0.; k <= 10; k++ { n := int(math.Pow(2, k)) @@ -47,12 +73,12 @@ func BenchmarkRistrettoGet(b *testing.B) { if err != nil { panic(err) } - store := NewRistretto(client, nil) + store := NewRistretto(client) key := "test" value := []byte("value") - store.Set(ctx, key, value, nil) + store.Set(ctx, key, value) for k := 0.; k <= 10; k++ { n := int(math.Pow(2, k)) diff --git a/store/ristretto/ristretto_mock.go b/store/ristretto/ristretto_mock.go index 4b11159c..e091737a 100644 --- a/store/ristretto/ristretto_mock.go +++ b/store/ristretto/ristretto_mock.go @@ -86,3 +86,15 @@ func (mr *MockRistrettoClientInterfaceMockRecorder) SetWithTTL(key, value, cost, mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWithTTL", reflect.TypeOf((*MockRistrettoClientInterface)(nil).SetWithTTL), key, value, cost, ttl) } + +// Wait mocks base method. +func (m *MockRistrettoClientInterface) Wait() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Wait") +} + +// Wait indicates an expected call of Wait. +func (mr *MockRistrettoClientInterfaceMockRecorder) Wait() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wait", reflect.TypeOf((*MockRistrettoClientInterface)(nil).Wait)) +} diff --git a/store/ristretto/ristretto_test.go b/store/ristretto/ristretto_test.go index 474866b0..4b5e8908 100644 --- a/store/ristretto/ristretto_test.go +++ b/store/ristretto/ristretto_test.go @@ -177,6 +177,28 @@ func TestRistrettoSetWhenError(t *testing.T) { assert.Equal(t, fmt.Errorf("An error has occurred while setting value '%v' on key '%v'", cacheValue, cacheKey), err) } +func TestRistrettoSetWithSynchronousSet(t *testing.T) { + // Given + ctrl := gomock.NewController(t) + + ctx := context.Background() + + cacheKey := "my-key" + cacheValue := []byte("my-cache-value") + + client := NewMockRistrettoClientInterface(ctrl) + client.EXPECT().SetWithTTL(cacheKey, cacheValue, int64(7), 0*time.Second).Return(true) + client.EXPECT().Wait() + + store := NewRistretto(client, lib_store.WithCost(7), lib_store.WithSynchronousSet()) + + // When + err := store.Set(ctx, cacheKey, cacheValue, lib_store.WithSynchronousSet()) + + // Then + assert.Nil(t, err) +} + func TestRistrettoSetWithTags(t *testing.T) { // Given ctrl := gomock.NewController(t)