Skip to content

Commit 5c06575

Browse files
authored
Merge pull request #184 from eko/RueidisStore
Added Rueidis store
2 parents 2a92702 + 373e087 commit 5c06575

File tree

8 files changed

+504
-8
lines changed

8 files changed

+504
-8
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Here is what it brings in detail:
2929
* [Memory (go-cache)](https://github.com/patrickmn/go-cache) (patrickmn/go-cache)
3030
* [Memcache](https://github.com/bradfitz/gomemcache) (bradfitz/memcache)
3131
* [Redis](https://github.com/go-redis/redis) (go-redis/redis)
32+
* [Redis (rueidis)](https://github.com/rueian/rueidis) (rueian/rueidis)
3233
* [Freecache](https://github.com/coocood/freecache) (coocood/freecache)
3334
* [Pegasus](https://pegasus.apache.org/) ([apache/incubator-pegasus](https://github.com/apache/incubator-pegasus)) [benchmark](https://pegasus.apache.org/overview/benchmark/)
3435
* More to come soon
@@ -55,6 +56,7 @@ go get github.com/eko/gocache/v4/store/memcache
5556
go get github.com/eko/gocache/v4/store/pegasus
5657
go get github.com/eko/gocache/v4/store/redis
5758
go get github.com/eko/gocache/v4/store/rediscluster
59+
go get github.com/eko/gocache/v4/store/rueidis
5860
go get github.com/eko/gocache/v4/store/ristretto
5961
```
6062

lib/store/options.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import (
88
type Option func(o *Options)
99

1010
type Options struct {
11-
Cost int64
12-
Expiration time.Duration
13-
Tags []string
11+
Cost int64
12+
Expiration time.Duration
13+
Tags []string
14+
ClientSideCacheExpiration time.Duration
1415
}
1516

1617
func (o *Options) IsEmpty() bool {
@@ -59,3 +60,11 @@ func WithTags(tags []string) Option {
5960
o.Tags = tags
6061
}
6162
}
63+
64+
// WithClientSideCaching allows setting the client side caching, enabled by default
65+
// Currently to be used by Rueidis(redis) library only.
66+
func WithClientSideCaching(clientSideCacheExpiration time.Duration) Option {
67+
return func(o *Options) {
68+
o.ClientSideCacheExpiration = clientSideCacheExpiration
69+
}
70+
}

store/redis/redis_bench_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ func BenchmarkRedisSet(b *testing.B) {
1414
ctx := context.Background()
1515

1616
store := NewRedis(redis.NewClient(&redis.Options{
17-
Addr: "redis:6379",
18-
}), nil)
17+
Addr: "localhost:6379",
18+
}))
1919

2020
for k := 0.; k <= 10; k++ {
2121
n := int(math.Pow(2, k))
@@ -34,13 +34,13 @@ func BenchmarkRedisGet(b *testing.B) {
3434
ctx := context.Background()
3535

3636
store := NewRedis(redis.NewClient(&redis.Options{
37-
Addr: "redis:6379",
38-
}), nil)
37+
Addr: "localhost:6379",
38+
}))
3939

4040
key := "test"
4141
value := []byte("value")
4242

43-
store.Set(ctx, key, value, nil)
43+
store.Set(ctx, key, value)
4444

4545
for k := 0.; k <= 10; k++ {
4646
n := int(math.Pow(2, k))

store/rueidis/go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module github.com/eko/gocache/v4/store/rueidis
2+
3+
go 1.19
4+
5+
require (
6+
github.com/eko/gocache/v4/lib v0.0.0
7+
github.com/golang/mock v1.6.0
8+
github.com/rueian/rueidis v0.0.86
9+
github.com/stretchr/testify v1.8.1
10+
)
11+
12+
require (
13+
github.com/davecgh/go-spew v1.1.1 // indirect
14+
github.com/pmezard/go-difflib v1.0.0 // indirect
15+
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 // indirect
16+
gopkg.in/yaml.v3 v3.0.1 // indirect
17+
)
18+
19+
replace github.com/eko/gocache/v4/lib => ../../lib/

store/rueidis/go.sum

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
5+
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
6+
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
7+
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
8+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
9+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10+
github.com/rueian/rueidis v0.0.86 h1:RdzZzYnECg27zwdBHL2JN1XPVfaZclSjx6gzAQuCF/o=
11+
github.com/rueian/rueidis v0.0.86/go.mod h1:LiKWMM/QnILwRfDZIhSIXi4vQqZ/UZy4+/aNkSCt8XA=
12+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
14+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
15+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
16+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
17+
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
18+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
19+
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
20+
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
21+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
22+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
23+
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
24+
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
25+
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
26+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
27+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
28+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
29+
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
30+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
31+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
32+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
33+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
34+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
35+
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
36+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
37+
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
38+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
39+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
40+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
41+
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
42+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
43+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
44+
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
45+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
46+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
47+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
48+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
49+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
50+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
51+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
52+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
53+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

store/rueidis/rueidis.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package rueidis
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
lib_store "github.com/eko/gocache/v4/lib/store"
9+
"github.com/rueian/rueidis"
10+
"github.com/rueian/rueidis/rueidiscompat"
11+
)
12+
13+
const (
14+
// RueidisType represents the storage type as a string value
15+
RueidisType = "rueidis"
16+
// RueidisTagPattern represents the tag pattern to be used as a key in specified storage
17+
RueidisTagPattern = "gocache_tag_%s"
18+
19+
defaultClientSideCacheExpiration = 10 * time.Second
20+
)
21+
22+
// RueidisStore is a store for Redis
23+
type RueidisStore struct {
24+
client rueidis.Client
25+
options *lib_store.Options
26+
cacheCompat rueidiscompat.CacheCompat
27+
compat rueidiscompat.Cmdable
28+
}
29+
30+
// NewRueidis creates a new store to Redis instance(s)
31+
func NewRueidis(client rueidis.Client, options ...lib_store.Option) *RueidisStore {
32+
// defaults client side cache expiration to 10s
33+
appliedOptions := lib_store.ApplyOptions(options...)
34+
35+
if appliedOptions.ClientSideCacheExpiration == 0 {
36+
appliedOptions.ClientSideCacheExpiration = defaultClientSideCacheExpiration
37+
}
38+
39+
return &RueidisStore{
40+
client: client,
41+
cacheCompat: rueidiscompat.NewAdapter(client).Cache(appliedOptions.ClientSideCacheExpiration),
42+
compat: rueidiscompat.NewAdapter(client),
43+
options: appliedOptions,
44+
}
45+
}
46+
47+
// Get returns data stored from a given key
48+
func (s *RueidisStore) Get(ctx context.Context, key any) (any, error) {
49+
object := s.client.DoCache(ctx, s.client.B().Get().Key(key.(string)).Cache(), s.options.ClientSideCacheExpiration)
50+
if object.RedisError() != nil && object.RedisError().IsNil() {
51+
return nil, lib_store.NotFoundWithCause(object.Error())
52+
}
53+
return object, object.Error()
54+
}
55+
56+
// GetWithTTL returns data stored from a given key and its corresponding TTL
57+
func (s *RueidisStore) GetWithTTL(ctx context.Context, key any) (any, time.Duration, error) {
58+
// get object first
59+
object, err := s.Get(ctx, key)
60+
if err != nil {
61+
return nil, 0, err
62+
}
63+
64+
// get TTL and return
65+
ttl, err := s.cacheCompat.TTL(ctx, key.(string)).Result()
66+
if err != nil {
67+
return nil, 0, err
68+
}
69+
70+
return object, ttl, err
71+
}
72+
73+
// Set defines data in Redis for given key identifier
74+
func (s *RueidisStore) Set(ctx context.Context, key any, value any, options ...lib_store.Option) error {
75+
opts := lib_store.ApplyOptionsWithDefault(s.options, options...)
76+
err := s.compat.Set(ctx, key.(string), value, opts.Expiration).Err()
77+
if err != nil {
78+
return err
79+
}
80+
81+
if tags := opts.Tags; len(tags) > 0 {
82+
s.setTags(ctx, key, tags)
83+
}
84+
85+
return nil
86+
}
87+
88+
func (s *RueidisStore) setTags(ctx context.Context, key any, tags []string) {
89+
for _, tag := range tags {
90+
tagKey := fmt.Sprintf(RueidisTagPattern, tag)
91+
s.compat.SAdd(ctx, tagKey, key.(string))
92+
s.compat.Expire(ctx, tagKey, 720*time.Hour)
93+
}
94+
}
95+
96+
// Delete removes data from Redis for given key identifier
97+
func (s *RueidisStore) Delete(ctx context.Context, key any) error {
98+
_, err := s.compat.Del(ctx, key.(string)).Result()
99+
return err
100+
}
101+
102+
// Invalidate invalidates some cache data in Redis for given options
103+
func (s *RueidisStore) Invalidate(ctx context.Context, options ...lib_store.InvalidateOption) error {
104+
opts := lib_store.ApplyInvalidateOptions(options...)
105+
106+
if tags := opts.Tags; len(tags) > 0 {
107+
for _, tag := range tags {
108+
tagKey := fmt.Sprintf(RueidisTagPattern, tag)
109+
110+
cacheKeys, err := s.cacheCompat.SMembers(ctx, tagKey).Result()
111+
if err != nil {
112+
continue
113+
}
114+
115+
for _, cacheKey := range cacheKeys {
116+
s.Delete(ctx, cacheKey)
117+
}
118+
119+
s.Delete(ctx, tagKey)
120+
}
121+
}
122+
123+
return nil
124+
}
125+
126+
// GetType returns the store type
127+
func (s *RueidisStore) GetType() string {
128+
return RueidisType
129+
}
130+
131+
// Clear resets all data in the store
132+
func (s *RueidisStore) Clear(ctx context.Context) error {
133+
if err := s.compat.FlushAll(ctx).Err(); err != nil {
134+
return err
135+
}
136+
137+
return nil
138+
}

store/rueidis/rueidis_bench_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package rueidis
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math"
7+
"testing"
8+
"time"
9+
10+
lib_store "github.com/eko/gocache/v4/lib/store"
11+
"github.com/rueian/rueidis"
12+
)
13+
14+
func BenchmarkRueidisSet(b *testing.B) {
15+
ctx := context.Background()
16+
17+
ruedisClient, _ := rueidis.NewClient(rueidis.ClientOption{
18+
InitAddress: []string{"localhost:26379"},
19+
Sentinel: rueidis.SentinelOption{
20+
MasterSet: "mymaster",
21+
},
22+
SelectDB: 0,
23+
})
24+
25+
store := NewRueidis(ruedisClient, lib_store.WithExpiration(time.Hour*4))
26+
27+
for k := 0.; k <= 10; k++ {
28+
n := int(math.Pow(2, k))
29+
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
30+
for i := 0; i < b.N*n; i++ {
31+
key := fmt.Sprintf("test-%d", n)
32+
value := []byte(fmt.Sprintf("value-%d", n))
33+
34+
store.Set(ctx, key, value, lib_store.WithTags([]string{fmt.Sprintf("tag-%d", n)}))
35+
}
36+
})
37+
}
38+
}
39+
40+
func BenchmarkRueidisGet(b *testing.B) {
41+
ctx := context.Background()
42+
43+
ruedisClient, _ := rueidis.NewClient(rueidis.ClientOption{
44+
InitAddress: []string{"localhost:26379"},
45+
Sentinel: rueidis.SentinelOption{
46+
MasterSet: "mymaster",
47+
},
48+
SelectDB: 0,
49+
})
50+
51+
store := NewRueidis(ruedisClient, lib_store.WithExpiration(time.Hour*4))
52+
53+
key := "test"
54+
value := []byte("value")
55+
56+
_ = store.Set(ctx, key, value)
57+
58+
for k := 0.; k <= 10; k++ {
59+
n := int(math.Pow(2, k))
60+
b.Run(fmt.Sprintf("%d", n), func(b *testing.B) {
61+
for i := 0; i < b.N*n; i++ {
62+
_, _ = store.Get(ctx, key)
63+
}
64+
})
65+
}
66+
}

0 commit comments

Comments
 (0)