From d38a813e33548733dab958e9d84620fbb39cdb16 Mon Sep 17 00:00:00 2001 From: Rueian Date: Sat, 10 Dec 2022 23:11:43 +0800 Subject: [PATCH] Make rueidis store to be more user-friendly and be more performant, 1. Make Get() and GetWithTTL() return go string instead of rueidis.RedisResult to be more user-friendly. 2. Make Set() only accept go string value, since Get() also returns go string. 3. Make GetWithTTL() retrive client-side TTL without additional network roundtrip. 4. Pipeline SADD and EXPIRE while setting key tags to reduce network roundtrips. 5. Not to cache SMEMBERS while doing Invalidate(), since it will also be invalidated soon. --- store/rueidis/go.mod | 2 +- store/rueidis/go.sum | 4 +- store/rueidis/rueidis.go | 62 +++++++++++++---------------- store/rueidis/rueidis_bench_test.go | 4 +- store/rueidis/rueidis_test.go | 17 +++++--- 5 files changed, 43 insertions(+), 46 deletions(-) diff --git a/store/rueidis/go.mod b/store/rueidis/go.mod index 308919e1..9f1ba859 100644 --- a/store/rueidis/go.mod +++ b/store/rueidis/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/eko/gocache/lib/v4 v4.1.1 github.com/golang/mock v1.6.0 - github.com/rueian/rueidis v0.0.86 + github.com/rueian/rueidis v0.0.89 github.com/stretchr/testify v1.8.1 ) diff --git a/store/rueidis/go.sum b/store/rueidis/go.sum index 4516a837..d0d537a6 100644 --- a/store/rueidis/go.sum +++ b/store/rueidis/go.sum @@ -7,8 +7,8 @@ github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rueian/rueidis v0.0.86 h1:RdzZzYnECg27zwdBHL2JN1XPVfaZclSjx6gzAQuCF/o= -github.com/rueian/rueidis v0.0.86/go.mod h1:LiKWMM/QnILwRfDZIhSIXi4vQqZ/UZy4+/aNkSCt8XA= +github.com/rueian/rueidis v0.0.89 h1:Q2TbuNXMJ2d2NegQ47uOoGOGPZLQwRuL0oX/dAlCh6k= +github.com/rueian/rueidis v0.0.89/go.mod h1:LiKWMM/QnILwRfDZIhSIXi4vQqZ/UZy4+/aNkSCt8XA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/store/rueidis/rueidis.go b/store/rueidis/rueidis.go index 7fb9911a..6c261430 100644 --- a/store/rueidis/rueidis.go +++ b/store/rueidis/rueidis.go @@ -21,10 +21,8 @@ const ( // RueidisStore is a store for Redis type RueidisStore struct { - client rueidis.Client - options *lib_store.Options - cacheCompat rueidiscompat.CacheCompat - compat rueidiscompat.Cmdable + client rueidis.Client + options *lib_store.Options } // NewRueidis creates a new store to Redis instance(s) @@ -37,43 +35,39 @@ func NewRueidis(client rueidis.Client, options ...lib_store.Option) *RueidisStor } return &RueidisStore{ - client: client, - cacheCompat: rueidiscompat.NewAdapter(client).Cache(appliedOptions.ClientSideCacheExpiration), - compat: rueidiscompat.NewAdapter(client), - options: appliedOptions, + client: client, + options: appliedOptions, } } // Get returns data stored from a given key func (s *RueidisStore) Get(ctx context.Context, key any) (any, error) { - object := s.client.DoCache(ctx, s.client.B().Get().Key(key.(string)).Cache(), s.options.ClientSideCacheExpiration) - if object.RedisError() != nil && object.RedisError().IsNil() { - return nil, lib_store.NotFoundWithCause(object.Error()) + cmd := s.client.B().Get().Key(key.(string)).Cache() + res := s.client.DoCache(ctx, cmd, s.options.ClientSideCacheExpiration) + str, err := res.ToString() + if rueidis.IsRedisNil(err) { + err = lib_store.NotFoundWithCause(err) } - return object, object.Error() + return str, err } // GetWithTTL returns data stored from a given key and its corresponding TTL func (s *RueidisStore) GetWithTTL(ctx context.Context, key any) (any, time.Duration, error) { - // get object first - object, err := s.Get(ctx, key) - if err != nil { - return nil, 0, err + cmd := s.client.B().Get().Key(key.(string)).Cache() + res := s.client.DoCache(ctx, cmd, s.options.ClientSideCacheExpiration) + str, err := res.ToString() + if rueidis.IsRedisNil(err) { + err = lib_store.NotFoundWithCause(err) } - - // get TTL and return - ttl, err := s.cacheCompat.TTL(ctx, key.(string)).Result() - if err != nil { - return nil, 0, err - } - - return object, ttl, err + return str, time.Duration(res.CacheTTL()) * time.Second, err } // Set defines data in Redis for given key identifier func (s *RueidisStore) Set(ctx context.Context, key any, value any, options ...lib_store.Option) error { opts := lib_store.ApplyOptionsWithDefault(s.options, options...) - err := s.compat.Set(ctx, key.(string), value, opts.Expiration).Err() + ttl := int64(opts.Expiration.Seconds()) + cmd := s.client.B().Set().Key(key.(string)).Value(value.(string)).ExSeconds(ttl).Build() + err := s.client.Do(ctx, cmd).Error() if err != nil { return err } @@ -86,17 +80,19 @@ func (s *RueidisStore) Set(ctx context.Context, key any, value any, options ...l } func (s *RueidisStore) setTags(ctx context.Context, key any, tags []string) { + ttl := 720 * time.Hour for _, tag := range tags { tagKey := fmt.Sprintf(RueidisTagPattern, tag) - s.compat.SAdd(ctx, tagKey, key.(string)) - s.compat.Expire(ctx, tagKey, 720*time.Hour) + s.client.DoMulti(ctx, + s.client.B().Sadd().Key(tagKey).Member(key.(string)).Build(), + s.client.B().Expire().Key(tagKey).Seconds(int64(ttl.Seconds())).Build(), + ) } } // Delete removes data from Redis for given key identifier func (s *RueidisStore) Delete(ctx context.Context, key any) error { - _, err := s.compat.Del(ctx, key.(string)).Result() - return err + return s.client.Do(ctx, s.client.B().Del().Key(key.(string)).Build()).Error() } // Invalidate invalidates some cache data in Redis for given options @@ -107,7 +103,7 @@ func (s *RueidisStore) Invalidate(ctx context.Context, options ...lib_store.Inva for _, tag := range tags { tagKey := fmt.Sprintf(RueidisTagPattern, tag) - cacheKeys, err := s.cacheCompat.SMembers(ctx, tagKey).Result() + cacheKeys, err := s.client.Do(ctx, s.client.B().Smembers().Key(tagKey).Build()).AsStrSlice() if err != nil { continue } @@ -130,9 +126,5 @@ func (s *RueidisStore) GetType() string { // Clear resets all data in the store func (s *RueidisStore) Clear(ctx context.Context) error { - if err := s.compat.FlushAll(ctx).Err(); err != nil { - return err - } - - return nil + return rueidiscompat.NewAdapter(s.client).FlushAll(ctx).Err() } diff --git a/store/rueidis/rueidis_bench_test.go b/store/rueidis/rueidis_bench_test.go index 628b59a9..7e4da24b 100644 --- a/store/rueidis/rueidis_bench_test.go +++ b/store/rueidis/rueidis_bench_test.go @@ -29,7 +29,7 @@ func BenchmarkRueidisSet(b *testing.B) { 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)) + value := fmt.Sprintf("value-%d", n) store.Set(ctx, key, value, lib_store.WithTags([]string{fmt.Sprintf("tag-%d", n)})) } @@ -51,7 +51,7 @@ func BenchmarkRueidisGet(b *testing.B) { store := NewRueidis(ruedisClient, lib_store.WithExpiration(time.Hour*4)) key := "test" - value := []byte("value") + value := "value" _ = store.Set(ctx, key, value) diff --git a/store/rueidis/rueidis_test.go b/store/rueidis/rueidis_test.go index 5af6e25b..dcdb73bc 100644 --- a/store/rueidis/rueidis_test.go +++ b/store/rueidis/rueidis_test.go @@ -37,7 +37,7 @@ func TestRueidisGet(t *testing.T) { // rueidis mock client client := mock.NewClient(ctrl) - client.EXPECT().DoCache(ctx, mock.Match("GET", "my-key"), defaultClientSideCacheExpiration).Return(mock.Result(mock.RedisString(""))) + client.EXPECT().DoCache(ctx, mock.Match("GET", "my-key"), defaultClientSideCacheExpiration).Return(mock.Result(mock.RedisString("my-value"))) store := NewRueidis(client) @@ -46,7 +46,7 @@ func TestRueidisGet(t *testing.T) { // Then assert.Nil(t, err) - assert.NotNil(t, value) + assert.Equal(t, value, "my-value") } func TestRueidisGetNotFound(t *testing.T) { @@ -66,7 +66,7 @@ func TestRueidisGetNotFound(t *testing.T) { // Then assert.NotNil(t, err) - assert.Nil(t, value) + assert.Equal(t, value, "") } func TestRueidisSet(t *testing.T) { @@ -123,8 +123,13 @@ func TestRedisSetWithTags(t *testing.T) { client := mock.NewClient(ctrl) client.EXPECT().Do(ctx, mock.Match("SET", cacheKey, cacheValue, "EX", "10")).Return(mock.Result(mock.RedisString(""))) - client.EXPECT().Do(ctx, mock.Match("SADD", "gocache_tag_tag1", "my-key")).Return(mock.Result(mock.RedisString(""))) - client.EXPECT().Do(ctx, mock.Match("EXPIRE", "gocache_tag_tag1", "2592000")).Return(mock.Result(mock.RedisString(""))) + client.EXPECT().DoMulti(ctx, + mock.Match("SADD", "gocache_tag_tag1", "my-key"), + mock.Match("EXPIRE", "gocache_tag_tag1", "2592000"), + ).Return([]rueidis.RedisResult{ + mock.Result(mock.RedisString("")), + mock.Result(mock.RedisString("")), + }) store := NewRueidis(client, lib_store.WithExpiration(time.Second*10)) @@ -162,7 +167,7 @@ func TestRedisInvalidate(t *testing.T) { ctx := context.Background() client := mock.NewClient(ctrl) - client.EXPECT().DoCache(ctx, mock.Match("SMEMBERS", "gocache_tag_tag1"), defaultClientSideCacheExpiration).Return(mock.Result(mock.RedisArray())) + client.EXPECT().Do(ctx, mock.Match("SMEMBERS", "gocache_tag_tag1")).Return(mock.Result(mock.RedisArray())) client.EXPECT().Do(ctx, mock.Match("DEL", "gocache_tag_tag1")).Return(mock.Result(mock.RedisInt64(1))) store := NewRueidis(client)